Pourquoi la commande "ls | "fichier"?

32

J'ai étudié la ligne de commande et appris que |(pipeline) est censé rediriger la sortie d'une commande vers l'entrée d'une autre. Alors, pourquoi la commande ls | filene fonctionne pas?

file l’entrée est l’un des noms de fichiers suivants, comme file filename1 filename2

lsla sortie est une liste de répertoires et de fichiers sur un dossier, donc je pensais ls | fileétait censé montrer 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 filecommande

IanC
la source
2
Si vous utilisez brut ls, cela signifie que vous voulez que tous les fichiers du répertoire en cours soient gérés avec la filecommande. ... Alors pourquoi ne pas simplement faire:, file *qui répondra avec une ligne pour chaque fichier, dossier.
Knud Larsen
file *est le moyen le plus intelligent, je me demandais simplement pourquoi l’utilisation de la lssortie ne fonctionnait pas. Le doute est
levé
6
Le principe est erroné: "l'entrée de fichier est l'un des noms de fichiers suivants, comme fichier file1 nomfichier2" qui n'est pas entrée. Ce sont des arguments en ligne de commande, comme le souligne @John Kugelman ci-dessous.
Monty Harder
3
Tangentiellement, l' analyse syntaxiquels est généralement une mauvaise idée.
Kojiro

Réponses:

71

Le problème fondamental est que fileles noms de fichiers doivent être utilisés comme arguments de ligne de commande et non sur stdin. Lorsque vous écrivez, ls | filele résultat de lsest transmis 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 comme variables $1, $2, $3, etc. C Vous les accès via char **argvet int argcarguments main().

  • L'entrée standard, stdin, est un flux de données. Certains programmes aiment catou wclisent à partir de stdin quand aucun argument de ligne de commande ne leur est attribué. Dans un script shell, vous pouvez utiliser readune 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. Cependant, comme le mentionne terdon , l'analyse syntaxique lsest une mauvaise idée. Le moyen le plus direct de le faire est simplement:

file *
John Kugelman soutient Monica
la source
2
Ou forcer filepour obtenir les noms de fichiers de son entrée, en utilisant ls | file -f -. Encore une mauvaise idée de
spectras
2
@ Braiam> C'est le point. Et lsla sortie de cette pipe dans file'stdin. Essaye le.
spectras
4
@ Braiam> En effet, c'est un gaspillage et dangereux. Mais cela fonctionne et il est agréable de pouvoir le comparer à de meilleures options si le PO apprend à utiliser les redirections. Par souci d’exhaustivité, je pourrais également mentionner file $(ls), qui fonctionne également, d’une autre manière encore.
spectras
2
Je pense qu'après avoir lu toutes les réponses, j'ai une vue d'ensemble de la question, même si je pense que j'aurai besoin de plus de lectures pour vraiment tout comprendre. Premièrement, apparemment, l'utilisation de la tuyauterie et de la redirection n'analyse pas la sortie en tant qu'arguments n’analyse , mais en tant que STDIN . Ce qui me reste à lire plus loin pour mieux comprendre, mais faire des arguments de recherche superficiels semble être du texte analysé dans le tableau du programme, et STDIN comme un moyen de regrouper des informations pour un fichier ou une sortie (tous les programmes ne sont pas conçus pour travailler avec ce "pooling")
IanC
3
Deuxièmement, utiliser ls pour créer une liste de noms de fichiers semble une mauvaise idée, car des caractères spéciaux sont acceptés dans les noms de fichiers mais peuvent aboutir à une sortie trompeuse dans ls . Depuis qu'il utilise des nouvelles lignes comme séparateur entre les noms de fichiers et que les noms de fichiers peuvent contenir des nouvelles lignes et d'autres caractères spéciaux, la sortie finale peut ne pas être précise.
IanC
18

Parce que, comme vous le dites, l'entrée de filedoit être un nom de fichier . 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 passer le résultat de lsà des programmes qui prennent du texte en entrée (bien que vous ne devriez vraiment pas le faire ):

$ 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 lsou 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 findest meilleur (les print0impressions d' un \0lieu d'un newilne après chaque nom de fichier et -0de la xargslaisse traiter avec une telle entrée, ce qui est une astuce pour faire vos commandes travail avec les noms de fichiers contenant des sauts de ligne):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Ce qui a aussi sa propre façon de faire cela, sans avoir besoin xargsde rien 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,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
terdon
la source
Un côté problème est que filene semble pas vraiment lire stdin à moins donné un explicite -espace réservé: comparer file foo, echo foo | fileet echo foo | file -; en fait c'est probablement la raison du message d'utilisation dans le cas des PO (c'est-à-dire que ce n'est pas vraiment parce que le résultat lsest "text simplement", mais plutôt parce que la liste d'arguments à fileest vide)
steeldriver
@steeldriver ouais. Autant que je sache, c’est le cas de tous les programmes qui attendent des fichiers et non du texte en entrée. Ils ignorent simplement stdin par défaut. Notez que echo foo | file -cela ne fonctionne pas réellement filesur le fichier foomais sur le flux stdin.
Terdon
Eh bien, il y a des canards impairs (?!) Comme catcelui-là, sauf stdin sans -sauf quand on me donne des arguments de fichier aussi, je pense?
Steeldriver
3
Cette réponse ne permet pas d'expliquer la différence entre les arguments stdin et les arguments de ligne de commande. Par conséquent, même si elle est plus pertinente que la réponse acceptée, elle reste profondément trompeuse pour la même raison.
dimanche
5
@terdon Je pense que c'est une grave erreur dans ce cas. "file (1) prend la liste des fichiers à utiliser comme arguments de ligne de commande et non comme entrée standard" est fondamental pour comprendre pourquoi la commande de l'OP n'a pas fonctionné, et la distinction est fondamentale pour les scripts shell en général; vous ne leur faites pas de faveurs en les négligeant.
Zwol
6

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 noms de fichiers en tant qu'arguments , qui sont ensuite testés. Les redirections ne transmettent pas ces noms de fichiers sous forme d'arguments, pas plus que la tuyauterie , ce que vous faites plus tard.

Ce que vous pouvez faire, c'est lire les noms de fichiers d'un fichier avec l' --files-fromoption si vous avez un fichier qui répertorie tous les fichiers que vous souhaitez tester, sinon, passez simplement les chemins d'accès à vos fichiers comme arguments.

Braiam
la source
6

La réponse acceptée explique pourquoi la commande de canal ne fonctionne pas immédiatement et file *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.

Donc, find `ls`prendra le résultat de la lscommande et le substituera comme argument de la findcommande. C'est plus long et plus compliqué que la solution acceptée, mais des variantes peuvent être utiles dans d'autres situations.

Schmuddi
la source
Je suis en train de lire un livre sur l'utilisation de la ligne de commande sous Linux (le doute venait de mon expérience) et, comme par hasard, je viens de lire sur la "substitution de commande". Vous pouvez utiliser soit $ (commande) ou command(impossible de trouver le code de barre oblique inverse sur mon téléphone) pour développer le résultat d'une commande dans le bash et l'utiliser comme paramètre pour d'autres commandes. Vraiment utile, même si l’utiliser dans ce cas (avec ls ) résulterait quand même en quelques problèmes à cause des caractères spéciaux sur certains noms de fichiers.
IanC
@IanC Malheureusement, la plupart des livres et des didacticiels sur bash sont des ordures, entachés de mauvaises pratiques, de syntaxe déconseillée, de bugs subtils; (les seules) références fiables disponibles sont les développeurs bash, c’est-à-dire le manuel et le canal IRC #bash sur freenode (consultez également les ressources liées dans le sujet du canal).
Ignis
1
Utiliser la substitution de commande peut parfois être très utile, mais dans ce contexte, il est assez pervers, en particulier avec ls.
Joe
également lié: unix.stackexchange.com/a/5782/107266
mardi
5

La sortie de lsvia un canal est un bloc solide de données avec 0x0a séparant chaque ligne - c’est-à-dire un caractère de saut de ligne - etfile obtient en tant que paramètre unique, dans lequel plusieurs caractères doivent fonctionner sur un à la fois.

En règle générale, ne jamais utiliser lspour générer une source de données pour d'autres commandes - un jour, cela va canaliser .. dansrm et vous aurez alors des problèmes!

Mieux vaut utiliser une boucle, telle que celle for i in *; do file "$i" ; donequi produira le résultat souhaité, de manière prévisible. Les citations sont là en cas de noms de fichiers avec des espaces.

Mark Williams
la source
8
plus facile: file *;-)
Wayne_Yux
3
@ IanC Je ne saurais vraiment trop insister sur le fait que l'analyse de la sortie de lsest une très, très mauvaise idée . Pas seulement parce que vous pourriez le transmettre à quelque chose de dangereux, comme rm, plus important encore, parce qu'il casse tout nom de fichier non standard.
Terdon
5
Le premier paragraphe se situe quelque part entre le non-sens trompeur et le non-sens. Les sauts de ligne n'ont aucune pertinence. Le deuxième paragraphe est correct pour la mauvaise raison. C’est mauvais d’analyser ls, mais pas parce que cela pourrait être magiquement "dirigé" vers rm.
John Kugelman soutient Monica le
1
Est-ce que les rmnoms de fichiers sont pris de l'entrée standard? Je crois que non. De même, en règle générale, l’ lsun des principaux exemples de sources de données pour l’utilisation des pipelines Unix depuis le début d’Unix. C'est pourquoi il utilise par défaut un simple nom de fichier par ligne sans attributs ni ornements lorsque sa sortie est un tube, contrairement à son formatage par défaut habituel lorsque la sortie est le terminal.
davidbak
2
@DewiMorgan Ce site Web s'adresse principalement à un public non technique. Par conséquent, répandre / encourager de mauvaises habitudes nuit à la qualité de vie de l'utilisateur et ne fait rien de bon. Sous Unix.SE ou une autre communauté technique, dont les utilisateurs ont les connaissances / moyens de viser très près de leurs pieds sans se tirer les pieds eux-mêmes, votre argument pourrait être valable (en ce qui concerne d’autres pratiques), mais dans ce cas, votre commentaire n’a pas l’air intelligent.
Ignis
4

Si vous souhaitez utiliser un tube pour alimenter, fileutilisez l'option -fqui 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 utilise -beaucoup des utilitaires de ligne de commande standard (bien qu’il soit-- parfois le cas), il est donc toujours intéressant 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 cet article pour plus de détails).

deamentiaemundi
la source
C'est quand --? Je n'ai jamais vu ça. --est typiquement l'indicateur "fin des drapeaux".
John Kugelman soutient Monica le
Oui, mais je l’ai trouvé dans quelques cas (ab) utilisés de cette manière par le programmeur. Je ne me souviens plus exactement où (j'ajouterai un commentaire si je le fais) mais je me souviens des malédictions que j'ai proférées quand je les ai découvertes et que ces malédictions étaient
clairement NSFW
2

Cela fonctionne utiliser la commande comme ci-dessous

ls | xargs file

Cela fonctionnera mieux pour moi

SuperKrish
la source