Wykonaj akcję w każdym podkatalogu za pomocą Bash

196

Pracuję nad skryptem, który musi wykonać akcję w każdym podkatalogu określonego folderu.

Jaki jest najskuteczniejszy sposób na napisanie tego?

mikewilliamson
źródło
1
Zastanów się nad ponownym sprawdzeniem i ponownym oszacowaniem odpowiedzi pod kątem poprawności - otrzymałeś akceptowaną odpowiedź, która otrzymała wiele wyświetleń pomimo poważnych błędów (f / e, uruchomienie jej w katalogu, w którym ktoś wcześniej prowadził mkdir 'foo * bar', spowoduje fooi barzostanie iterowane, nawet jeśli nie istnieją i *zostaną zastąpione listą wszystkich nazw plików, nawet tych, które nie należą do katalogu).
Charles Duffy,
1
... nawet gorzej, jeśli ktoś uruchomił mkdir -p '/tmp/ /etc/passwd /'- jeśli ktoś uruchomi skrypt zgodnie z tą praktyką, /tmpaby na przykład znaleźć katalogi do usunięcia, może skończyć się usunięciem /etc/passwd.
Charles Duffy,

Odpowiedzi:

178
for D in `find . -type d`
do
    //Do whatever you need with D
done
Mike Clark
źródło
81
to złamie się na białych
spacjach
2
Pamiętaj też, że musisz poprawić parametry, findjeśli chcesz zachować rekurencyjne lub nierekurencyjne zachowanie.
Chris Tonkinson
32
Powyższa odpowiedź dała mi również własny katalog, więc następujące działały dla mnie trochę lepiej: find . -mindepth 1 -type d
jzheaux,
1
@JoshC, oba warianty podzielą utworzony katalog mkdir 'directory name with spaces'na cztery osobne słowa.
Charles Duffy,
4
Musiałem dodać, -mindepth 1 -maxdepth 1bo poszło zbyt głęboko.
ScrappyDev
283

Wersja, która unika tworzenia podprocesu:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

Lub, jeśli twoje działanie jest pojedynczym poleceniem, jest to bardziej zwięzłe:

for D in *; do [ -d "${D}" ] && my_command; done

Lub jeszcze bardziej zwięzła wersja ( dzięki @enzotib ). Zauważ, że w tej wersji każda wartość Dbędzie miała ukośnik końcowy:

for D in */; do my_command; done
kanaka
źródło
48
Możesz uniknąć iflub za [pomocą:for D in */; do
enzotib
2
+1, ponieważ nazwy katalogów nie zaczynają się od ./ w przeciwieństwie do zaakceptowanej odpowiedzi
Hayri Uğur Koltuk
3
Ten jest poprawny nawet do spacji w nazwach katalogów +1
Alex Reinking
5
Z ostatnim poleceniem jest jeden problem: jeśli jesteś w katalogu bez podkatalogów; zwróci „* /”. Lepiej więc użyj drugiego polecenia for D in *; do [ -d "${D}" ] && my_command; donelub kombinacji dwóch ostatnich:for D in */; do [ -d $D ] && my_command; done
Chris Maes
5
Zauważ, że ta odpowiedź ignoruje ukryte katalogi. Aby dołączyć ukryte katalogi, użyj for D in .* *; dozamiast tego for D in *; do.
patryk.beza
106

Najprostszym nierekurencyjnym sposobem jest:

for d in */; do
    echo "$d"
done

Na /końcu jest napisane, że należy używać tylko katalogów.

Nie ma takiej potrzeby

  • odnaleźć
  • awk
  • ...
d0x
źródło
11
Uwaga: nie będzie to obejmować kropkowych katalogów (co może być dobrą rzeczą, ale ważne jest, aby wiedzieć).
wisbucky
Warto zauważyć, że shopt -s dotglobpodczas rozwijania symboli wieloznacznych można używać plików / plików dotdir. Zobacz także: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt
Myślę, że masz na myśli /*, zamiast */z /reprezentującą ścieżkę, której chcesz użyć.
Brōtsyorfuzthrāx,
2
@Shule /*byłoby absolutną ścieżką, podczas gdy */zawierałoby podkatalogi z bieżącej lokalizacji
Dan G
3
pomocna wskazówka: jeśli chcesz przyciąć końcowe „/” od $ d, użyj${d%/*}
Hafthor,
18

Użyj findpolecenia.

W GNU findmożesz użyć -execdirparametru:

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

lub za pomocą -execparametru:

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

lub z xargspoleceniem:

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

Lub używając pętli for :

for d in */; { echo "$d"; }

W przypadku rekurencyjności spróbuj **/zamiast tego rozszerzyć globbing ( ) (włącz: :) shopt -s extglob.


Aby uzyskać więcej przykładów, zobacz: Jak przejść do każdego katalogu i wykonać polecenie? w SO

kenorb
źródło
1
-exec {} +jest określony przez POSIX, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +jest kolejną legalną opcją i wywołuje mniej powłok niż -exec sh -c '...' {} \;.
Charles Duffy,
13

Poręczne jednowarstwowe

for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A

find * -type d ### Option B

Opcja A jest poprawna dla folderów ze spacjami pomiędzy nimi. Ponadto, ogólnie szybciej, ponieważ nie drukuje każdego słowa w nazwie folderu jako osobnej jednostki.

# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real    0m0.327s
user    0m0.084s
sys     0m0.236s

# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real    0m0.787s
user    0m0.484s
sys     0m0.308s
Sriram Murali
źródło
10

find . -type d -print0 | xargs -0 -n 1 my_command

Paul Tomblin
źródło
czy mogę wprowadzić wartość zwracaną tego znaleziska do pętli for? Jest to część większego skryptu ...
mikewilliamson
@Mike, mało prawdopodobne. $? prawdopodobnie raczej otrzyma status polecenia find lub xargs my_command.
Paul Tomblin
7

Spowoduje to utworzenie podpowłoki (co oznacza, że ​​wartości zmiennych zostaną utracone po zamknięciu whilepętli):

find . -type d | while read -r dir
do
    something
done

To nie będzie:

while read -r dir
do
    something
done < <(find . -type d)

Oba będą działać, jeśli w nazwach katalogów są spacje.

Wstrzymano do odwołania.
źródło
Aby jeszcze lepiej radzić sobie z dziwnymi nazwami plików (w tym nazwami kończącymi się białymi spacjami i / lub włączeniami linii), użyj find ... -print0iwhile IFS="" read -r -d $'\000' dir
Gordon Davisson
@GordonDavisson, ... nawet twierdziłbym, że -d ''jest to mniej mylące w kwestii składni bash i możliwości, ponieważ -d $'\000'sugeruje (fałszywie), że $'\000'w pewien sposób różni się od ''- w rzeczywistości można łatwo (i znowu fałszywie) wywnioskować z to, że bash obsługuje łańcuchy w stylu Pascala (określone długości, mogą zawierać literały NUL), a nie łańcuchy C (rozdzielone NUL, nie mogą zawierać NUL).
Charles Duffy,
5

Możesz spróbować:

#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/

for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
  cd "$f"
  <your job here>
done

lub podobne...

Wyjaśnienie:

find . -maxdepth 1 -mindepth 1 -type d: Znajdź tylko katalogi o maksymalnej rekurencyjnej głębokości 1 (tylko podkatalogi 1 USD) i minimalnej głębokości 1 (wyklucza bieżący folder .)

Henry Dobson
źródło
To jest błąd - spróbuj z nazwą katalogu ze spacjami. Zobacz BashPitfalls # 1 i DontReadLinesWithFor .
Charles Duffy,
Nazwa katalogu ze spacjami jest ujęta w cudzysłowy i dlatego działa, a OP nie próbuje odczytać wierszy z pliku.
Henry Dobson
działa w cd "$f". Nie działa, gdy dane wyjściowe findsą podzielone na ciągi, więc będziesz mieć oddzielne fragmenty nazwy jako osobne wartości $f, co sprawi, że dobrze sobie poradzisz , lub nie będziesz cytował $frozwinięcia.
Charles Duffy
Nie mówią, że były próby odczytu wiersze z pliku. findDane wyjściowe są zorientowane liniowo (teoretycznie jedna nazwa na linię - ale patrz poniżej) z domyślną -printakcją.
Charles Duffy
Wyjście zorientowane liniowo, ponieważ from find -printnie jest bezpiecznym sposobem na przekazywanie dowolnych nazw plików, ponieważ można uruchomić coś mkdir -p $'foo\n/etc/passwd\nbar'i uzyskać katalog, który ma /etc/passwdosobną linię w nazwie. Obsługa nazw z plików w katalogach /uploadlub /tmpkatalogów bez opieki to świetny sposób na ataki polegające na eskalacji uprawnień.
Charles Duffy
4

zaakceptowana odpowiedź złamie się na białych spacjach, jeśli zawierają je nazwy katalogów, a preferowaną składnią jest $()bash / ksh. Użyj GNU find -exec opcji z +;np

find .... -exec mycommand +; #this is same as passing to xargs

lub użyj pętli while

find .... |  while read -r D
do
  ...
done 
ghostdog74
źródło
jaki parametr będzie przechowywać nazwę katalogu? np. chmod + x $ DIR_NAME (tak, wiem, że istnieje opcja chmod tylko dla katalogów)
Mike Graf
Jest jedna subtelna różnica między find -execprzejściem a przejściem doxargs : findzignoruje wartość wyjściową wykonywanego polecenia, a xargszakończy się niepowodzeniem przy niezerowym wyjściu. Każda z nich może być poprawna, w zależności od potrzeb.
Jason Wodicka,
find ... -print0 | while IFS= read -r djest bezpieczniejszy - obsługuje nazwy rozpoczynające się lub kończące spacją oraz nazwy zawierające literały nowego wiersza.
Charles Duffy,