Wie führe ich einen Bash-Befehl in allen möglichen Unterverzeichnissen aus?

7

Angenommen, mein Hauptverzeichnis ist /home/test

Darunter habe ich viele Unterverzeichnisse und unter Unterverzeichnissen noch viele davon und so weiter.

Beispiel:

/home/test
/home/test/subdir1
/home/test/subdir1/subdir2
/home/test/subdir1/subdir2/subdir3
/home/test/subdir1/subdir2/subdir4
/home/test/subdir1/subdir2/subdir4/subdir5

und so weiter ...

Ich möchte ein einfaches Skript, das jedes Verzeichnis verwendet und nur den pwdBefehl ausführt.

youweek1
quelle
3
Was ist der Sinn dieser Übung? Ist es einfach, eine Liste der Namen aller Verzeichnisse zu erstellen ? Oder haben Sie, wie Ihr Titel schon sagt, einen anderen Befehl, den Sie in jedem Verzeichnis aufrufen möchten, und den Sie nur pwdals Proof of Concept verwenden?
G-Man sagt "Reinstate Monica"
youweek1, unten, haben Sie kommentiert "pwd war nur ein Beispiel ... ich muss mehr Befehle in jedem Unterverzeichnis ausführen" - Anmerkung pwdist in vielerlei Hinsicht etwas Besonderes - das ist Verwirrung, da es als Beispielbefehl verwendet wird.
Volker Siegel

Antworten:

7

Lösung mit Parallel

Sie können GNU Parallel für eine kompakte, schnellere Lösung verwenden.

find . -type d -print0 | parallel -0 cd {}'&&' <command-name>

Dies funktioniert auch bei Verzeichnisnamen mit Leerzeichen und Zeilenumbrüchen einwandfrei. Was parallelhier bedeutet, ist, dass es die Ausgabe aus findjedem Verzeichnis nimmt und sie dann mit cd an die CD weiterleitet {}. Wenn das Ändern des Verzeichnisses erfolgreich ist, wird unter diesem Verzeichnis ein separater Befehl ausgeführt.

Regelmäßige Lösung mit while-Schleife

find "$PWD" -type d | while read -r line; do cd "$line" && <command-name>; done;

Beachten Sie, dass dies $PWDhier verwendet wird, da diese Variable den absoluten Pfad des aktuellen Verzeichnisses enthält, von dem aus der Befehl ausgeführt wird. Wenn Sie keinen absoluten Pfad verwenden, wird cdmöglicherweise ein Fehler in der while-Schleife ausgegeben.

Dies ist eine einfachere Lösung. Es funktioniert die meiste Zeit, außer wenn Verzeichnisnamen seltsame Zeichen wie Zeilenumbrüche enthalten (siehe Kommentare).

Shivams
quelle
Ich bin mir nicht sicher, wie robust dies sonst funktionieren wird, aber zumindest die Variablenerweiterungen müssen in Anführungszeichen gesetzt werden. Versuchen Sie es mit einem Leerzeichen in $PWDoder einem Unterverzeichnisnamen.
Volker Siegel
1
Diese schlagen bei Verzeichnisnamen fehl, die Zeilenumbrüche enthalten.
G-Man sagt "Reinstate Monica"
1
Der zweite Fehler schlägt auch für Verzeichnisnamen fehl, die mit Leerzeichen enden. Es gibt auch ein Problem darin, dass der Befehl ausgeführt wird, unabhängig davon, ob er cderfolgreich ist oder nicht.
Stéphane Chazelas
1
@shivams: Sie wollen in der Regel verwenden , -print0mit findder Ausgabe in eindeutiger Form zu erhalten (nul-getrennt statt Newline-getrennt). Verwenden Sie für das zweite Beispiel, um read -d $'\0' -r linedie Ausgabe von zu lesen find. Überprüfen Sie beim ersten Mal, ob paralleleine Option vorhanden ist, die angibt, dass die Eingabe nicht durch Zeilenumbrüche getrennt werden soll. es könnte so etwas wie genannt werden -0, -zoder --null. (Oder für einen anderen Ansatz siehe meine Antwort.)
G-Man sagt 'Reinstate Monica'
2
GNU Parallel hat -0 und --null. OP schreibt, dass er Unterverzeichnisse hat. Es handelt sich also wahrscheinlich nicht um einen böswilligen Benutzer. Ich habe noch keinen Verzeichnisnamen mit \ n gesehen, der von einem nicht böswilligen Benutzer erstellt wurde. Räume, ja. Zitate, ja. Stern, Tildes und Pfeifen, ja. Aber niemals \ n.
Ole Tange
5

Der findBefehl ist mächtig, aber das macht es ein wenig schwierig zu benutzen.
Ich bin mir ziemlich sicher, dass es das kann, was Sie brauchen - dieser Befehl unten ist "fast" das, wonach Sie fragen:

find . -type d -execdir pwd \;

Aber - dies führt den Befehl nicht in der tiefsten Verzeichnisebene aus - er wird in den Verzeichnissen ausgeführt, in denen sich andere Verzeichnisse befinden.

Also läuft es rein subdir4, weil es enthält subdir5. Aber nicht in subdir5- wie Sie wahrscheinlich erwarten.

Es ist wichtig, dass die -execdirOption verwendet wird, nicht die bekanntere -exec(siehe man find):

Die -execOption führt den Befehl im Startverzeichnis aus und hat auch andere Nachteile:

    -exec command ;
           Execute command; true if 0 status  is  returned.   All  following
           arguments  to find are taken to be arguments to the command until
           an argument consisting of ';' is encountered.  The string '{}' is
           replaced  by  the current file name being processed everywhere it
           occurs in the arguments to the command,  not  just  in  arguments
           where  it  is  alone, as in some versions of find.  Both of these
           constructions might need to be escaped (with a \ ) or quoted  to
           protect  them from expansion by the shell.  See the EXAMPLES sec
           tion for examples of the use of the -exec option.  The  specified
           command  is  run once for each matched file.  The command is exe
           cuted in the starting directory.   There are unavoidable security
           problems  surrounding use of the -exec action; you should use the
          -execdir option instead.

Aber die Option -execdirist genau das, wonach Sie fragen:

   -execdir command ;
   -execdir command {} +
           Like  -exec,  but the specified command is run from the subdirec
           tory containing the matched  file,  which  is  not  normally  the
           directory  in  which  you  started find.  This a much more secure
           method for invoking commands, as it avoids race conditions during
           resolution  of the paths to the matched files.  As with the -exec
           action, the '+' form of -execdir will build  a  command  line  to
           process  more  than one matched file, but any given invocation of
           command will only list files that exist in the same subdirectory.
           If  you use this option, you must ensure that your $PATH environ
           ment variable does not reference '.'; otherwise, an attacker  can
           run any commands they like by leaving an appropriately-named file
           in a directory in which you will run -execdir.  The same  applies
           to having entries in $PATH which are empty or which are not abso
           lute directory names.
Volker Siegel
quelle
1
Hmm ... das würde den gleichen Befehl mehrere Male auf dem gleichen Verzeichnis mit mehreren Kindern laufen (zB wenn es 100 dirs eine Ebene darunter dir1wird es execdir pwd100 - mal).
don_crissti
Oh, das stimmt ...
Volker Siegel
1

Eine der anderen Antworten kam dem nahe:

finden . -Typ d -exec sh -c 'cd "$ 0" && cmd ' {} \;

(Ausführen des Befehls nur, wenn dies cderfolgreich ist). Einige Leute empfehlen, ein Dummy-Argument einzufügen, damit das gefundene Verzeichnis ( {}) zu Folgendem wechselt $1:

finden . -Typ d -exec sh -c 'cd "$ 1" && cmd ' foo {} ";"

Natürlich können Sie hier anstelle von auch eine beliebige Zeichenfolge verwenden foo. Gemeinsame Entscheidungen sind -, --und sh. Diese Zeichenfolge wird in Fehlermeldungen (falls vorhanden) verwendet, z.

foo: Zeile 0: cd: eingeschränktes_Verzeichnis : Berechtigung verweigert

so shscheint eine gute Wahl zu sein.  \;und ";"sind absolut gleichwertig; Das ist nur eine Stilvorliebe.

Die obigen Befehle führen die Shell für jedes Verzeichnis einmal aus. Dies kann mit Leistungseinbußen verbunden sein (insbesondere wenn der auszuführende Befehl relativ leicht ist). Alternativen sind

finden . -Typ d -exec sh -c 'für d; do (cd "$ d" && cmd ); erledigt 'sh {} +

die die Shell einmal ausführt, aber für jedes Verzeichnis einmal gabelt, und

finden . -type d -exec sh -c 'save_d = $ PWD; für d; CD "$ d" && cmd ; cd "$ save_d"; erledigt 'sh {} +

das bringt keine Subshells hervor. Wenn Sie einen absoluten Pfad suchen, wie Ihre Frage nahelegt, können Sie das oben Genannte kombinieren und tun

find / home / test -typ d -exec sh -c 'für d; CD "$ d" && cmd ; erledigt 'sh {} +

Das bringt keine Subshells hervor und muss sich auch nicht darum kümmern, den Startpunkt zu speichern.

G-Man sagt "Reinstate Monica"
quelle
Beachten Sie, dass POSIX-Shells bereits festgelegt sind $OLDPWD, sodass Sie keine zusätzliche Kopie speichern müssen $save_d. Die POSIX-Syntax zum Schleifen über "$ @" ist for d donicht for d; do(obwohl letztere ebenfalls weitgehend unterstützt wird (außer in der Bourne-Shell, die ohnehin keine POSIX-Shell ist)).
Stéphane Chazelas
Ja, aber wenn cmdes durch eine Folge von Befehlen einschließlich anderer cdBefehle ersetzt wird, ist dies $OLDPATHnicht mehr das Verzeichnis, in dem das ausgeführt findwird.
G-Man sagt "Reinstate Monica"
Wahr. Beachten Sie, dass außer mit bashund pdkshbasierend auf Implementierungen von sh, wenn cmdes sich um einen externen Befehl handelt, (cd "$d" && cmd)keinen zusätzlichen Prozess (im Vergleich zu demselben ohne die Subshell) verzweigt, da er cmddirekt im Subshell-Prozess ausgeführt wird. Es wäre also eine Schande, ihn zu vermeiden Leistungsgründe in diesen Fällen.
Stéphane Chazelas
Nützlich zu wissen. Aber ich glaube wieder, dass Sie davon ausgehen, dass dies cmdein einziger Befehl ist.  (cd "$d" && cmd₁ && cmd₂)würde benötigen einen zusätzlichen Prozess gabeln, wäre es nicht?
G-Man sagt "Reinstate Monica"
Nicht, wenn der letzte cmd extern ist (und die Shell nicht bash- oder pdksh-basiert ist)
Stéphane Chazelas
0

Sie müssen keinen zusätzlichen pwd Befehl ausführen. Sie können den folgenden Befehl ausführen:

find test/ -type d

Sie können Ihr Verzeichnis durch ersetzen test/

Persischer Golf
quelle
pwd war nur ein Beispiel ... ich muss mehr Befehle in jedem Unterverzeichnis
ausführen
@ youweek1 pwdist in vielerlei Hinsicht etwas Besonderes - das sorgt für Verwirrung.
Volker Siegel
0

Sie können so etwas verwenden

find /home/test/ -type d -exec bash -c '<Command to be executed>' \; 

Beispiel:

Der folgende Befehl zeigt den Verzeichnisnamen an, ähnlich wie beim Ausführen eines pwd-Befehls.

find /home/test/ -type d -exec bash -c 'echo $0' \;
rahul
quelle
Wirft Fehler: find: missing argument to -exec'`
Shivams