J'ai étudié la ligne de commande et appris que |
(pipeline) est destiné à rediriger le résultat d'une commande vers l'entrée d'un autre. Alors pourquoi la commande ls | file
ne fonctionne-t-elle pas?
file
input est l'un des noms de fichiers suivants, tel que file filename1 filename2
ls
output est une liste de répertoires et de fichiers sur un dossier. Je pensais donc que ls | file
était censé afficher le type de fichier de chaque fichier d’un dossier.
Quand je l'utilise cependant, la sortie est:
Usage: file [-bcEhikLlNnprsvz0] [--Apple] [--mime-encoding] [--mime-type]
[-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
file -C [-m magicfiles]
file [--help]
Comme il y avait une erreur avec l'utilisation de la commande file
Le problème fondamental est que file
s'attend à ce que les noms de fichiers soient des arguments de ligne de commande et non sur stdin. Lorsque vous écrivez ls | file
, la sortie de ls
est transmise en entrée à file
name__. Pas comme arguments, comme entrée.
Quelle est la différence?
Les arguments de ligne de commande sont utilisés lorsque vous écrivez des indicateurs et des noms de fichier après une commande, comme dans cmd arg1 arg2 arg3
. Dans les scripts Shell, ces arguments sont disponibles sous les variables $1
, $2
, $3
, etc. En C, vous y accédez via les arguments char **argv
et int argc
à main()
.
L'entrée standard, stdin, est un flux de données. Certains programmes tels que cat
ou wc
lisent à partir de stdin quand aucun argument de ligne de commande ne leur est attribué. Dans un script Shell, vous pouvez utiliser read
pour obtenir une seule ligne d'entrée. En C, vous pouvez utiliser scanf()
ou getchar()
, parmi diverses options.
file
ne lit pas normalement à partir de stdin. Il s'attend à ce qu'au moins un nom de fichier soit transmis en tant qu'argument. C'est pourquoi il imprime l'utilisation lorsque vous écrivez ls | file
, car vous n'avez pas passé d'argument.
Vous pouvez utiliser xargs
pour convertir stdin en arguments, comme dans ls | xargs file
. Néanmoins, comme terdon mentionne , analyser ls
est une mauvaise idée. Le moyen le plus direct de le faire est simplement:
file *
Parce que, comme vous le dites, l’entrée de file
doit être des noms de fichiers . La sortie de ls
, cependant, n’est que du texte. Le fait qu'il s'agisse d'une liste de noms de fichiers ne change pas le fait qu'il s'agit simplement de texte et non de l'emplacement des fichiers sur le disque dur.
Lorsque vous voyez une sortie imprimée à l'écran, vous voyez du texte. Que ce texte soit un poème ou une liste de noms de fichiers ne fait aucune différence pour l'ordinateur. Tout ce qu'il sait, c'est que c'est du texte. C’est pourquoi vous pouvez transmettre la sortie de ls
aux programmes qui prennent du texte en entrée (bien que vous vous ne devriez vraiment pas ):
$ ls / | grep etc
etc
Ainsi, pour utiliser le résultat d'une commande qui répertorie les noms de fichiers sous forme de texte (tels que ls
ou find
) en tant qu'entrée pour une commande prenant des noms de fichiers, vous devez utiliser certaines astuces. L'outil typique pour cela est xargs
:
$ ls
file1 file2
$ ls | xargs wc
9 9 38 file1
5 5 20 file2
14 14 58 total
Comme je l'ai déjà dit, vous ne voulez vraiment pas analyser la sortie de ls
. Un nom comme find
est préférable (le print0
imprime un \0
au lieu d'un newilne après chaque nom de fichier et le -0
de xargs
lui permet de gérer une telle entrée; c'est un truc pour que vos commandes fonctionnent avec les noms de fichiers contenant des nouvelles lignes):
$ find . -type f -print0 | xargs -0 wc
9 9 38 ./file1
5 5 20 ./file2
14 14 58 total
Ce qui a également sa propre façon de faire cela, sans avoir besoin de xargs
:
$ find . -type f -exec wc {} +
9 9 38 ./file1
5 5 20 ./file2
14 14 58 total
Enfin, vous pouvez également utiliser une boucle Shell. Cependant, notez que dans la plupart des cas, xargs
sera beaucoup plus rapide et plus efficace. Par exemple:
$ for file in *; do wc "$file"; done
9 9 38 file1
5 5 20 file2
La réponse acceptée explique pourquoi la commande de canal ne fonctionne pas immédiatement et avec la commande file *
, elle offre une solution simple et directe.
J'aimerais suggérer une autre solution qui pourrait être utile à un moment donné. L'astuce consiste à utiliser le caractère backtick (`)
. Le backtick est expliqué en détail ici . En bref, il prend la sortie de la commande incluse dans les backticks et la remplace par une chaîne dans la commande restante.
Ainsi, find `ls`
prendra le résultat de la commande ls
et le substituera en tant qu’argument pour la commande find
. C'est plus long et plus compliqué que la solution acceptée, mais des variantes peuvent être utiles dans d'autres situations.
appris que '|' (pipeline) est destiné à rediriger la sortie d'une commande vers l'entrée d'une autre.
Il ne "redirige" pas la sortie, mais prend la sortie d'un programme et l'utilise comme entrée, alors que le fichier ne prend pas les entrées mais les noms de fichiers en tant qu'arguments , qui sont ensuite testés. Les redirections ne transmettent pas ces noms de fichiers en tant qu'arguments non plus piping , plus tard, ce que vous faites.
Ce que vous pouvez faire est de lire les noms de fichiers dans un fichier avec l’option --files-from
si vous avez un fichier qui répertorie tous les fichiers que vous souhaitez tester, sinon transmettez simplement les chemins d’accès à vos fichiers en tant qu’arguments.
La sortie de ls
par un canal est un bloc solide de données avec 0x0a séparant chaque ligne - c’est-à-dire un saut de ligne - et file
l’obtient comme un paramètre unique, dans lequel plusieurs caractères doivent fonctionner sur un à la fois.
En règle générale, n'utilisez jamais ls
pour générer une source de données pour d'autres commandes. Un jour, il dirigera .. dans rm
et vous aurez un problème!
Mieux vaut utiliser une boucle, telle que for i in *; do file "$i" ; done
, qui produira la sortie souhaitée, de manière prévisible. Les guillemets sont là en cas de noms de fichiers avec des espaces.
Si vous souhaitez utiliser un tube pour alimenter file
name__, utilisez l'option -f
qui est normalement suivie d'un nom de fichier, mais vous pouvez également utiliser un seul trait d'union -
pour lire à partir de stdin.
$ ls
cow.pdf some.txt
$ ls | file -f -
cow.pdf: PDF document, version 1.4
some.txt: ASCII text
L'astuce avec le trait d'union -
fonctionne avec un grand nombre d'utilitaires de ligne de commande standard (bien qu'il s'agisse parfois de --
), donc cela vaut toujours la peine d'essayer.
L'outil xarg
est beaucoup plus puissant et n'est généralement nécessaire que si la liste d'arguments est trop longue (voir this post pour plus de détails).
Cela fonctionne utiliser la commande comme ci-dessous
ls | xargs file
Cela fonctionnera mieux pour moi
Cela devrait également fonctionner:
file $(ls)
comme discuté ici: https://unix.stackexchange.com/questions/5778/whats-the-difference-between-stuff-and-stuff