Wie führt man einen Befehl für jede Zeile einer Datei aus?

161

Im Moment verwende ich beispielsweise Folgendes, um einige Dateien zu ändern, deren Unix-Pfade ich in eine Datei geschrieben habe:

cat file.txt | while read in; do chmod 755 "$in"; done

Gibt es einen eleganteren und sichereren Weg?

Falke
quelle

Antworten:

127

Lesen Sie eine Datei Zeile für Zeile und führen Sie Befehle aus: 4 Antworten

Dies liegt daran, dass es nicht nur 1 Antwort gibt ...

  1. shell Befehlszeilenerweiterung
  2. xargs dediziertes Werkzeug
  3. while read mit einigen Bemerkungen
  4. while read -uVerwendung dedizierter fd, für die interaktive Verarbeitung (Beispiel)

Die OP Anfrage in Bezug auf : Laufen chmodauf allen Zielen in Datei aufgeführt , xargsist das angezeigte Werkzeug. Aber für einige andere Anwendungen, kleine Menge von Dateien, etc ...

  1. Lesen Sie die gesamte Datei als Befehlszeilenargument.

    Wenn Ihre Datei nicht zu groß ist und alle Dateien einen guten Namen haben (ohne Leerzeichen oder andere Sonderzeichen wie Anführungszeichen), können Sie die shellBefehlszeilenerweiterung verwenden . Einfach:

    chmod 755 $(<file.txt)

    Für eine kleine Anzahl von Dateien (Zeilen) ist dieser Befehl der leichtere.

  2. xargs ist das richtige Werkzeug

    Für eine größere Anzahl von Dateien oder fast eine beliebige Anzahl von Zeilen in Ihrer Eingabedatei ...

    Für viele binutils Tools, wie chown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt

    Wenn Sie spezielle Zeichen und / oder viele Zeilen haben file.txt.

    xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)

    Wenn Ihr Befehl genau 1 Mal per Eintrag ausgeführt werden muss:

    xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)

    Dies wird für dieses Beispiel nicht benötigt, da chmodmehrere Dateien als Argument akzeptiert werden, dies entspricht jedoch dem Titel der Frage.

    In einigen speziellen Fällen können Sie sogar den Speicherort des Dateiarguments in Befehlen definieren, die generiert werden von xargs:

    xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)

    Test mit seq 1 5als Eingabe

    Versuche dies:

    xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
    Blah 1 blabla 1..
    Blah 2 blabla 2..
    Blah 3 blabla 3..
    Blah 4 blabla 4..
    Blah 5 blabla 5..

    Wo Befehl einmal pro Zeile ausgeführt wird .

  3. while read und Varianten.

    Wie OP vorschlagen cat file.txt | while read in; do chmod 755 "$in"; donewird funktionieren, aber es gibt 2 Probleme:

    • cat |ist eine nutzlose Gabel , und

    • | while ... ;donewird zu einer Unterschale, in der die Umgebung danach verschwindet ;done.

    Das könnte also besser geschrieben werden:

    while read in; do chmod 755 "$in"; done < file.txt

    Aber,

    • Sie können gewarnt werden $IFSund readFlaggen:

      help read
      read: read [-r] ... [-d delim] ... [name ...]
          ...
          Reads a single line from the standard input... The line is split
          into fields as with word splitting, and the first word is assigned
          to the first NAME, the second word to the second NAME, and so on...
          Only the characters found in $IFS are recognized as word delimiters.
          ...
          Options:
            ...
            -d delim   continue until the first character of DELIM is read, 
                       rather than newline
            ...
            -r do not allow backslashes to escape any characters
          ...
          Exit Status:
          The return code is zero, unless end-of-file is encountered...

      In einigen Fällen müssen Sie möglicherweise verwenden

      while IFS= read -r in;do chmod 755 "$in";done <file.txt

      Zur Vermeidung von Problemen mit Stranges-Dateinamen. Und vielleicht, wenn Sie Probleme haben mit UTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
    • Während Sie STDINzum Lesen verwenden file.txt, kann Ihr Skript nicht interaktiv sein (Sie können es nicht STDINmehr verwenden).

  4. while read -umit dedizierten fd.

    Syntax: while read ...;done <file.txtleitet STDINzu um file.txt. Das heißt, Sie werden nicht in der Lage sein, mit Prozessen umzugehen, bis sie abgeschlossen sind.

    Wenn Sie ein interaktives Tool erstellen möchten, müssen Sie die Verwendung eines STDINalternativen Dateideskriptors vermeiden und diesen verwenden .

    Konstanten Filedeskriptoren sind: 0für STDIN , 1für STDOUT und 2für STDERR . Sie konnten sie sehen durch:

    ls -l /dev/fd/

    oder

    ls -l /proc/self/fd/

    Von dort aus müssen Sie eine nicht verwendete Nummer zwischen 0und 63(tatsächlich, abhängig vom sysctlSuperuser-Tool) als Dateideskriptor auswählen :

    Für diese Demo werde ich fd verwenden 7 :

    exec 7<file.txt      # Without spaces between `7` and `<`!
    ls -l /dev/fd/

    Dann könnten Sie read -u 7diesen Weg benutzen :

    while read -u 7 filename;do
        ans=;while [ -z "$ans" ];do
            read -p "Process file '$filename' (y/n)? " -sn1 foo
            [ "$foo" ]&& [ -z "${foo/[yn]}" ]&& ans=$foo || echo '??'
        done
        if [ "$ans" = "y" ] ;then
            echo Yes
            echo "Processing '$filename'."
        else
            echo No
        fi
    done 7<file.txt

    done

    Zum Schließen fd/7:

    exec 7<&-            # This will close file descriptor 7.
    ls -l /dev/fd/

    Hinweis : Ich habe eine gestrichene Version zugelassen, da diese Syntax nützlich sein kann, wenn viele E / A-Vorgänge mit Parallelen ausgeführt werden:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo
F. Hauri
quelle
3
Wie xargsursprünglich für die Beantwortung dieser Art von Anforderungen erstellt, sorgen einige Funktionen, wie z. B. das Erstellen von Befehlen so lange wie möglich in der aktuellen Umgebung, um chmodin diesem Fall so wenig wie möglich aufzurufen , und das Reduzieren von Gabeln für Effizienz. while ;do..done <$fileimplizieren, dass 1 Gabel für 1 Datei ausgeführt wird. xargskönnte 1 Gabel für tausend Dateien ausführen ... auf zuverlässige Weise.
F. Hauri
1
Warum funktioniert der dritte Befehl in einem Makefile nicht? Ich erhalte "Syntaxfehler in der Nähe eines unerwarteten Tokens" <", aber die Ausführung direkt über die Befehlszeile funktioniert.
Woodrow Barlow
2
Dies scheint mit der Makefile-spezifischen Syntax verbunden zu sein. Sie könnten versuchen, die Befehlszeile umzukehren:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. Hauri
@ F.Hauri ist aus irgendeinem Grund tr \\n \\0 <file.txt |xargs -0 [command]etwa 50% schneller als die von Ihnen beschriebene Methode.
Phil294
Oktober 2019, neue Bearbeitung, interaktives Dateiprozessor-Beispiel hinzufügen .
F. Hauri
150

Ja.

while read in; do chmod 755 "$in"; done < file.txt

Auf diese Weise können Sie einen catProzess vermeiden .

catist für einen solchen Zweck fast immer schlecht. Sie können mehr über die nutzlose Verwendung von Cat lesen .

PP
quelle
Vermeiden Sie eine cat ist eine gute Idee, aber in diesem Fall ist der angegebene Befehlxargs
F. Hauri
Dieser Link scheint nicht relevant zu sein, vielleicht hat sich der Inhalt der Webseite geändert? Der Rest der Antwort ist jedoch fantastisch :)
Starbeamrainbowlabs
@starbeamrainbowlabs Ja. Es scheint, dass die Seite verschoben wurde. Ich habe wieder verlinkt und sollte jetzt in Ordnung sein. Danke :)
PP
1
Vielen Dank! Dies war hilfreich, insbesondere wenn Sie etwas anderes als das Aufrufen chmodausführen müssen (dh wirklich einen Befehl für jede Zeile in der Datei ausführen).
Per Lundberg
Vorsicht mit Backslashes! from unix.stackexchange.com/a/7561/28160 - " read -rliest eine einzelne Zeile aus der Standardeingabe ( readohne -rBackslashes zu interpretieren, möchten Sie das nicht)."
Dieser Brasilianer
16

Wenn Sie einen netten Selektor haben (zum Beispiel alle TXT-Dateien in einem Verzeichnis), können Sie Folgendes tun:

for i in *.txt; do chmod 755 "$i"; done

Bash für Schleife

oder eine Variante von dir:

while read line; do chmod 755 "$line"; done <file.txt
d.raev
quelle
Was nicht funktioniert, ist, dass bei Leerzeichen in der Zeile die Eingabe durch Leerzeichen und nicht durch Zeilen aufgeteilt wird.
Michael Fox
@ Michael Fox: Zeilen mit Leerzeichen können durch Ändern des Trennzeichens unterstützt werden. Um es in Zeilenumbrüche zu ändern, setzen Sie die Umgebungsvariable 'IFS' vor dem Skript / Befehl. Beispiel: export IFS = '$ \ n'
Codesniffer
Tippfehler in meinem letzten Kommentar. Sollte sein: export IFS = $ '\ n'
Codesniffer
14

Wenn Sie wissen, dass die Eingabe kein Leerzeichen enthält:

xargs chmod 755 < file.txt

Wenn die Pfade möglicherweise Leerzeichen enthalten und GNU-xargs vorhanden sind:

tr '\n' '\0' < file.txt | xargs -0 chmod 755
Glenn Jackman
quelle
Ich weiß über xargs Bescheid, aber (leider) scheint es weniger eine zuverlässige Lösung zu sein als integrierte Funktionen wie while und read. Außerdem habe ich keine GNU-xargs, aber ich verwende OS X und xargs hat hier auch die Option -0. Danke für die Antwort.
Falke
1
@hawk Nein: xargsist robust. Dieses Tool ist sehr alt und sein Code wird stark überarbeitet . Sein Ziel war es zunächst, Linien in Bezug auf Shell-Einschränkungen (64 kchar / Linie oder so etwas) zu bauen. Jetzt kann dieses Tool mit sehr großen Dateien arbeiten und die Anzahl der Verzweigungen auf den endgültigen Befehl erheblich reduzieren. Siehe meine Antwort und / oder man xargs.
F. Hauri
@hawk Weniger eine zuverlässige Lösung auf welche Weise? Wenn es unter Linux, Mac / BSD und Windows funktioniert (ja, MSYSGITs Bundles GNU xargs), ist es so zuverlässig wie es nur geht.
Camilo Martin
1
Für diejenigen, die dies noch in den Suchergebnissen finden ... Sie können GNU xargs unter macOS mit Homebrew ( brew install findutils) installieren und dann gxargsstattdessen GNU xargs mit z. B.gxargs chmod 755 < file.txt
Jase
13

Wenn Sie Ihren Befehl für jede Zeile parallel ausführen möchten, können Sie GNU Parallel verwenden

parallel -a <your file> <program>

Jede Zeile Ihrer Datei wird als Argument an das Programm übergeben. Standardmäßig werden parallelso viele Threads ausgeführt, wie Ihre CPUs zählen. Sie können es aber mit angeben-j

janisz
quelle
3

Ich sehe, dass Sie bash markiert haben, aber Perl wäre auch ein guter Weg, dies zu tun:

perl -p -e '`chmod 755 $_`' file.txt

Sie können auch einen regulären Ausdruck anwenden, um sicherzustellen, dass Sie die richtigen Dateien erhalten, z. B. um nur TXT-Dateien zu verarbeiten:

perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt

Um eine Vorschau der Ereignisse anzuzeigen, ersetzen Sie einfach die Backticks durch doppelte Anführungszeichen und stellen Sie Folgendes voran print:

perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt
1.618
quelle
2
Warum Backticks verwenden? Perl hat eine chmodFunktion
Glenn Jackman
1
Sie möchten perl -lpe 'chmod 0755, $_' file.txt- verwenden Sie -lfür die "Auto-Chomp" -Funktion
Glenn Jackman
1

Sie können auch AWK verwenden, um mehr Flexibilität beim Umgang mit der Datei zu erhalten

awk '{ print "chmod 755 "$0"" | "/bin/sh"}' file.txt

Wenn Ihre Datei ein Feldtrennzeichen hat wie:

Feld1, Feld2, Feld3

Um nur das erste Feld zu erhalten, das Sie tun

awk -F, '{ print "chmod 755 "$1"" | "/bin/sh"}' file.txt

Weitere Informationen finden Sie in der GNU-Dokumentation unter https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple

brunocrt
quelle
0

Die Logik gilt für viele andere Ziele. Und wie liest man .sh_history jedes Benutzers aus / home / filesystem? Was ist, wenn es Tausende von ihnen gibt?

#!/bin/ksh
last |head -10|awk '{print $1}'|
 while IFS= read -r line
 do
su - "$line" -c 'tail .sh_history'
 done

Hier ist das Skript https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh

BladeMight
quelle
0

Ich weiß, dass es spät ist, aber immer noch

Wenn Sie zufällig auf eine mit Windows gespeicherte Textdatei mit \r\nstatt stoßen \n, werden Sie möglicherweise durch die Ausgabe verwirrt, wenn Ihr Befehl etw nach der Lesezeile als Argument enthält. Entfernen Sie \rzum Beispiel:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
EP
quelle