Lakukan tindakan di setiap sub-direktori menggunakan Bash


194

Saya sedang mengerjakan skrip yang perlu melakukan tindakan di setiap sub-direktori folder tertentu.

Apa cara paling efisien untuk menulis itu?


1
Silakan pertimbangkan untuk kembali dan mengevaluasi kembali jawaban untuk kebenaran - Anda mendapat jawaban yang diterima mendapatkan banyak pandangan meskipun ada bug besar (f / e, menjalankannya di direktori di mana seseorang sebelumnya berlari mkdir 'foo * bar'akan menyebabkan foodan baruntuk diulang bahkan jika mereka tidak ada, dan *akan diganti dengan daftar semua nama file, bahkan yang bukan direktori).
Charles Duffy

1
... yang lebih parah adalah jika seseorang berlari mkdir -p '/tmp/ /etc/passwd /'- jika seseorang menjalankan skrip yang mengikuti praktik ini /tmpke, katakanlah, temukan direktori untuk dihapus, mereka bisa berakhir dengan menghapus /etc/passwd.
Charles Duffy

Jawaban:


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

81
ini akan pecah di ruang putih
ghostdog74

2
Juga, perhatikan bahwa Anda perlu mengubah findparams jika Anda ingin perilaku rekursif atau non-rekursif.
Chris Tonkinson

32
Jawaban di atas memberi saya direktori mandiri juga, jadi yang berikut ini bekerja sedikit lebih baik untuk saya: find . -mindepth 1 -type d
jzheaux

1
@JoshC, kedua varian akan memecah direktori yang dibuat mkdir 'directory name with spaces'menjadi empat kata terpisah.
Charles Duffy

4
Saya perlu menambahkan -mindepth 1 -maxdepth 1atau terlalu dalam.
ScrappyDev

282

Versi yang menghindari pembuatan sub-proses:

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

Atau, jika tindakan Anda adalah satu perintah, ini lebih ringkas:

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

Atau versi yang lebih ringkas ( terima kasih @enzotib ). Perhatikan bahwa dalam versi ini setiap nilai Dakan memiliki garis miring:

for D in */; do my_command; done

48
Anda dapat menghindari ifatau [dengan:for D in */; do
enzotib

2
+1 karena nama direktori tidak dimulai dengan ./ sebagai lawan dari jawaban yang diterima
Hayri Uğur Koltuk

3
Yang ini benar bahkan hingga spasi dalam nama direktori +1
Alex Reinking

5
Ada satu masalah dengan perintah terakhir: jika Anda berada di direktori tanpa subdirektori; itu akan mengembalikan "* /". Jadi lebih baik gunakan perintah kedua for D in *; do [ -d "${D}" ] && my_command; doneatau kombinasi dari dua yang terbaru:for D in */; do [ -d $D ] && my_command; done
Chris Maes

5
Perhatikan bahwa jawaban ini mengabaikan direktori tersembunyi. Untuk memasukkan direktori tersembunyi gunakan for D in .* *; dosaja for D in *; do.
patryk.beza

105

Cara non rekursif yang paling sederhana adalah:

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

Di /bagian akhir memberitahu, gunakan direktori saja.

Tidak perlu

  • Temukan
  • awk
  • ...

11
Catatan: ini tidak akan termasuk dot dirs (yang bisa menjadi hal yang baik, tetapi penting untuk diketahui).
wisbucky

Berguna untuk dicatat bahwa Anda dapat menggunakan shopt -s dotglobuntuk memasukkan dotfiles / dotdirs ketika memperluas wildcard. Lihat juga: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt

Saya pikir maksud Anda /*alih-alih */dengan /mewakili jalur yang ingin Anda gunakan.
Brōtsyorfuzthrāx

2
@Shule /*akan menjadi jalur absolut sedangkan */akan mencakup subdirektori dari lokasi saat ini
Dan G

3
petunjuk bermanfaat: jika Anda perlu memangkas trailing '/' dari $ d, gunakan${d%/*}
Hafthor

18

Gunakan findperintah.

Di GNU find, Anda dapat menggunakan -execdirparameter:

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

atau dengan menggunakan -execparameter:

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

atau dengan xargsperintah:

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

Atau gunakan untuk loop:

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

Untuk rekursivitas coba globbing diperpanjang ( **/) sebagai gantinya (diaktifkan oleh:) shopt -s extglob.


Untuk lebih banyak contoh, lihat: Cara pergi ke setiap direktori dan menjalankan perintah? di SO


1
-exec {} +ditentukan POSIX, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +adalah opsi hukum lain, dan meminta lebih sedikit shell -exec sh -c '...' {} \;.
Charles Duffy

13

Satu garis yang praktis

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

find * -type d ### Option B

Opsi A benar untuk folder dengan spasi di antaranya. Juga, umumnya lebih cepat karena tidak mencetak setiap kata dalam nama folder sebagai entitas yang terpisah.

# 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


dapatkah saya memasukkan nilai pengembalian penemuan itu ke dalam for loop? Ini adalah bagian dari naskah yang lebih besar ...
mikewilliamson

@ Mike, tidak mungkin. $? mungkin akan memberi Anda status perintah find atau xargs, daripada my_command.
Paul Tomblin

7

Ini akan membuat subkulit (yang berarti bahwa nilai variabel akan hilang ketika whileloop keluar):

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

Ini tidak akan:

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

Salah satu akan bekerja jika ada spasi dalam nama direktori.


Untuk penanganan nama file aneh yang lebih baik (termasuk nama yang diakhiri dengan spasi putih dan / atau termasuk linefeeds), gunakan find ... -print0danwhile IFS="" read -r -d $'\000' dir
Gordon Davisson

@GordonDavisson, ... memang, saya bahkan berpendapat bahwa -d ''kurang menyesatkan tentang sintaks dan kemampuan bash, karena -d $'\000'menyiratkan (salah) yang $'\000'dalam beberapa cara berbeda dari ''- memang, seseorang dapat dengan mudah (dan sekali lagi, palsu) disimpulkan dari itu bash mendukung string gaya Pascal (panjang ditentukan, mampu mengandung NUL literal) daripada string C (NUL dibatasi, tidak dapat mengandung NUL).
Charles Duffy

5

Kamu bisa mencoba:

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

atau serupa ...

Penjelasan:

find . -maxdepth 1 -mindepth 1 -type d: Hanya temukan direktori dengan kedalaman rekursif maksimum 1 (hanya subdirektori $ 1) dan kedalaman minimum 1 (tidak termasuk folder saat ini .)


Ini buggy - coba dengan nama direktori dengan spasi. Lihat BashPitfalls # 1 , dan DontReadLinesWithFor .
Charles Duffy

Nama direktori dengan spasi terlampir dalam tanda kutip dan karena itu berfungsi dan OP tidak mencoba membaca baris dari file.
Henry Dobson

ini bekerja di cd "$f". Ini tidak berfungsi ketika output dari findstring-split, jadi Anda akan memiliki potongan-potongan nama yang terpisah sebagai nilai-nilai terpisah $f, membuat seberapa baik Anda melakukan atau tidak mengutip $fmoot ekspansi.
Charles Duffy

Saya tidak mengatakan mereka sedang mencoba untuk membaca baris dari sebuah file. findOutputnya berorientasi garis (satu nama ke baris, dalam teori - tetapi lihat di bawah) dengan -printaksi default .
Charles Duffy

Keluaran berorientasi garis, karena dari find -printitu bukan cara yang aman untuk melewati nama file yang sewenang-wenang, karena seseorang dapat menjalankan sesuatu mkdir -p $'foo\n/etc/passwd\nbar'dan mendapatkan direktori yang memiliki /etc/passwdbaris terpisah dalam namanya. Menangani nama dari file dalam /uploadatau /tmpdirektori tanpa perawatan adalah cara yang bagus untuk mendapatkan serangan eskalasi hak istimewa.
Charles Duffy

4

jawaban yang diterima akan pecah pada spasi putih jika nama direktori memilikinya, dan sintaks yang disukai adalah $()untuk bash / ksh. Gunakan GNU find -exec opsi dengan +;misalnya

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

atau gunakan loop sementara

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

param apa yang akan menyimpan nama direktori? misalnya, chmod + x $ DIR_NAME (ya, saya tahu ada opsi chmod untuk direktori saja)
Mike Graf

Ada satu perbedaan halus antara find -execdan meneruskan ke xargs: findakan mengabaikan nilai keluar dari perintah yang dieksekusi, sementara xargsakan gagal pada jalan keluar yang bukan nol. Mungkin benar, tergantung pada kebutuhan Anda.
Jason Wodicka

find ... -print0 | while IFS= read -r dlebih aman - mendukung nama yang dimulai atau berakhir di spasi putih, dan nama yang berisi literal baris baru.
Charles Duffy
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.