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?

mikewilliamson
sumber
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
Mike Clark
sumber
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
kanaka
sumber
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
  • ...
d0x
sumber
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

kenorb
sumber
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
Sriram Murali
sumber
10

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

Paul Tomblin
sumber
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.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
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 .)

Henry Dobson
sumber
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 
ghostdog74
sumber
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