Saat mengulang file, ada dua cara:
gunakan
for
-loop:for f in *; do echo "$f" done
gunakan
find
:find * -prune | while read f; do echo "$f" done
Dengan asumsi dua loop ini akan menemukan daftar file yang sama, apa perbedaan dalam dua opsi dalam kinerja dan penanganan?
bash
shell-script
performance
rubo77
sumber
sumber
find
tidak membuka file yang ditemukannya. Satu-satunya hal yang saya bisa lihat menggigit Anda di sini sehubungan dengan sejumlah besar file adalah ARG_MAX .read f
akan memecah-mecah nama file ketika membacanya (mis. Nama dengan blanko terkemuka). Jugafind * -prune
tampaknya menjadi cara yang sangat berbelit-belit untuk mengatakan hanyals -1
ya?find .
, bukanfind *
.ls -l
adalah ide yang buruk. Tapi parsingls -1
(itu1
bukan anl
) tidak lebih buruk daripada parsingfind * -prune
. Keduanya gagal pada file dengan baris baru di namanya.Jawaban:
1.
Yang pertama:
gagal untuk file yang dipanggil
-n
,-e
dan varian menyukai-nene
dan dengan beberapa penyebaran bash, dengan nama file yang mengandung backslash.Kedua:
gagal untuk bahkan lebih kasus (file disebut
!
,-H
,-name
,(
, nama file yang dimulai atau berakhir dengan kosong atau berisi karakter baris baru ...)Shell yang mengembang
*
,find
tidak melakukan apa-apa selain mencetak file yang diterimanya sebagai argumen. Anda juga bisa menggunakanprintf '%s\n'
yang sepertiprintf
yang dibangun juga akan menghindari potensi kesalahan terlalu banyak args .2.
Perluasan
*
diurutkan, Anda dapat membuatnya sedikit lebih cepat jika Anda tidak perlu menyortir. Dizsh
:atau hanya:
bash
tidak memiliki setara sejauh yang saya tahu, jadi Anda harus menggunakanfind
.3.
(di atas menggunakan
-print0
ekstensi non-standar GNU / BSD ).Itu masih melibatkan pemunculan perintah find dan menggunakan
while read
loop lambat , jadi itu mungkin akan lebih lambat daripada menggunakanfor
loop kecuali daftar file sangat besar.4.
Selain itu, bertentangan dengan ekspansi wildcard,
find
akan melakukanlstat
panggilan sistem pada setiap file, jadi tidak mungkin bahwa non-sorting akan mengimbangi itu.Dengan GNU / BSD
find
, itu bisa dihindari dengan menggunakan-maxdepth
ekstensi mereka yang akan memicu optimasi menyimpanlstat
:Karena
find
mulai mengeluarkan nama file segera setelah ditemukan (kecuali untuk buffer keluaran stdio), yang mungkin lebih cepat adalah jika apa yang Anda lakukan dalam loop memakan waktu dan daftar nama file lebih dari buffer stdio (4 / 8 kB). Dalam hal ini, pemrosesan dalam loop akan dimulai sebelumfind
selesai menemukan semua file. Pada sistem GNU dan FreeBSD, Anda dapat menggunakannyastdbuf
untuk menyebabkan hal itu terjadi lebih cepat (menonaktifkan buffering stdio).5.
Cara POSIX / standar / portabel untuk menjalankan perintah untuk setiap file
find
adalah dengan menggunakan-exec
predikat:Dalam hal ini
echo
, itu kurang efisien daripada melakukan perulangan di shell karena shell akan memiliki versi builtinecho
sementarafind
perlu memunculkan proses baru dan mengeksekusi/bin/echo
di dalamnya untuk setiap file.Jika Anda perlu menjalankan beberapa perintah, Anda dapat melakukan:
Namun waspadalah yang
cmd2
hanya dijalankan jikacmd1
berhasil.6.
Cara kanonik untuk menjalankan perintah kompleks untuk setiap file adalah dengan memanggil shell dengan
-exec ... {} +
:Saat itu, kami kembali menjadi efisien
echo
karena kami menggunakan versi bawaansh
dan-exec +
versi memunculkan sesedikitsh
mungkin.7.
Dalam pengujian saya pada direktori dengan 200.000 file dengan nama pendek di ext4, yang
zsh
(paragraf 2.) sejauh ini tercepat, diikuti olehfor i in *
loop sederhana pertama (meskipun seperti biasa,bash
jauh lebih lambat daripada kerang lain untuk itu).sumber
!
dilakukan dalam perintah find?!
untuk negasi.! -name . -prune more...
akan melakukan-prune
(danmore...
karena-prune
selalu mengembalikan true) untuk setiap file tetapi.
. Jadi itu akan dilakukanmore...
pada semua file di.
, tetapi akan mengecualikan.
dan tidak akan turun ke subdirektori dari.
. Jadi itu setara dengan standar GNU-mindepth 1 -maxdepth 1
.Saya mencoba ini pada direktori dengan 2259 entri, dan menggunakan
time
perintah.Output dari
time for f in *; do echo "$f"; done
(minus file!) Adalah:Output dari
time find * -prune | while read f; do echo "$f"; done
(minus file!) Adalah:Saya menjalankan setiap perintah beberapa kali, sehingga dapat menghilangkan kesalahan cache. Ini menyarankan untuk menyimpannya di
bash
(untuk i di ...) lebih cepat daripada menggunakanfind
dan menyalurkan output (kebash
)Hanya untuk kelengkapan, saya menjatuhkan pipa dari
find
, karena dalam contoh Anda, itu sepenuhnya berlebihan. Output dari adilfind * -prune
adalah:Juga,
time echo *
(keluaran tidak dipisahkan baris, sayangnya):Pada titik ini, saya menduga alasannya
echo *
lebih cepat karena tidak menghasilkan begitu banyak baris baru, sehingga hasilnya tidak terlalu banyak. Ayo coba ...hasil:
sementara
time find * -prune > /dev/null
hasil:dan
time for f in *; do echo "$f"; done > /dev/null
hasil:dan akhirnya:
time echo * > /dev/null
hasil:Beberapa variasi dapat diperhitungkan oleh faktor acak, tetapi tampaknya jelas:
for f in *; do ...
lebih lambat daripadafind * -prune
sendiri, tetapi untuk konstruksi di atas yang melibatkan pipa, lebih cepat.Selain itu, di samping itu, kedua pendekatan tersebut tampaknya menangani nama dengan spasi.
EDIT:
Pengaturan waktu untuk
find . -maxdepth 1 > /dev/null
vsfind * -prune > /dev/null
.:time find . -maxdepth 1 > /dev/null
:find * -prune > /dev/null
:Jadi, kesimpulan tambahan:
find * -prune
lebih lambat darifind . -maxdepth 1
- di yang pertama, shell sedang memproses bola dunia, kemudian membangun baris perintah (besar) untukfind
. NB:find . -prune
hanya mengembalikan.
.Tes lebih lanjut
time find . -maxdepth 1 -exec echo {} \; >/dev/null
::Kesimpulan:
sumber
find * -prune | while read f; do echo "$f"; done
memiliki pipa redundan - semua pipa lakukan adalah mengeluarkanfind
output apa sendiri. Tanpa pipa, itu akan menjadi sederhanafind * -prune
. Pipa hanya redundan khusus karena hal di sisi lain pipa hanya menyalin stdin ke stdout (untuk sebagian besar). Ini adalah no-op yang mahal. Jika Anda ingin melakukan hal-hal dengan output dari find, selain hanya meludahkannya kembali, itu berbeda.*
. Seperti yang dikatakan BitsOfNix : Saya masih sangat menyarankan untuk tidak menggunakan*
dan.
sebagaifind
gantinya.find . -prune
lebih cepat karenafind
akan membaca entri direktori kata demi kata, sementara shell akan melakukan hal yang sama, berpotensi cocok dengan glob (mungkin mengoptimalkan untuk*
), kemudian membangun baris perintah besar untukfind
.find . -prune
hanya mencetak.
pada sistem saya. Ini hampir tidak berhasil sama sekali. Sama sekali tidak sama denganfind * -prune
yang menunjukkan semua nama di direktori saat ini. Telanjangread f
akan memotong nama file dengan spasi terdepan.Saya pasti akan pergi dengan find walaupun saya akan mengubah temuan Anda menjadi hanya ini:
Dari segi kinerja,
find
jauh lebih cepat tergantung kebutuhan Anda tentunya. Apa yang Anda miliki saatfor
ini hanya akan menampilkan file / direktori di direktori saat ini tetapi tidak isi direktori. Jika Anda menggunakan find, itu juga akan menampilkan isi dari sub-direktori.Saya katakan menemukan lebih baik karena dengan Anda
for
yang*
akan harus diperluas pertama dan aku takut bahwa jika Anda memiliki sebuah direktori dengan sejumlah besar file mungkin memberikan kesalahan daftar argumen terlalu lama . Sama berlaku untukfind *
Sebagai contoh, di salah satu sistem yang saya gunakan saat ini ada beberapa direktori dengan lebih dari 2 juta file (masing-masing <100k):
sumber
-prune
untuk membuat dua contoh lebih mirip. dan saya lebih suka pipa dengan sementara sehingga lebih mudah untuk menerapkan lebih banyak perintah dalam loopadalah penggunaan yang tidak berguna
find
- Apa yang Anda katakan efektif "untuk setiap file di direktori (*
), tidak menemukan file. Juga, itu tidak aman karena beberapa alasan:-r
opsi untukread
. Ini bukan masalah denganfor
loop.for
loop.Penanganan setiap nama file dengan
find
yang sulit , sehingga Anda harus menggunakanfor
opsi lingkaran bila memungkinkan karena alasan itu saja. Juga, menjalankan program eksternal sepertifind
pada umumnya akan lebih lambat daripada menjalankan perintah loop internal sepertifor
.sumber
find
'-print0
maupunxargs
'-0
tidak kompatibel dengan POSIX, dan Anda tidak dapat memasukkan perintah sembarangsh -c ' ... '
(tanda kutip tunggal tidak dapat diloloskan dalam tanda kutip tunggal), sehingga tidak terlalu sederhana.Tapi kami payah untuk pertanyaan kinerja! Permintaan untuk eksperimen ini membuat setidaknya dua asumsi yang membuatnya tidak terlalu valid.
A. Asumsikan mereka menemukan file yang sama ...
Yah, mereka akan menemukan file yang sama pada awalnya, karena mereka berdua mengulangi glob yang sama, yaitu
*
. Tetapifind * -prune | while read f
menderita beberapa kelemahan yang membuatnya sangat mungkin tidak akan menemukan semua file yang Anda harapkan:find
implementasi memang, tetapi tetap saja, Anda tidak harus bergantung pada itu.find *
dapat pecah ketika Anda menekanARG_MAX
.for f in *
tidak akan, karenaARG_MAX
berlaku untukexec
, bukan bawaan.while read f
dapat putus dengan nama file mulai dan berakhir dengan spasi putih, yang akan dilucuti. Anda bisa mengatasi ini denganwhile read
dan parameter default-nyaREPLY
, tetapi itu masih tidak akan membantu Anda ketika datang ke nama file dengan baris baru di dalamnya.B.
echo
. Tidak ada yang akan melakukan ini hanya untuk menggemakan nama file. Jika Anda menginginkannya, lakukan saja salah satu dari ini:Pipa ke
while
loop di sini membuat subshell implisit yang menutup ketika loop berakhir, yang mungkin tidak intuitif untuk beberapa orang.Untuk menjawab pertanyaan, berikut adalah hasil dalam direktori saya yang memiliki 184 file dan direktori di dalamnya.
sumber
$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
find *
tidak akan berfungsi dengan benar jika*
menghasilkan token yang terlihat seperti predikat daripada jalur.Anda tidak dapat menggunakan
--
argumen yang biasa untuk memperbaikinya karena--
menunjukkan akhir dari opsi, dan menemukan opsi datang sebelum jalan.Untuk memperbaiki masalah ini, Anda dapat menggunakannya
find ./*
. Tapi kemudian itu tidak menghasilkan string yang sama persis denganfor x in *
.Perhatikan bahwa
find ./* -prune | while read f ..
sebenarnya tidak menggunakan fungsionalitas pemindaianfind
. Ini adalah sintaks globbing./*
yang sebenarnya melintasi direktori dan menghasilkan nama. Makafind
program harus melakukan setidaknyastat
pemeriksaan pada masing-masing nama tersebut. Anda memiliki overhead untuk meluncurkan program dan memilikinya mengakses file-file ini, dan kemudian melakukan I / O untuk membaca outputnya.Sulit membayangkan bagaimana itu bisa menjadi apa pun selain kurang efisien
for x in ./* ...
.sumber
Yah sebagai permulaan
for
adalah kata kunci shell, dibangun ke Bash, sementarafind
merupakan executable terpisah.The
for
Loop hanya akan menemukan file dari karakter globstar ketika mengembang, hal itu tidak akan recurse ke setiap direktori yang ditemukan.Temukan di sisi lain juga akan diberikan daftar yang diperluas oleh globstar, tetapi akan secara rekursif menemukan semua file dan direktori di bawah daftar yang diperluas ini dan menyalurkan masing-masing ke
while
loop.Kedua pendekatan ini mungkin dianggap berbahaya dalam arti bahwa mereka tidak menangani jalur atau nama file yang mengandung spasi.
Hanya itu yang bisa saya pikirkan untuk mengomentari kedua pendekatan ini.
sumber
Jika semua file yang dikembalikan oleh find dapat diproses dengan satu perintah (jelas tidak berlaku untuk contoh gema Anda di atas), Anda dapat menggunakan xargs:
sumber
Selama bertahun-tahun saya telah menggunakan ini: -
untuk mencari file-file tertentu (misalnya * .txt) yang berisi pola yang dapat dicari grep dan disalurkan menjadi lebih banyak sehingga tidak menggulung layar. Terkadang saya menggunakan pipa >> untuk menulis hasilnya ke file lain yang bisa saya lihat nanti.
Berikut contoh hasilnya: -
sumber