Wie wende ich einen Shell-Befehl auf jede Zeile einer Befehlsausgabe an?

192

Angenommen, ich habe eine Ausgabe von einem Befehl (z. B. ls -1):

a
b
c
d
e
...

Ich möchte echonacheinander einen Befehl (sagen wir ) auf jeden anwenden . Z.B

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

Was ist der einfachste Weg, dies in Bash zu tun?

Alex Budovski
quelle
3
ls -1mag hier ein Beispiel sein, aber es ist wichtig zu bedenken, dass es nicht gut ist, die Ausgabe von zu analysieren ls. Siehe: mywiki.wooledge.org/ParsingLs
Codeforester

Antworten:

217

Es ist wahrscheinlich am einfachsten zu bedienen xargs. In deinem Fall:

ls -1 | xargs -L1 echo

Das -LFlag stellt sicher, dass die Eingabe richtig gelesen wird. Aus der Manpage von 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
quelle
24
lsautomatisch -1in einem Rohr.
Bis auf weiteres angehalten.
5
@ Tennis, sieht nicht so aus: ls | xargs -L2 echound ls -1 | xargs -L2 echogeben zwei verschiedene Ausgänge. Ersteres ist alles in einer Zeile.
Alex Budovski
2
@ Alex: Ich bekomme die gleiche Ausgabe.
Bis auf weiteres angehalten.
6
xargskann nur ausführbare Dateien ausführen, keine Shell-Funktionen oder integrierten Shell-Befehle. Für die erstere ist die beste Lösung wahrscheinlich die mit readeiner Schleife.
Pabouk
12
Ich wünschte, diese Antwort enthielt eine Erklärung, wofür es -L1ist.
Wyck
161

Sie können in jeder Zeile eine grundlegende Voranstelloperation verwenden:

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

Oder Sie können die Ausgabe für komplexere Vorgänge an sed weiterleiten:

ls -1 | sed 's/^\(.*\)$/echo \1/'
Trey Hunner
quelle
1
Der Befehl sed scheint nicht zu funktionieren: Es sh: cho: not found a sh: cho: not foundsieht so aus, als würde das eIn-Echo ein sed-Befehl oder so sein.
Alex Budovski
+1 für die whileSchleife. cmd1 | while read line; do cmd2 $line; done. Oder while read line; do cmd2 $line; done < <(cmd1)was keine Unterschale erzeugt. Dies ist die vereinfachte Version Ihres sedBefehls:sed 's/.*/echo &/'
Bis auf weiteres angehalten.
1
@Alex: Ändern Sie die doppelten Anführungszeichen in einfache Anführungszeichen.
Bis auf weiteres angehalten.
5
Zitieren Sie die "$line"while-Schleife, um eine Wortteilung zu vermeiden.
Ignis
3
Versuchen Sie es mit read -r line, um ein readDurcheinander mit maskierten Zeichen zu verhindern . Zum Beispiel echo '"a \"nested\" quote"' | while read line; do echo "$line"; donegibt "a "nested" quote", was seine Flucht verloren hat. Wenn wir das tun echo '"a \"nested\" quote"' | while read -r line; do echo "$line"; done, bekommen wir "a \"nested\" quote"wie erwartet. Siehe wiki.bash-hackers.org/commands/builtin/read
Warbo
9

Sie können eine for-Schleife verwenden :

für Datei in *; machen
   Echo "$ file"
getan

Beachten Sie, dass die Verwendung von xargs fast immer effizienter ist , wenn der betreffende Befehl mehrere Argumente akzeptiert, da das betreffende Dienstprogramm nur einmal und nicht mehrmals erzeugt werden muss.

Michael Aaron Safyan
quelle
1
Es lohnt sich, die ordnungsgemäße / sichere Verwendung von Xargs zu beschreiben, d. H. printf '%s\0' * | xargs -0 ...- Ansonsten ist es ziemlich unsicher mit Dateinamen mit Leerzeichen, Anführungszeichen usw.
Charles Duffy
8

Sie können sed tatsächlich verwenden, um dies zu tun, vorausgesetzt, es ist GNU sed.

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

Wie es funktioniert:

  1. Ersetzen Sie die Übereinstimmung durch die Befehlsübereinstimmung
  2. Bei Ersetzung Befehl ausführen
  3. Ersetzen Sie die ersetzte Zeile durch die Befehlsausgabe.
Łukasz Daniluk
quelle
Fantastisch. Ich habe vergessen, den Befehl e am Ende zu setzen, aber nachdem ich Ihre Antwort gesehen habe, hat es funktioniert. Ich habe versucht, eine zufällige ID zwischen 1000 und 15000 anzuhängen, wenn SED mit einer Zeile übereinstimmt. 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 schlägt mit Backslashes, Anführungszeichen fehl. Es muss so etwas sein

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

xargs -0 Option:

-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 -1Beendet die Elemente mit Zeilenumbrüchen und trübersetzt sie in Nullzeichen.

Dieser Ansatz ist etwa 50-mal langsamer als das manuelle Iterieren mit for ...(siehe Antwort von Michael Aaron Safyan ) (3,55 s gegenüber 0,066 s). Bei anderen Eingabebefehlen wie Suchen, Suchen, Lesen aus einer Datei ( tr \\n \\0 <file) oder ähnlichem müssen Sie jedoch so arbeiten xargs.

phil294
quelle
1

Besseres Ergebnis für mich:

ls -1 | xargs -L1 -d "\n" CMD
Andrej Pandovich
quelle
Besser, aber nicht perfekt. find . -mindepth 1 -maxdepth 1 -print0 | xargs -0 commandbehandelt Fälle, in denen die Ausgabe von nicht ls -1eindeutig ist; Verwenden Sie -printf '%P\0'eher, als -print0wenn Sie nicht eine Führung ./auf jedem wollen.
Charles Duffy
1

Ich mag es, Gawk zu verwenden, um zum Beispiel mehrere Befehle auf einer Liste auszuführen

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

Das Entkommen der flüchtigen Charaktere kann jedoch etwas haarig werden.

Chris
quelle