web-dev-qa-db-fra.com

Pourquoi la commande "ls | file" ne fonctionne-t-elle pas?

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

32
IanC

Le problème fondamental est que files'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 lsest transmise en entrée à filename__. 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 catou wclisent à partir de stdin quand aucun argument de ligne de commande ne leur est attribué. Dans un script Shell, vous pouvez utiliser readpour obtenir une seule ligne d'entrée. En C, vous pouvez utiliser scanf() ou getchar(), parmi diverses options.

filene 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 xargspour convertir stdin en arguments, comme dans ls | xargs file. Néanmoins, comme terdon mentionne , analyser lsest une mauvaise idée. Le moyen le plus direct de le faire est simplement:

file *
71
John Kugelman

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
18
terdon

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.

6
Schmuddi

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.

6
Braiam

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.

5
Mark Williams

Si vous souhaitez utiliser un tube pour alimenter filename__, 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 xargest 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).

4
deamentiaemundi

Cela fonctionne utiliser la commande comme ci-dessous

ls | xargs file

Cela fonctionnera mieux pour moi

2
SuperKrish

Cela devrait également fonctionner:

file $(ls)

comme discuté ici: https://unix.stackexchange.com/questions/5778/whats-the-difference-between-stuff-and-stuff

1
matth