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.