Pourquoi la commande “ls | "fichier"?

32

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 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 dans un dossier. J'ai donc pensé que ls | file était censé afficher le type de fichier de chaque fichier d'un dossier.

Quand je l'utilise cependant, le résultat 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 a eu une erreur d'utilisation de la commande file

    
posée IanC 06.07.2016 - 17:51
la source

8 réponses

71

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 tant qu’entrée à file . 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 comme cat ou wc lisent à partir de stdin quand aucun argument de ligne de commande ne leur est fourni. 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 normalement pas à partir de stdin. Il s'attend à ce qu'au moins un nom de fichier soit transmis en tant qu'argument. C’est pourquoi il affiche 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 mentions de terdon , l'analyse ls C'est une mauvaise idée. Le moyen le plus direct de le faire est simplement:

file *
    
réponse donnée John Kugelman 06.07.2016 - 19:15
la source
18

Parce que, comme vous le dites, l'entrée de file doit être 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, vraiment . ):

$ ls / | grep etc
etc

Ainsi, pour utiliser le résultat d'une commande qui répertorie les noms de fichiers sous forme de texte (tel que ls ou find ) comme 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 . Quelque chose comme find est préférable (le print0 imprime un -0 au lieu d'un newilne après chaque nom de fichier et le xargs de xargs lui permet de gérer cette 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

Qui a aussi sa propre façon de faire cela, sans avoir besoin de xargs du tout:

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Enfin, vous pouvez également utiliser une boucle de shell. Cependant, notez que dans la plupart des cas, %code% sera beaucoup plus rapide et plus efficace. Par exemple:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
    
réponse donnée terdon 06.07.2016 - 18:47
la source
6

learned that '|' (pipeline) is meant to redirect the output from a command to the input of another one.

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 nom de fichiers en tant qu'arguments , qui sont ensuite testés. Les redirections ne transmettent pas ces noms de fichiers en tant qu'arguments, ni piping , le plus tard, ce que vous font.

Ce que vous pouvez faire est de lire les noms de fichiers d'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 sous forme d'arguments.

    
réponse donnée Braiam 06.07.2016 - 19:26
la source
6

La réponse acceptée explique pourquoi la commande pipe ne fonctionne pas immédiatement. Avec la commande file * , elle offre une solution simple et directe.

Je voudrais 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.

    
réponse donnée Schmuddi 07.07.2016 - 01:17
la source
5

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 en tant que paramètre unique, dans lequel plusieurs caractères doivent fonctionner sur une ligne à la fois. temps.

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!

Il est préférable d’utiliser une boucle, telle que for i in *; do file "$i" ; done , qui produira le résultat souhaité, de manière prévisible. Les guillemets sont là en cas de noms de fichiers avec des espaces.

    
réponse donnée Mark Williams 06.07.2016 - 18:03
la source
4

Si vous souhaitez utiliser un tube pour alimenter file , 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 de nombreux utilitaires de ligne de commande standard (bien qu'il s'agisse de -- parfois), cela vaut donc 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 cet article pour plus de détails).

    
réponse donnée deamentiaemundi 06.07.2016 - 20:55
la source
2

Cela fonctionne, utilisez la commande comme ci-dessous

ls | xargs file

Cela fonctionnera mieux pour moi

    
réponse donnée SuperKrish 08.07.2016 - 09:54
la source
1

Cela devrait également fonctionner:

file $(ls)

comme indiqué également ici: link

    
réponse donnée matth 15.02.2017 - 12:02
la source

Lire d'autres questions sur les étiquettes