Apa yang meluas ke semua file di direktori saat ini secara rekursif?
92
Aku tahu **/*.extmengembang ke semua file dalam semua subdirektori yang cocok *.ext, tapi apa adalah ekspansi yang sama yang mencakup semua file tersebut di saat direktori juga?
Pesta saya tidak tertangani **/*.ext. Apakah Anda yakin itu berhasil untuk Anda?
tangens
@tangens Anda harus mengaktifkan globstaropsi sesuai jawaban Dennis.
kenorb
Jawaban:
111
Ini akan bekerja di Bash 4:
ls -l {,**/}*.ext
Agar globe tanda bintang ganda bekerja, globstaropsi perlu disetel (default: aktif):
shopt -s globstar
Dari man bash:
globstar
Jika disetel, pola ** yang digunakan dalam konteks perluasan nama file
teks akan cocok dengan file dan nol atau lebih direktori dan
subdirektori. Jika polanya hanya diikuti oleh a /, saja
direktori dan subdirektori cocok.
Sekarang saya bertanya-tanya apakah mungkin pernah ada bug dalam pemrosesan globstar, karena sekarang dengan hanya menggunakan ls **/*.extsaya mendapatkan hasil yang benar.
Terlepas dari itu, saya melihat analisis yang dilakukan kenorb menggunakan repositori VLC dan menemukan beberapa masalah dengan analisis itu dan dalam jawaban saya tepat di atas:
Perbandingan dengan keluaran findperintah tidak valid karena menentukan -type ftidak menyertakan jenis file lain (khususnya direktori) dan lsperintah yang terdaftar kemungkinan besar melakukannya. Juga, salah satu perintah yang terdaftar, ls -1 {,**/}*.*- yang tampaknya didasarkan pada milik saya di atas, hanya mengeluarkan nama yang menyertakan titik untuk file-file yang ada di subdirektori. Pertanyaan OP dan jawaban saya menyertakan titik karena yang dicari adalah file dengan ekstensi tertentu.
Yang paling penting, bagaimanapun, adalah bahwa ada masalah khusus dalam menggunakan lsperintah dengan pola globstar **. Banyak duplikat muncul karena pola tersebut diperluas oleh Bash ke semua nama file (dan nama direktori) di pohon yang sedang diperiksa. Setelah perluasan, lsperintah mencantumkan masing - masing dan kontennya jika berupa direktori.
Contoh:
Di direktori kami saat ini adalah subdirektori Adan isinya:
A
└── AB
└── ABC
├── ABC1
├── ABC2
└── ABCD
└── ABCD1
Di pohon itu, **diperluas menjadi "AA / AB A / AB / ABC A / AB / ABC / ABC1 A / AB / ABC / ABC2 A / AB / ABC / ABCD A / AB / ABC / ABCD / ABCD1" (7 entri) . Jika Anda melakukan echo **itu, hasil yang tepat akan Anda dapatkan dan setiap entri diwakili satu kali. Namun , jika Anda melakukannya, ls **itu akan menghasilkan daftar dari masing - masing entri tersebut. Jadi pada dasarnya itu ls Adiikuti oleh ls A/AB, dll., Jadi A/ABakan ditampilkan dua kali. Juga, lsakan memisahkan keluaran setiap subdirektori:
Jadi menggunakan wc -lmenghitung semua baris kosong dan judul bagian nama direktori yang membuang hitungan lebih jauh.
Ini adalah alasan lain mengapa Anda tidak perlu mengurails .
Sebagai hasil dari analisis lebih lanjut ini, saya menyarankan untuk tidak menggunakan pola globstar dalam keadaan apa pun selain melakukan iterasi pada pohon file dengan cara ini:
for entry in **
do
something "$entry"done
Sebagai perbandingan terakhir, saya menggunakan repositori sumber Bash yang saya miliki dan melakukan ini:
Saya biasa trmengubah spasi ke baris baru yang hanya berlaku di sini karena tidak ada nama yang menyertakan spasi. Saya biasa sedmenghapus garis depan ./dari setiap baris keluaran find. Saya mengurutkan output findkarena biasanya tidak disortir dan perluasan gumpalan Bash sudah diurutkan. Seperti yang Anda lihat, satu-satunya keluaran dari diffadalah .keluaran direktori saat ini oleh find. Ketika saya melakukannya ls ** | wc -l, hasilnya hampir dua kali lebih banyak baris.
Saya menguji Ubuntu dan Cygwin, dan globstardefaultoff
Steven Penny
12
Jawaban terbaik! tapi saya pikir **/*.extseharusnya sudah cukup. Selain itu, Anda tidak akan memiliki file tersembunyi kecuali Anda shopt -s dotglob.
gniourf_gniourf
2
Untuk menonaktifkan globstar: shopt -u globstar.
kenorb
4
@gniourf_gniourf Pertanyaan sebenarnya meminta untuk menyertakan direktori saat ini secara khusus jadi tidak, **/*.exttidak akan cukup
msciwoj
2
@dotnetCarpenter: Versi Bash yang disertakan dengan MacOS adalah 3.2 yang tidak mendukung globstar, seperti yang Anda ketahui. Tanda bintang ganda diperlakukan sama dengan tanda bintang tunggal. Globstar diperkenalkan di Bash 4.0.
Dijeda sampai pemberitahuan lebih lanjut.
13
Ini akan mencetak semua file di direktori saat ini dan subdirektorinya yang diakhiri dengan '.ext'.
Meskipun jawaban ini tidak memenuhi "perluasan" yang diminta OP dalam arti yang paling sempit, namun kemungkinan besar akan memberikan hasil yang diinginkan.
Dijeda sampai pemberitahuan lebih lanjut.
7
Anda dapat menggunakan: **/*.*untuk memasukkan semua file secara rekursif (diaktifkan oleh:) shopt -s globstar.
Silakan temukan di bawah pengujian variasi lain dan bagaimana perilakunya.
Menguji folder dengan 3472 file di folder repositori VLC sampel :
(Total file dari 3472 dihitung sebagai per: find . -type f | wc -l)
ls -1 **/*.* - mengembalikan 3338
ls -1 {,**/}*.*- mengembalikan 3341 (seperti yang diusulkan oleh Dennis )
ls -1 {,**/}* - mengembalikan 8265
ls -1 **/*- mengembalikan 7817, kecuali file tersembunyi (seperti yang diusulkan oleh Dennis )
ls -1 **/{.[^.],}*- mengembalikan 7869 (seperti yang diusulkan oleh Dennis )
ls -1 {,**/}.?* - mengembalikan 15855
ls -1 {,**/}.* - mengembalikan 20321
Jadi saya pikir metode paling dekat untuk mendaftar semua file secara rekursif adalah contoh pertama ( **/*.*) sesuai komentar gniourf-gniourf (dengan asumsi file memiliki ekstensi yang tepat, atau gunakan yang spesifik), karena contoh kedua memberikan beberapa duplikat lagi seperti di bawah ini :
dan yang lainnya menghasilkan duplikat lebih lanjut.
Untuk menyertakan file tersembunyi, gunakan: shopt -s dotglob(nonaktifkan oleh shopt -u dotglob). Ini tidak disarankan, karena dapat mempengaruhi perintah seperti mvatau rmdan Anda dapat menghapus file yang salah secara tidak sengaja.
Di terminal Mac dan bash dengan globstar diaktifkan, saya menemukan solusi di atas ( **/*.*) informatif dan bekerja paling baik. Jawaban yang diterima menyebabkan duplikat item di direktori teratas. Pola kerja saya adalah:"${path}"**/*.*
mummybot
Akan menarik untuk mencoba ini dengan opsi lain seperti nullglob dan dotglob
Wilf
4
$ find . -type f
Itu akan mencantumkan semua file di direktori saat ini. Anda kemudian dapat melakukan beberapa perintah lain pada keluaran menggunakan -exec
$find . -type f -exec grep "foo" {} \;
Itu akan menggrep setiap file dari pencarian untuk string "foo".
Sekarang 11 tahun kemudian, mungkin sudah saatnya seseorang menunjukkan bahwa find . -type fberlaku secara rekursif dengan root di direktori saat ini, tidak hanya ke direktori saat ini.
Roger Dahl
4
Mengapa tidak menggunakan perluasan brace untuk menyertakan direktori saat ini juga?
./{*,**/*}.ext
Ekspansi brace terjadi sebelum ekspansi glob, sehingga Anda dapat secara efektif melakukan apa yang Anda inginkan dengan versi bash yang lebih lama, dan dapat mengabaikan penggunaan globstar di versi yang lebih baru.
Selain itu, dianggap praktik yang baik dalam bash untuk memasukkan yang terdepan ./dalam pola glob Anda.
**/*.ext
. Apakah Anda yakin itu berhasil untuk Anda?globstar
opsi sesuai jawaban Dennis.Jawaban:
Ini akan bekerja di Bash 4:
Agar globe tanda bintang ganda bekerja,
globstar
opsi perlu disetel (default: aktif):shopt -s globstar
Dari
man bash
:Sekarang saya bertanya-tanya apakah mungkin pernah ada bug dalam pemrosesan globstar, karena sekarang dengan hanya menggunakan
ls **/*.ext
saya mendapatkan hasil yang benar.Terlepas dari itu, saya melihat analisis yang dilakukan kenorb menggunakan repositori VLC dan menemukan beberapa masalah dengan analisis itu dan dalam jawaban saya tepat di atas:
Perbandingan dengan keluaran
find
perintah tidak valid karena menentukan-type f
tidak menyertakan jenis file lain (khususnya direktori) danls
perintah yang terdaftar kemungkinan besar melakukannya. Juga, salah satu perintah yang terdaftar,ls -1 {,**/}*.*
- yang tampaknya didasarkan pada milik saya di atas, hanya mengeluarkan nama yang menyertakan titik untuk file-file yang ada di subdirektori. Pertanyaan OP dan jawaban saya menyertakan titik karena yang dicari adalah file dengan ekstensi tertentu.Yang paling penting, bagaimanapun, adalah bahwa ada masalah khusus dalam menggunakan
ls
perintah dengan pola globstar**
. Banyak duplikat muncul karena pola tersebut diperluas oleh Bash ke semua nama file (dan nama direktori) di pohon yang sedang diperiksa. Setelah perluasan,ls
perintah mencantumkan masing - masing dan kontennya jika berupa direktori.Contoh:
Di direktori kami saat ini adalah subdirektori
A
dan isinya:Di pohon itu,
**
diperluas menjadi "AA / AB A / AB / ABC A / AB / ABC / ABC1 A / AB / ABC / ABC2 A / AB / ABC / ABCD A / AB / ABC / ABCD / ABCD1" (7 entri) . Jika Anda melakukanecho **
itu, hasil yang tepat akan Anda dapatkan dan setiap entri diwakili satu kali. Namun , jika Anda melakukannya,ls **
itu akan menghasilkan daftar dari masing - masing entri tersebut. Jadi pada dasarnya ituls A
diikuti olehls A/AB
, dll., JadiA/AB
akan ditampilkan dua kali. Juga,ls
akan memisahkan keluaran setiap subdirektori:Jadi menggunakan
wc -l
menghitung semua baris kosong dan judul bagian nama direktori yang membuang hitungan lebih jauh.Ini adalah alasan lain mengapa Anda tidak perlu mengurai
ls
.Sebagai hasil dari analisis lebih lanjut ini, saya menyarankan untuk tidak menggunakan pola globstar dalam keadaan apa pun selain melakukan iterasi pada pohon file dengan cara ini:
for entry in ** do something "$entry" done
Sebagai perbandingan terakhir, saya menggunakan repositori sumber Bash yang saya miliki dan melakukan ini:
shopt -s globstar dotglob diff <(echo ** | tr ' ' '\n') <(find . | sed 's|\./||' | sort) 0a1 > .
Saya biasa
tr
mengubah spasi ke baris baru yang hanya berlaku di sini karena tidak ada nama yang menyertakan spasi. Saya biasased
menghapus garis depan./
dari setiap baris keluaranfind
. Saya mengurutkan outputfind
karena biasanya tidak disortir dan perluasan gumpalan Bash sudah diurutkan. Seperti yang Anda lihat, satu-satunya keluaran daridiff
adalah.
keluaran direktori saat ini olehfind
. Ketika saya melakukannyals ** | wc -l
, hasilnya hampir dua kali lebih banyak baris.sumber
globstar
defaultoff
**/*.ext
seharusnya sudah cukup. Selain itu, Anda tidak akan memiliki file tersembunyi kecuali Andashopt -s dotglob
.globstar
:shopt -u globstar
.**/*.ext
tidak akan cukupIni akan mencetak semua file di direktori saat ini dan subdirektorinya yang diakhiri dengan '.ext'.
find . -name '*.ext' -print
sumber
Anda dapat menggunakan:
**/*.*
untuk memasukkan semua file secara rekursif (diaktifkan oleh:)shopt -s globstar
.Silakan temukan di bawah pengujian variasi lain dan bagaimana perilakunya.
Menguji folder dengan 3472 file di folder repositori VLC sampel :
(Total file dari 3472 dihitung sebagai per:
find . -type f | wc -l
)ls -1 **/*.*
- mengembalikan 3338ls -1 {,**/}*.*
- mengembalikan 3341 (seperti yang diusulkan oleh Dennis )ls -1 {,**/}*
- mengembalikan 8265ls -1 **/*
- mengembalikan 7817, kecuali file tersembunyi (seperti yang diusulkan oleh Dennis )ls -1 **/{.[^.],}*
- mengembalikan 7869 (seperti yang diusulkan oleh Dennis )ls -1 {,**/}.?*
- mengembalikan 15855ls -1 {,**/}.*
- mengembalikan 20321Jadi saya pikir metode paling dekat untuk mendaftar semua file secara rekursif adalah contoh pertama (
**/*.*
) sesuai komentar gniourf-gniourf (dengan asumsi file memiliki ekstensi yang tepat, atau gunakan yang spesifik), karena contoh kedua memberikan beberapa duplikat lagi seperti di bawah ini :dan yang lainnya menghasilkan duplikat lebih lanjut.
Untuk menyertakan file tersembunyi, gunakan:
shopt -s dotglob
(nonaktifkan olehshopt -u dotglob
). Ini tidak disarankan, karena dapat mempengaruhi perintah sepertimv
ataurm
dan Anda dapat menghapus file yang salah secara tidak sengaja.sumber
**/*.*
) informatif dan bekerja paling baik. Jawaban yang diterima menyebabkan duplikat item di direktori teratas. Pola kerja saya adalah:"${path}"**/*.*
$ find . -type f
Itu akan mencantumkan semua file di direktori saat ini. Anda kemudian dapat melakukan beberapa perintah lain pada keluaran menggunakan -exec
$find . -type f -exec grep "foo" {} \;
Itu akan menggrep setiap file dari pencarian untuk string "foo".
sumber
find . -type f
berlaku secara rekursif dengan root di direktori saat ini, tidak hanya ke direktori saat ini.Mengapa tidak menggunakan perluasan brace untuk menyertakan direktori saat ini juga?
Ekspansi brace terjadi sebelum ekspansi glob, sehingga Anda dapat secara efektif melakukan apa yang Anda inginkan dengan versi bash yang lebih lama, dan dapat mengabaikan penggunaan globstar di versi yang lebih baru.
Selain itu, dianggap praktik yang baik dalam bash untuk memasukkan yang terdepan
./
dalam pola glob Anda.sumber