x=$(find . -name "*.txt")
echo $x
jika saya menjalankan potongan kode di atas dalam shell Bash, yang saya dapatkan adalah string yang berisi beberapa nama file yang dipisahkan oleh kosong, bukan daftar.
Tentu saja, saya dapat memisahkan mereka dengan blank untuk mendapatkan daftar, tetapi saya yakin ada cara yang lebih baik untuk melakukannya.
Jadi apa cara terbaik untuk mengulang hasil find
perintah?
x=( $(find . -name "*.txt") ); echo "${x[@]}"
Kemudian Anda bisa mengulangfor item in "${x[@]}"; { echo "$item"; }
Jawaban:
TL; DR: Jika Anda hanya di sini untuk jawaban yang paling benar, Anda mungkin menginginkan preferensi pribadi saya,
find . -name '*.txt' -exec process {} \;
(lihat bagian bawah posting ini). Jika Anda punya waktu, baca sisanya untuk melihat beberapa cara berbeda dan masalah dengan sebagian besar dari mereka.Jawaban lengkapnya:
Cara terbaik tergantung pada apa yang ingin Anda lakukan, tetapi di sini ada beberapa pilihan. Selama tidak ada file atau folder di subtree yang memiliki spasi putih dalam namanya, Anda bisa memutarnya:
Secara marginal lebih baik, hilangkan variabel sementara
x
:Jauh lebih baik untuk glob ketika Anda bisa. Brankas ruang putih, untuk file di direktori saat ini:
Dengan mengaktifkan
globstar
opsi ini, Anda dapat menggumpalkan semua file yang cocok di direktori ini dan semua subdirektori:Dalam beberapa kasus, misalnya jika nama file sudah ada dalam file, Anda mungkin perlu menggunakan
read
:read
dapat digunakan dengan aman dalam kombinasi denganfind
dengan mengatur pembatas secara tepat:Untuk pencarian yang lebih kompleks, Anda mungkin ingin menggunakan
find
, baik dengan-exec
opsi atau dengan-print0 | xargs -0
:find
juga dapat melakukan cd ke direktori masing-masing file sebelum menjalankan perintah dengan menggunakan-execdir
alih-alih-exec
, dan dapat dibuat interaktif (meminta sebelum menjalankan perintah untuk setiap file) menggunakan-ok
alih-alih-exec
(atau-okdir
bukannya-execdir
).*: Secara teknis, keduanya
find
danxargs
(secara default) akan menjalankan perintah dengan argumen sebanyak yang sesuai pada baris perintah, sebanyak yang diperlukan untuk melewati semua file. Dalam praktiknya, kecuali jika Anda memiliki jumlah file yang sangat besar itu tidak masalah, dan jika Anda melebihi panjangnya tetapi membutuhkan semuanya pada baris perintah yang sama,Anda SOLmenemukan cara yang berbeda.sumber
done < filename
dan mengikuti satu dengan pipa stdin tidak dapat digunakan lagi (→ tidak ada hal yang lebih interaktif dalam loop), tetapi dalam kasus di mana itu dibutuhkan satu dapat menggunakan3<
bukannya<
dan menambahkan<&3
atau-u3
untuk ituread
bagian, pada dasarnya menggunakan deskriptor file terpisah. Juga, saya percayaread -d ''
sama denganread -d $'\0'
tetapi saya tidak dapat menemukan dokumentasi resmi tentang itu sekarang.-exec process {} \;
dan dugaan saya adalah itu pertanyaan lain - apa artinya dan bagaimana saya memanipulasinya? Di mana T / A atau doc yang baik? di atasnya?man find
). Dalam hal ini,-exec
suruhfind
untuk mengeksekusi perintah berikut, diakhiri oleh;
(atau+
), di mana{}
akan diganti dengan nama file yang sedang diproses (atau, jika+
digunakan, semua file yang telah membuatnya ke kondisi itu).-d ''
lebih baik daripada-d $'\0'
. Yang terakhir tidak hanya lebih lama tetapi juga menunjukkan bahwa Anda bisa melewati argumen yang mengandung byte nol, tetapi Anda tidak bisa. Byte nol pertama menandai akhir dari string. Dalam bash$'a\0bc'
sama dengana
dan$'\0'
sama dengan$'\0abc'
atau hanya string kosong''
.help read
menyatakan bahwa " Karakter pertama''
pembatas digunakan untuk mengakhiri input " jadi menggunakan sebagai pembatas adalah sedikit peretasan. Karakter pertama dalam string kosong adalah byte nol yang selalu menandai akhir string (bahkan jika Anda tidak menuliskannya secara eksplisit).Apa pun yang Anda lakukan, jangan gunakan
for
loop :Tiga alasan:
find
harus dijalankan untuk menyelesaikan.for
loop Anda mengembalikan 40KB teks. 8KB yang terakhir akan dijatuhkan langsung darifor
loop Anda dan Anda tidak akan pernah mengetahuinya.Selalu gunakan
while read
konstruk:Loop akan dieksekusi saat
find
perintah dieksekusi. Plus, perintah ini akan berfungsi bahkan jika nama file dikembalikan dengan spasi putih di dalamnya. Dan, Anda tidak akan membanjiri buffer baris perintah Anda.The
-print0
akan menggunakan NULL sebagai pemisah file bukan baris baru dan-d $'\0'
akan menggunakan NULL sebagai pemisah saat membaca.sumber
-exec
saja.-exec
adalah yang paling aman karena tidak menggunakan shell sama sekali. Namun, NL dalam nama file cukup langka. Spasi dalam nama file cukup umum. Poin utamanya adalah jangan menggunakanfor
loop yang direkomendasikan banyak poster.for file $(find)
karena masalah yang terkait dengan itu.-r
opsi untukread
:-r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Catatan: metode ini dan metode (kedua) yang ditunjukkan oleh bmargulies aman digunakan dengan spasi putih dalam nama file / folder.
Agar dapat memiliki - baris yang agak eksotis - baris baru dalam nama file / folder yang dicakup, Anda harus menggunakan
-exec
predikatfind
seperti ini:The
{}
adalah tempat untuk item berhasil ditemukan dan\;
digunakan untuk mengakhiri-exec
predikat.Dan demi kelengkapan, izinkan saya menambahkan varian lain - Anda harus menyukai cara * nix karena keserbagunaannya:
Ini akan memisahkan item yang dicetak dengan
\0
karakter yang tidak diperbolehkan dalam sistem file dalam nama file atau folder, sepengetahuan saya, dan karena itu harus mencakup semua pangkalan.xargs
mengambilnya satu per satu lalu ...sumber
find -print0
danxargs -0
keduanya merupakan ekstensi GNU dan bukan argumen portabel (POSIX). Sangat berguna pada sistem yang memilikinya!read -r
akan memperbaiki), atau nama file berakhir dengan spasi putih (yangIFS= read
akan memperbaiki). Karenanya BashFAQ # 1 menyarankanwhile IFS= read -r filename; do ...
exit
tidak akan berfungsi seperti yang diharapkan dan variabel yang ditetapkan dalam tubuh loop tidak akan tersedia setelah loop.Nama file dapat menyertakan spasi dan bahkan mengontrol karakter. Spasi adalah pembatas (default) untuk ekspansi shell di bash dan karenanya dari
x=$(find . -name "*.txt")
pertanyaan itu tidak disarankan sama sekali. Jika find mendapatkan nama file dengan spasi misalnya"the file.txt"
Anda akan mendapatkan 2 string terpisah untuk diproses, jika Anda memprosesx
dalam satu lingkaran. Anda dapat meningkatkan ini dengan mengubah pembatas (bashIFS
Variable) misalnya ke\r\n
, tetapi nama file dapat menyertakan karakter kontrol - jadi ini bukan metode (sepenuhnya) aman.Dari sudut pandang saya, ada 2 pola yang direkomendasikan (dan aman) untuk memproses file:
1. Gunakan untuk ekspansi loop & nama file:
2. Gunakan substitusi find-read-while & proses
Catatan
pada Pola 1:
nullglob
dapat digunakan untuk menghindari garis tambahan ini.failglob
opsi shell diatur, dan tidak ada kecocokan yang ditemukan, pesan kesalahan dicetak dan perintah tidak dijalankan." (dari Bash Manual di atas)globstar
: "Jika diatur, pola '**' yang digunakan dalam konteks ekspansi nama file akan cocok dengan semua file dan nol atau lebih direktori dan subdirektori. Jika pola diikuti oleh '/', hanya direktori dan subdirektori yang cocok." lihat Bash Manual, Shopt Builtinextglob
,nocaseglob
,dotglob
& variabel shellGLOBIGNORE
pada Pola 2:
nama file dapat berisi kosong, tab, spasi, baris baru, ... untuk memproses nama file dengan cara yang aman,
find
dengan-print0
digunakan: nama file dicetak dengan semua karakter kontrol & diakhiri dengan NUL. lihat juga Gnu Findutils Manpage, Penanganan Nama File Tidak Aman , Penanganan Nama File aman , karakter yang tidak biasa dalam nama file . Lihat David A. Wheeler di bawah ini untuk diskusi terperinci tentang topik ini.Ada beberapa pola yang mungkin untuk memproses hasil pencarian dalam loop sementara. Lainnya (kevin, David W.) telah menunjukkan cara melakukan ini menggunakan pipa:
Ketika Anda mencoba potongan kode ini, Anda akan melihat, bahwa itu tidak berfungsi:files_found
selalu "benar" & kode akan selalu bergema "tidak ada file yang ditemukan". Alasannya adalah: setiap perintah pipeline dieksekusi dalam subshell terpisah, sehingga variabel yang diubah di dalam loop (subshell terpisah) tidak mengubah variabel dalam skrip shell utama. Inilah sebabnya saya merekomendasikan menggunakan proses substitusi sebagai pola "lebih baik", lebih bermanfaat, lebih umum.Lihat Saya mengatur variabel dalam satu lingkaran yang ada dalam pipa. Mengapa mereka menghilang ... (dari FAQ Bash Greg) untuk diskusi terperinci tentang topik ini.
Referensi & Sumber Tambahan:
Gnu Bash Manual, Pencocokan Pola
Nama file dan Nama Path di Shell: Cara melakukannya dengan benar, David A. Wheeler
Mengapa Anda tidak membaca baris dengan "untuk", Greg's Wiki
Mengapa Anda tidak harus menguraikan output ls (1), Greg's Wiki
Gnu Bash Manual, Substitusi Proses
sumber
(Diperbarui untuk menyertakan peningkatan kecepatan eksekutif Socowi)
Dengan apa pun
$SHELL
yang mendukungnya (tanda hubung / zsh / bash ...):Selesai
Jawaban asli (lebih pendek, tetapi lebih lambat):
sumber
\;
Anda dapat menggunakan+
untuk mengirimkan file sebanyak mungkin ke satuexec
. Kemudian gunakan"$@"
di dalam skrip shell untuk memproses semua parameter ini.$@
menghilangkannya karena biasanya nama skrip. Kami hanya perlu menambahkandummy
di antara'
dan{}
sehingga dapat menggantikan nama skrip, memastikan semua kecocokan diproses oleh loop.OTHERVAR=foo find . -na.....
seharusnya memungkinkan Anda untuk mengakses$OTHERVAR
dari dalam shell yang baru dibuat.sumber
for x in $(find ...)
akan merusak setiap nama file dengan spasi putih di dalamnya. Sama denganfind ... | xargs
kecuali jika Anda menggunakan-print0
dan-0
find . -name "*.txt -exec process_one {} ";"
sebagai gantinya. Mengapa kita harus menggunakan xargs untuk mengumpulkan hasil, yang sudah kita miliki?process_one
itu. Jika itu adalah pengganti untuk perintah aktual , tentu itu akan berhasil (jika Anda memperbaiki kesalahan ketik dan menambahkan kutipan penutup setelah"*.txt
). Tetapi jikaprocess_one
fungsi yang ditentukan pengguna, kode Anda tidak akan berfungsi.Anda dapat menyimpan
find
output dalam array jika Anda ingin menggunakan output nanti sebagai:Sekarang untuk mencetak setiap elemen dalam baris baru, Anda dapat menggunakan
for
perulangan iterating ke semua elemen array, atau Anda dapat menggunakan pernyataan printf.atau
Anda juga bisa menggunakan:
Ini akan mencetak setiap nama file di baris baru
Untuk hanya mencetak
find
output dalam bentuk daftar, Anda dapat menggunakan salah satu dari berikut ini:atau
Ini akan menghapus pesan kesalahan dan hanya memberikan nama file sebagai output di baris baru.
Jika Anda ingin melakukan sesuatu dengan nama file, menyimpannya dalam array itu bagus, kalau tidak maka tidak perlu mengkonsumsi ruang itu dan Anda dapat langsung mencetak hasilnya
find
.sumber
Jika Anda dapat menganggap nama file tidak mengandung baris baru, Anda dapat membaca output
find
menjadi array Bash menggunakan perintah berikut:catatan:
-t
menyebabkanreadarray
strip baru.readarray
ada di dalam pipa, maka proses substitusi.readarray
tersedia sejak Bash 4.Bash 4.4 dan yang lebih tinggi juga mendukung
-d
parameter untuk menentukan pembatas. Menggunakan karakter nol, alih-alih baris baru, untuk membatasi nama file berfungsi juga dalam kasus yang jarang terjadi bahwa nama file berisi baris baru:readarray
dapat juga dipanggilmapfile
dengan opsi yang sama.Referensi: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
sumber
exit
ketika mengulangi hasilreadarray -d '' x < <(find . -name '*.txt' -print0)
Saya suka menggunakan find yang pertama kali ditugaskan ke variabel dan IFS beralih ke baris baru sebagai berikut:
Hanya dalam kasus Anda ingin mengulangi lebih banyak tindakan pada set DATA yang sama dan menemukan sangat lambat pada server Anda (I / 0 utilisasi tinggi)
sumber
Anda bisa memasukkan nama file yang dikembalikan oleh
find
ke dalam array seperti ini:Sekarang Anda bisa mengulang melalui array untuk mengakses item individual dan melakukan apa pun yang Anda inginkan dengannya.
Catatan: Ini adalah ruang putih yang aman.
sumber
mapfile -t -d '' array < <(find ...)
. PengaturanIFS
tidak diperlukan untukmapfile
.berdasarkan jawaban dan komentar @phk lainnya, menggunakan fd # 3:
(yang masih memungkinkan untuk menggunakan stdin di dalam loop)
sumber
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
Ini akan mencantumkan file dan memberikan detail tentang atribut.
sumber
Bagaimana jika Anda menggunakan grep daripada menemukan?
Sekarang Anda dapat membaca file ini dan nama file dalam bentuk daftar.
sumber