Jika Anda harus menyimpan output dan Anda menginginkan sebuah array, mapfile
membuatnya mudah.
Pertama, pertimbangkan jika Anda perlu menyimpan output perintah Anda sama sekali. Jika tidak, jalankan saja perintahnya.
Jika Anda memutuskan ingin membaca output dari suatu perintah sebagai array dari baris, memang benar bahwa salah satu cara untuk melakukannya adalah dengan menonaktifkan globbing, mengatur IFS
untuk membagi pada baris, menggunakan substitusi perintah di dalam (
)
sintaks pembuatan array, dan mengatur ulang IFS
setelah itu, yang lebih kompleks dari yang terlihat. Jawaban terdon mencakup beberapa pendekatan itu. Tapi saya sarankan Anda menggunakan mapfile
, perintah built-in Bash shell untuk membaca teks sebagai array baris. Ini kasus sederhana di mana Anda membaca dari sebuah file:
mapfile < filename
Ini membaca baris ke dalam array yang disebut MAPFILE
. Tanpa pengalihan input , Anda akan membaca dari input standar shell (biasanya terminal Anda) alih-alih file yang dinamai oleh . Untuk membaca ke dalam array selain dari default , berikan namanya. Sebagai contoh, ini berbunyi ke dalam array :< filename
filename
MAPFILE
lines
mapfile lines < filename
Perilaku default lain yang mungkin Anda pilih untuk diubah adalah bahwa karakter baris baru di ujung baris dibiarkan di tempatnya; mereka muncul sebagai karakter terakhir di setiap elemen array (kecuali input berakhir tanpa karakter baris baru, dalam hal ini elemen terakhir tidak memilikinya). Untuk mengejar baris baru ini sehingga tidak muncul dalam array, berikan -t
opsi mapfile
. Misalnya, ini membaca array records
dan tidak menulis karakter baris baru ke elemen arraynya:
mapfile -t records < filename
Anda juga dapat menggunakan -t
tanpa melewati nama array; yaitu, ia bekerja dengan nama array implisit MAPFILE
juga.
The mapfile
shell builtin mendukung pilihan lain, dan dapat alternatif dipanggil sebagai readarray
. Jalankan help mapfile
(dan help readarray
) untuk detail.
Tetapi Anda tidak ingin membaca dari file, Anda ingin membaca dari output dari perintah. Untuk mencapai itu, gunakan substitusi proses . Perintah ini membaca baris dari perintah some-command
dengan arguments...
sebagai argumen baris perintah dan menempatkannya dalam mapfile
larik default MAPFILE
, dengan karakter baris baru yang dihapus dihapus:
mapfile -t < <(some-command arguments...)
Substitusi proses menggantikan dengan nama file aktual dari mana output dari menjalankan dapat dibaca. File tersebut adalah pipa bernama daripada file biasa dan, di Ubuntu, itu akan dinamai seperti (kadang-kadang dengan beberapa nomor selain ), tetapi Anda tidak perlu khawatir dengan detailnya, karena shell menangani itu semua di belakang layar.<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Anda mungkin berpikir Anda bisa melupakan proses substitusi dengan menggunakan gantinya, tetapi itu tidak akan berhasil karena, ketika Anda memiliki pipeline dari beberapa perintah yang dipisahkan oleh , Bash menjalankan menjalankan semua perintah dalam subkulit . Dengan demikian keduanya dan berjalan di lingkungan mereka sendiri , diinisialisasi dari tetapi terpisah dari lingkungan shell di mana Anda menjalankan pipa. Dalam subkulit tempat berjalan, array tidak diisi, tetapi kemudian array tersebut dibuang saat perintah berakhir. tidak pernah dibuat atau dimodifikasi untuk pemanggil.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
Inilah yang contoh di jawaban Terdon ini terlihat seperti, secara keseluruhan, jika Anda menggunakan mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
Itu dia. Anda tidak perlu memeriksa apakah IFS
sudah disetel , melacak apakah belum disetel dan dengan nilai apa, setel ke baris baru, lalu setel ulang atau atur ulang pengaturan nanti. Anda tidak perlu menonaktifkan globbing (misalnya, dengan set -f
) - yang benar-benar diperlukan jika Anda menggunakan metode yang serius, karena nama file mungkin berisi *
, ?
dan [
--then mengaktifkan kembali itu ( set +f
) setelah itu.
Anda juga dapat mengganti loop tertentu sepenuhnya dengan satu printf
perintah - meskipun ini sebenarnya bukan manfaat mapfile
, karena Anda dapat melakukannya apakah Anda menggunakan mapfile
atau metode lain untuk mengisi array. Ini versi yang lebih pendek:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Penting untuk diingat bahwa, seperti yang disebutkan terdon , operasi baris demi baris tidak selalu sesuai, dan tidak akan berfungsi dengan benar dengan nama file yang mengandung baris baru. Saya merekomendasikan untuk tidak menamai file seperti itu, tetapi itu bisa terjadi, termasuk secara tidak sengaja.
Sebenarnya tidak ada solusi satu ukuran untuk semua.
Anda meminta "perintah umum untuk semua skenario," dan pendekatan menggunakan mapfile
pendekatan yang agak mendekati tujuan itu, tetapi saya mendorong Anda untuk mempertimbangkan kembali kebutuhan Anda.
Tugas yang ditunjukkan di atas lebih baik dicapai hanya dengan satu find
perintah:
find . -type d -printf 'DIR: %p\n'
Anda juga bisa menggunakan perintah eksternal ingin sed
menambahkan DIR:
ke awal setiap baris. Ini bisa dibilang agak jelek, dan tidak seperti perintah find itu akan menambah "awalan" tambahan dalam nama file yang mengandung baris baru, tetapi itu bekerja terlepas dari inputnya sehingga memenuhi persyaratan Anda dan masih lebih baik untuk membaca output menjadi sebuah variabel atau array :
find . -type d | sed 's/^/DIR: /'
Jika Anda perlu mendaftar dan juga melakukan tindakan pada setiap direktori yang ditemukan, seperti menjalankan some-command
dan melewati jalur direktori sebagai argumen, find
memungkinkan Anda melakukannya juga:
find . -type d -print -exec some-command {} \;
Sebagai contoh lain, mari kembali ke tugas umum menambahkan awalan untuk setiap baris. Misalkan saya ingin melihat output help mapfile
tetapi nomor baris. Saya sebenarnya tidak akan menggunakan mapfile
ini, atau metode lain yang membacanya menjadi variabel shell atau shell array. Seandainya help mapfile | cat -n
tidak memberikan format yang saya inginkan, saya dapat menggunakan awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Membaca semua output dari suatu perintah ke variabel tunggal atau array kadang-kadang berguna dan sesuai, tetapi memiliki kelemahan utama. Anda tidak hanya harus berurusan dengan kompleksitas tambahan menggunakan shell Anda ketika perintah yang ada atau kombinasi dari perintah yang sudah ada mungkin sudah melakukan apa yang Anda butuhkan juga atau lebih baik, tetapi seluruh output dari perintah harus disimpan dalam memori. Terkadang Anda mungkin tahu itu bukan masalah, tetapi kadang-kadang Anda mungkin memproses file besar.
Alternatif yang biasanya dicoba - dan kadang dilakukan dengan benar - adalah membaca input baris demi baris dengan read -r
dalam satu lingkaran. Jika Anda tidak perlu menyimpan saluran sebelumnya saat beroperasi pada saluran selanjutnya, dan Anda harus menggunakan input panjang, maka itu mungkin lebih baik daripada mapfile
. Tapi itu juga, harus dihindari dalam kasus-kasus ketika Anda bisa mengirimkannya ke perintah yang dapat melakukan pekerjaan, yang kebanyakan kasus .