Comment appliquer la commande shell à chaque ligne d'une sortie de commande?

192

Supposons que j'ai une sortie d'une commande (telle que ls -1):

a
b
c
d
e
...

Je veux appliquer une commande (disons echo) à chacun d'eux, à tour de rôle. Par exemple

echo a
echo b
echo c
echo d
echo e
...

Quelle est la façon la plus simple de faire ça en bash?

Alex Budovski
la source
3
ls -1peut être un exemple ici, mais il est important de se rappeler qu'il n'est pas bon d'analyser la sortie de ls. Voir: mywiki.wooledge.org/ParsingLs
codeforester

Réponses:

217

C'est probablement le plus simple à utiliser xargs. Dans ton cas:

ls -1 | xargs -L1 echo

L' -Lindicateur garantit que l'entrée est lue correctement. Depuis la page de manuel de xargs:

-L number
    Call utility for every number non-empty lines read. 
    A line ending with a space continues to the next non-empty line. [...]
Michael Mrozek
la source
24
lsfait automatiquement -1dans un tuyau.
Suspendu jusqu'à nouvel ordre.
5
@Dennis, ça ne ressemble pas à ça: ls | xargs -L2 echoet ls -1 | xargs -L2 echodonne deux sorties différentes. Le premier étant tous sur une seule ligne.
Alex Budovski
2
@Alex: j'obtiens le même résultat.
Suspendu jusqu'à nouvel ordre.
6
xargsne peut exécuter que des fichiers exécutables et non des fonctions shell ou des commandes intégrées du shell. Pour le premier, la meilleure solution est probablement celle avec readune boucle.
pabouk
12
Je souhaite que cette réponse comprenne une explication de ce à quoi cela -L1sert.
Wyck
164

Vous pouvez utiliser une opération de préfixe de base sur chaque ligne:

ls -1 | while read line ; do echo $line ; done

Ou vous pouvez diriger la sortie vers sed pour des opérations plus complexes:

ls -1 | sed 's/^\(.*\)$/echo \1/'
Trey Hunner
la source
1
La commande sed ne semble pas fonctionner: on sh: cho: not found a sh: cho: not founddirait qu'elle prend ein echo pour être une commande sed ou quelque chose.
Alex Budovski
+1 pour la whileboucle. cmd1 | while read line; do cmd2 $line; done. Ou while read line; do cmd2 $line; done < <(cmd1)qui ne crée pas de sous-shell. Voici la version simplifiée de votre sedcommande:sed 's/.*/echo &/'
pause jusqu'à nouvel ordre.
1
@Alex: changez les guillemets doubles en guillemets simples.
Suspendu jusqu'à nouvel ordre.
5
Citez la "$line"boucle while, afin d'éviter la division des mots.
ignis
3
Essayez d'utiliser read -r linepour éviter de readjouer avec les caractères échappés. Par exemple echo '"a \"nested\" quote"' | while read line; do echo "$line"; donedonne "a "nested" quote", qui a perdu son échappatoire. Si nous le faisons, echo '"a \"nested\" quote"' | while read -r line; do echo "$line"; donenous obtenons "a \"nested\" quote"comme prévu. Voir wiki.bash-hackers.org/commands/builtin/read
Warbo
9

Vous pouvez utiliser une boucle for :

pour fichier dans *; faire
   echo "$ fichier"
terminé

Notez que si la commande en question accepte plusieurs arguments, utiliser xargs est presque toujours plus efficace car il suffit de générer l'utilitaire en question une fois au lieu de plusieurs fois.

Michael Aaron Safyan
la source
1
Cela vaut la peine de décrire l'utilisation correcte / sûre de xargs, c'est-à-dire. printf '%s\0' * | xargs -0 ...- sinon, c'est assez dangereux avec les noms de fichiers avec des espaces, des guillemets, etc.
Charles Duffy
8

Vous pouvez en fait utiliser sed pour le faire, à condition que ce soit GNU sed.

... | sed 's/match/command \0/e'

Comment ça fonctionne:

  1. Remplacer la correspondance par la correspondance de commande
  2. Lors de la substitution, exécuter la commande
  3. Remplacez la ligne substituée par la sortie de commande.
Łukasz Daniluk
la source
Fantastique. J'ai oublié de mettre la commande e à la fin, mais je l' ai fait après avoir vu votre réponse et cela a fonctionné. J'essayais d'ajouter un ID aléatoire entre 1000 et 15000, lorsque SED correspond à une ligne. cat /logs/lfa/Modified.trace.log.20150904.pw | sed -r 's/^(.*)(\|006\|00032\|)(.*)$/echo "\1\2\3 - ID `shuf -i 999-14999 -n 1`"/e'
sgsi
2

xargs échoue avec des barres obliques inverses, des guillemets. Cela doit être quelque chose comme

ls -1 |tr \\n \\0 |xargs -0 -iTHIS echo "THIS is a file."

Option xargs -0:

-0, --null
          Input  items are terminated by a null character instead of by whitespace, and the quotes and backslash are
          not special (every character is taken literally).  Disables the end of file string, which is treated  like
          any  other argument.  Useful when input items might contain white space, quote marks, or backslashes.  The
          GNU find -print0 option produces input suitable for this mode.

ls -1termine les éléments avec des caractères de nouvelle ligne, donc les trtraduit en caractères nuls.

Cette approche est environ 50 fois plus lente que l'itération manuelle avec for ...(voir la réponse de Michael Aaron Safyan ) (3,55s contre 0,066s). Mais pour d'autres commandes d'entrée comme localiser, rechercher, lire à partir d'un fichier ( tr \\n \\0 <file) ou similaire, vous devez travailler avec xargscomme ceci.

phil294
la source
1

Meilleur résultat pour moi:

ls -1 | xargs -L1 -d "\n" CMD
Andrej Pandovich
la source
Mieux, mais pas parfait. find . -mindepth 1 -maxdepth 1 -print0 | xargs -0 commandtraitera les cas où la sortie de ls -1est ambiguë; utilisez -printf '%P\0'plutôt que -print0si vous ne voulez pas de tête ./sur chacun.
Charles Duffy
1

j'aime utiliser gawk pour exécuter plusieurs commandes sur une liste, par exemple

ls -l | gawk '{system("/path/to/cmd.sh "$1)}'

cependant, la fuite des personnages échappables peut devenir un peu poilue.

Chris
la source