Esegui un'azione in ogni sottodirectory usando Bash


196

Sto lavorando a uno script che deve eseguire un'azione in ogni sottodirectory di una cartella specifica.

Qual è il modo più efficace per scriverlo?


1
Ti preghiamo di considerare di rivalutare e rivalutare le risposte per correttezza: hai una risposta accettata che ottiene molte visualizzazioni nonostante i principali bug (f / e, eseguirla su una directory in cui qualcuno precedentemente eseguito mkdir 'foo * bar'causerà fooe barverrà ripetuta anche se non esistono e *verranno sostituiti con un elenco di tutti i nomi di file, anche quelli non di directory).
Charles Duffy,

1
... ancora peggio se qualcuno corre mkdir -p '/tmp/ /etc/passwd /'- se qualcuno esegue uno script seguendo questa pratica /tmpper, per esempio, trovare directory da eliminare, potrebbero finire per cancellarle /etc/passwd.
Charles Duffy,

Risposte:


179
for D in `find . -type d`
do
    //Do whatever you need with D
done

81
questo si spezzerà su spazi bianchi
ghostdog74

2
Si noti inoltre che è necessario modificare i findparametri se si desidera un comportamento ricorsivo o non ricorsivo.
Chris Tonkinson,

32
La risposta di cui sopra mi ha dato anche la directory personale, quindi per me ha funzionato un po 'meglio: find . -mindepth 1 -type d
jzheaux,

1
@JoshC, entrambe le varianti spezzeranno la directory creata da mkdir 'directory name with spaces'in quattro parole separate.
Charles Duffy,

4
Ho dovuto aggiungere -mindepth 1 -maxdepth 1o è andato troppo in profondità.
ScrappyDev,

284

Una versione che evita la creazione di un processo secondario:

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

Oppure, se la tua azione è un singolo comando, questo è più conciso:

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

O una versione ancora più concisa ( grazie @enzotib ). Si noti che in questa versione ogni valore di Davrà una barra finale:

for D in */; do my_command; done

49
Puoi evitare il ifo [con:for D in */; do
enzotib,

2
+1 perché i nomi delle directory non iniziano con ./ rispetto alla risposta accettata
Hayri Uğur Koltuk

3
Questo è corretto anche negli spazi nei nomi delle directory +1
Alex Reinking

5
C'è un problema con l'ultimo comando: se ti trovi in ​​una directory senza sottodirectory; restituirà "* /". Quindi meglio usare il secondo comando for D in *; do [ -d "${D}" ] && my_command; doneo una combinazione dei due più recenti:for D in */; do [ -d $D ] && my_command; done
Chris Maes,

5
Nota che questa risposta ignora le directory nascoste. Per includere for D in .* *; doinvece directory nascoste usare invece for D in *; do.
patryk.beza,

108

Il modo più semplice non ricorsivo è:

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

Alla /fine dice, usa solo le directory.

Non ce n'è bisogno

  • trova
  • awk
  • ...

11
Nota: questo non includerà i punti dir (che può essere una buona cosa, ma è importante saperlo).
Wisbucky,

Utile notare che è possibile utilizzare shopt -s dotglobper includere dotfile / dotdir quando si espandono i caratteri jolly. Vedi anche: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt

Credo che si intende /*invece che */con /che rappresenta il percorso che si desidera utilizzare.
Brōtsyorfuzthrāx

2
@Shule /*sarebbe per percorso assoluto mentre */includerebbe le sottodirectory dalla posizione corrente
Dan G

3
suggerimento utile: se hai bisogno di tagliare il '/' finale da $ d, usa${d%/*}
Hafthor

18

Usa il findcomando.

In GNU find, puoi usare il -execdirparametro:

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

o usando il -execparametro:

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

o con xargscomando:

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

O usando per loop:

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

Per la ricorsività prova **/invece il globbing esteso ( ) (abilita da :) shopt -s extglob.


Per altri esempi, vedi: Come accedere a ciascuna directory ed eseguire un comando? a SO


1
-exec {} +è specificato da POSIX, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +è un'altra opzione legale e invoca meno shell di -exec sh -c '...' {} \;.
Charles Duffy,

13

Pratiche linee singole

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

find * -type d ### Option B

L'opzione A è corretta per le cartelle con spazi in mezzo. Inoltre, generalmente più veloce poiché non stampa ogni parola nel nome di una cartella come entità separata.

# 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

10

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


posso inserire il valore di ritorno di quel ritrovamento in un ciclo for? Questo fa parte di una sceneggiatura più ampia ...
mikewilliamson,

@ Mike, improbabile. $? probabilmente otterrai lo stato del comando find o xargs, anziché my_command.
Paul Tomblin,

7

Questo creerà una subshell (il che significa che i valori delle variabili andranno persi alla whilechiusura del ciclo):

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

Questo non:

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

O uno funzionerà se ci sono spazi nei nomi delle directory.


Per una gestione ancora migliore di nomi di file strani (compresi i nomi che terminano con spazi bianchi e / o includono avanzamenti di riga), utilizzare find ... -print0ewhile IFS="" read -r -d $'\000' dir
Gordon Davisson,

@GordonDavisson, ... anzi, direi persino che -d ''è meno fuorviante riguardo alla sintassi e alle capacità di bash, poiché -d $'\000'implica (falsamente) che $'\000'è in qualche modo diverso da ''- anzi, si potrebbe facilmente (e ancora, falsamente) dedurre da che bash supporta stringhe di tipo Pascal (specificate in lunghezza, in grado di contenere valori letterali NUL) anziché stringhe in C (delimitate da NUL, incapaci di contenere NUL).
Charles Duffy,

5

Puoi provare:

#!/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

o simili...

Spiegazione:

find . -maxdepth 1 -mindepth 1 -type d: Trova solo le directory con una profondità ricorsiva massima di 1 (solo le sottodirectory di $ 1) e una profondità minima di 1 (esclude la cartella corrente .)


Questo è difettoso: prova con un nome di directory con spazi. Vedi BashPitfalls # 1 e DontReadLinesWithFor .
Charles Duffy,

Il nome della directory con spazi è racchiuso tra virgolette e quindi funziona e OP non sta cercando di leggere le righe dal file.
Henry Dobson,

funziona nel cd "$f". Non funziona quando l'output da findè suddiviso in stringhe, quindi avrai i pezzi separati del nome come valori separati in $f, rendendo quanto bene fai o non citi $fil moot di espansione.
Charles Duffy,

Non ho detto che stavano cercando di leggere le righe da un file. findL'output è orientato alla linea (un nome per una linea, in teoria - ma vedi sotto) con l' -printazione predefinita .
Charles Duffy,

L'output orientato alla linea, poiché da find -printnon è un modo sicuro per passare nomi di file arbitrari, dal momento che si può eseguire qualcosa mkdir -p $'foo\n/etc/passwd\nbar'e ottenere una directory che ha /etc/passwduna linea separata nel suo nome. Gestire nomi da file in /uploado /tmpdirectory senza cura è un ottimo modo per ottenere attacchi di escalation di privilegi.
Charles Duffy,

4

la risposta accettata si spezzerà su spazi bianchi se i nomi delle directory li hanno, e la sintassi preferita è $()per bash / ksh. Utilizzare l' GNU find -exec opzione con +;es

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

o usa un ciclo while

find .... |  while read -r D
do
  ...
done 

quale parametro conterrà il nome della directory? ad esempio, chmod + x $ DIR_NAME (sì, lo so che esiste un'opzione chmod solo per le directory)
Mike Graf

C'è una sottile differenza tra find -exece passare a xargs: findignorerà il valore di uscita del comando in esecuzione, mentre xargsfallirà su un'uscita diversa da zero. Entrambi potrebbero essere corretti, a seconda delle tue esigenze.
Jason Wodicka,

find ... -print0 | while IFS= read -r dè più sicuro: supporta nomi che iniziano o finiscono in spazi bianchi e nomi che contengono letterali newline.
Charles Duffy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.