Wie gehe ich zu jedem Verzeichnis und führe einen Befehl aus?

143

Wie schreibe ich einen Bash - Skript , das in einem parent_directory durch jedes Verzeichnis geht und führt einen Befehl in jedem Verzeichnis .

Die Verzeichnisstruktur ist wie folgt:

parent_directory (Name kann alles sein - folgt keinem Muster)

  • 001 (Verzeichnisnamen folgen diesem Muster)
    • 0001.txt (Dateinamen folgen diesem Muster)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

Die Anzahl der Verzeichnisse ist unbekannt.

Van de Graff
quelle

Antworten:

108

Sie können Folgendes tun, wenn Ihr aktuelles Verzeichnis lautet parent_directory:

for d in [0-9][0-9][0-9]
do
    ( cd "$d" && your-command-here )
done

Die (und )erstellen eine Unterschale, damit das aktuelle Verzeichnis im Hauptskript nicht geändert wird.

Mark Longair
quelle
5
Dies spielt hier keine Rolle, da der Platzhalter nur mit Zahlen übereinstimmt. Im Allgemeinen möchten Sie den Verzeichnisnamen jedoch immer in doppelte Anführungszeichen innerhalb der Schleife setzen. cd "$d"Dies wäre insofern besser, als es in Situationen übertragen wird, in denen der Platzhalter mit Dateien übereinstimmt, deren Namen Leerzeichen und / oder Shell-Metazeichen enthalten.
Tripleee
1
(Alle Antworten hier scheinen den gleichen Fehler zu haben, aber es ist am wichtigsten in der am besten
bewerteten
1
Außerdem ist es der überwiegenden Mehrheit der Befehle egal, in welchem ​​Verzeichnis Sie sie ausführen. for f in foo bar; do cat "$f"/*.txt; done >outputist funktional äquivalent zu cat foo/*.txt bar/*.txt >output. Es handelt sich jedoch lsum einen Befehl, der je nach Übergabe der Argumente eine geringfügig andere Ausgabe erzeugt. In ähnlicher Weise unterscheidet sich ein grepoder, wcdas einen relativen Dateinamen ausgibt,, wenn Sie ihn in einem Unterverzeichnis ausführen (aber häufig möchten Sie genau aus diesem Grund vermeiden, in ein Unterverzeichnis zu wechseln).
Tripleee
158

Diese Antwort von Todd hat mir geholfen.

find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;

Die \( ! -name . \) Vermeiden den Befehl im aktuellen Verzeichnis auszuführen.

Christian Vielma
quelle
4
Und wenn Sie den Befehl nur in bestimmten Ordnern ausführen möchten:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K
3
Ich musste tun, -exec bash -c 'cd "$0" && pwd' {} \;da einige Verzeichnisse einfache Anführungszeichen und einige doppelte Anführungszeichen enthielten.
Charlie Gorichanaz
12
Sie können auch das aktuelle Verzeichnis weglassen, indem Sie-mindepth 1
mdziob
Wie kann man dies in eine Funktion ändern, die einen Befehl wie "git remote get-url origin" anstelle von pwddirekt
akzeptiert
disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}nicht arbeiten.
Roachsinai
48

Wenn Sie GNU verwenden find, können Sie -execdirParameter ausprobieren , z.

find . -type d -execdir realpath "{}" ';'

oder (gemäß @gniourf_gniourf Kommentar ):

find . -type d -execdir sh -c 'printf "%s/%s\n" "$PWD" "$0"' {} \;

Hinweis: Sie können ${0#./}anstelle von verwenden $0, um ./in der Front zu reparieren .

oder praktischeres Beispiel:

find . -name .git -type d -execdir git pull -v ';'

Wenn Sie das aktuelle Verzeichnis einschließen möchten, ist es noch einfacher, wenn Sie Folgendes verwenden -exec:

find . -type d -exec sh -c 'cd -P -- "{}" && pwd -P' \;

oder mit xargs:

find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'

Oder ein ähnliches Beispiel, das von @gniourf_gniourf vorgeschlagen wurde :

find . -type d -print0 | while IFS= read -r -d '' file; do
# ...
done

Die obigen Beispiele unterstützen Verzeichnisse mit Leerzeichen im Namen.


Oder durch Zuweisen in ein Bash-Array:

dirs=($(find . -type d))
for dir in "${dirs[@]}"; do
  cd "$dir"
  echo $PWD
done

Ändern Sie .Ihren spezifischen Ordnernamen. Wenn Sie nicht rekursiv ausgeführt werden müssen, können Sie dirs=(*)stattdessen Folgendes verwenden. Das obige Beispiel unterstützt keine Verzeichnisse mit Leerzeichen im Namen.

Wie @gniourf_gniourf vorgeschlagen hat, ist die einzig richtige Möglichkeit, die Ausgabe von find in ein Array zu setzen, ohne eine explizite Schleife zu verwenden, in Bash 4.4 verfügbar mit:

mapfile -t -d '' dirs < <(find . -type d -print0)

Oder kein empfohlener Weg (der das Parsen vonls beinhaltet ):

ls -d */ | awk '{print $NF}' | xargs -n1 sh -c 'cd $0 && pwd && echo Do stuff'

Das obige Beispiel würde das aktuelle Verzeichnis ignorieren (wie von OP angefordert), aber es wird bei Namen mit den Leerzeichen unterbrochen.

Siehe auch:

Kenorb
quelle
1
Die einzig richtige Möglichkeit, die Ausgabe von findin ein Array zu setzen, ohne eine explizite Schleife zu verwenden, ist in Bash 4.4 mit verfügbar mapfile -t -d '' dirs < <(find . -type d -print0). Bis dahin besteht Ihre einzige Option darin, eine Schleife zu verwenden (oder die Operation auf zu verschieben find).
gniourf_gniourf
1
Tatsächlich benötigen Sie das dirsArray nicht wirklich . Sie könnten den findAusgang (sicher) wie folgt durchlaufen : find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
gniourf_gniourf
@gniourf_gniourf Ich bin visuell und versuche immer, die Dinge zu finden / zu machen, die einfach aussehen. ZB habe ich dieses Skript und die Verwendung des Bash-Arrays ist für mich einfach und lesbar (indem ich die Logik in kleinere Teile zerlege, anstatt Pipes zu verwenden). Einführung zusätzliche Rohre IFS, readgibt es den Eindruck zusätzlicher Komplexität. Ich muss darüber nachdenken, vielleicht gibt es einen einfacheren Weg, es zu tun.
Kenorb
Wenn mit leichter gemeint ist gebrochen, dann ja, es gibt einfachere Möglichkeiten. Nun mach dir keine Sorgen, all diese IFSund read(wie Sie sagen) , um eine zweite Natur , wie Sie sie zu gewöhnen ... sie sind Teil der kanonischen und idiomatischen Möglichkeiten von Skripten zu schreiben.
gniourf_gniourf
Übrigens, Ihr erster Befehl find . -type d -execdir echo $(pwd)/{} ';'macht nicht das, was Sie wollen (der Befehl $(pwd)wird erweitert, bevor er findüberhaupt ausgeführt wird)…
gniourf_gniourf
29

Sie können dies erreichen, indem Sie Rohrleitungen verwenden und dann verwenden xargs. Der Haken ist, dass Sie das -IFlag verwenden müssen, das den Teilstring in Ihrem bash-Befehl durch den Teilstring ersetzt, der von jedem der Befehle übergeben wird xargs.

ls -d */ | xargs -I {} bash -c "cd '{}' && pwd"

Möglicherweise möchten Sie durch einen pwdbeliebigen Befehl ersetzen, den Sie in jedem Verzeichnis ausführen möchten.

Piyush Singh
quelle
2
Ich weiß nicht, warum dies nicht positiv bewertet wurde, aber das einfachste Skript, das ich bisher gefunden habe. Danke
Linvi
20

Wenn der Ordner auf oberster Ebene bekannt ist, können Sie einfach Folgendes schreiben:

for dir in `ls $YOUR_TOP_LEVEL_FOLDER`;
do
    for subdir in `ls $YOUR_TOP_LEVEL_FOLDER/$dir`;
    do
      $(PLAY AS MUCH AS YOU WANT);
    done
done

Auf dem $ (SPIELEN SIE SO VIEL, WIE SIE WOLLEN); Sie können so viel Code eingeben, wie Sie möchten.

Beachten Sie, dass ich in keinem Verzeichnis "cd" habe.

Prost,

gforcada
quelle
3
Es gibt hier einige Beispiele für "Nutzlose Verwendung von ls" - Sie können dies for dir in $YOUR_TOP_LEVEL_FOLDER/*stattdessen einfach tun .
Mark Longair
1
Nun, vielleicht ist es im Allgemeinen nutzlos, aber es erlaubt das Filtern direkt im ls (dh alle Verzeichnisse endeten mit .tmp). Deshalb habe ich den ls $dirAusdruck
gforcada
13
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done
Idelic
quelle
Und das ist sehr leicht zu verstehen!
Rbjz
6

Während ein Liner für den schnellen und schmutzigen Gebrauch gut ist, bevorzuge ich unten eine ausführlichere Version zum Schreiben von Skripten. Dies ist die Vorlage, die ich verwende, die sich um viele Randfälle kümmert und es Ihnen ermöglicht, komplexeren Code für die Ausführung in einem Ordner zu schreiben. Sie können Ihren Bash-Code in die Funktion dir_command schreiben. Im Folgenden implementiert dir_coomand die Kennzeichnung jedes Repositorys in git als Beispiel. Der Rest des Skripts ruft dir_command für jeden Ordner im Verzeichnis auf. Das Beispiel für das Durchlaufen nur eines bestimmten Ordnersatzes ist ebenfalls enthalten.

#!/bin/bash

#Use set -x if you want to echo each command while getting executed
#set -x

#Save current directory so we can restore it later
cur=$PWD
#Save command line arguments so functions can access it
args=("$@")

#Put your code in this function
#To access command line arguments use syntax ${args[1]} etc
function dir_command {
    #This example command implements doing git status for folder
    cd $1
    echo "$(tput setaf 2)$1$(tput sgr 0)"
    git tag -a ${args[0]} -m "${args[1]}"
    git push --tags
    cd ..
}

#This loop will go to each immediate child and execute dir_command
find . -maxdepth 1 -type d \( ! -name . \) | while read dir; do
   dir_command "$dir/"
done

#This example loop only loops through give set of folders    
declare -a dirs=("dir1" "dir2" "dir3")
for dir in "${dirs[@]}"; do
    dir_command "$dir/"
done

#Restore the folder
cd "$cur"
Shital Shah
quelle
5

Ich verstehe den Punkt beim Formatieren der Datei nicht, da Sie nur durch Ordner iterieren möchten ... Suchen Sie nach so etwas?

cd parent
find . -type d | while read d; do
   ls $d/
done
Aif
quelle
4

Sie können verwenden

find .

um alle Dateien / Verzeichnisse im aktuellen Verzeichnis rekursiv zu durchsuchen

Dann können Sie die Ausgabe mit dem Befehl xargs wie folgt weiterleiten

find . | xargs 'command here'
Dan Bizdadea
quelle
3
Dies wurde nicht gefragt.
Kenorb
0
for p in [0-9][0-9][0-9];do
    (
        cd $p
        for f in [0-9][0-9][0-9][0-9]*.txt;do
            ls $f; # Your operands
        done
    )
done
Fedir RYKHTIK
quelle
Sie müssten das Verzeichnis erneut cdsichern oder in einer Subshell ausführen.
Mark Longair
Downvote: Die Bearbeitung hat das verloren, cdso dass dieser Code eigentlich nichts Nützliches tut.
Tripleee