Jawaban yang lain akan pecah jika output dari perintah mengandung spasi (yang agak sering) atau gumpal karakter seperti *, ?, [...].
Untuk mendapatkan output dari sebuah perintah dalam sebuah array, dengan satu baris per elemen, pada dasarnya ada 3 cara:
Dengan penggunaan Bash≥4 mapfile— yang paling efisien:
mapfile -t my_array < <( my_command )
Jika tidak, loop membaca output (lebih lambat, tapi aman):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Seperti yang disarankan oleh Charles Duffy di komentar (terima kasih!), Berikut ini mungkin bekerja lebih baik daripada metode loop di nomor 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Harap pastikan Anda menggunakan formulir ini persis, yaitu, pastikan Anda memiliki yang berikut:
IFS=$'\n' pada baris yang sama dengan readpernyataan: ini hanya akan menyetel variabel lingkungan IFS untuk readpernyataan saja. Jadi itu tidak akan memengaruhi seluruh skrip Anda sama sekali. Tujuan dari variabel ini adalah untuk memberitahu readuntuk menghentikan aliran pada karakter EOL \n.
-r: ini penting. Ini memberitahu read untuk tidak menafsirkan garis miring terbalik sebagai urutan pelarian.
-d '': harap perhatikan jarak antara -dopsi dan argumennya ''. Jika Anda tidak meninggalkan spasi di sini, maka ''tidak akan pernah terlihat, karena akan hilang pada langkah penghapusan kutipan saat Bash mengurai pernyataan tersebut. Ini memberitahu readuntuk berhenti membaca pada byte nol. Beberapa orang menulisnya sebagai -d $'\0', tetapi sebenarnya tidak terlalu perlu. -d ''lebih baik.
-a my_arraymemberitahu readuntuk mengisi larik my_arraysaat membaca aliran.
- Anda harus menggunakan
printf '\0'pernyataan setelah my_command , sehingga readmengembalikan 0; sebenarnya bukan masalah besar jika Anda tidak melakukannya (Anda hanya akan mendapatkan kode pengembalian 1, yang tidak masalah jika Anda tidak menggunakannya set -e- yang seharusnya tidak Anda gunakan ), tetapi ingatlah itu. Ini lebih bersih dan lebih tepat secara semantik. Perhatikan bahwa ini berbeda dari printf '', yang tidak menghasilkan apa pun. printf '\0'mencetak byte nol, diperlukan oleh readuntuk dengan senang hati berhenti membaca di sana (ingat -d ''opsinya?).
Jika Anda bisa, misalnya, jika Anda yakin kode Anda akan berjalan di Bash≥4, gunakan metode pertama. Dan Anda juga bisa melihatnya lebih pendek.
Jika Anda ingin menggunakan read, loop (metode 2) mungkin memiliki keunggulan dibandingkan metode 3 jika Anda ingin melakukan beberapa pemrosesan saat baris dibaca: Anda memiliki akses langsung ke sana (melalui $linevariabel dalam contoh yang saya berikan), dan Anda juga memiliki akses ke baris yang sudah dibaca (melalui array ${my_array[@]}dalam contoh yang saya berikan).
Perhatikan bahwa mapfilemenyediakan cara agar callback mengevaluasi pada setiap baris yang dibaca, dan pada kenyataannya Anda bahkan dapat memberitahukannya untuk hanya memanggil callback ini setiap baris N yang dibaca; lihat help mapfiledan opsi -Cdan di -cdalamnya. (Pendapat saya tentang ini adalah bahwa ini sedikit kikuk, tetapi terkadang dapat digunakan jika Anda hanya memiliki hal-hal sederhana yang harus dilakukan - Saya tidak begitu mengerti mengapa ini bahkan diterapkan di tempat pertama!).
Sekarang saya akan memberi tahu Anda mengapa metode berikut:
my_array=( $( my_command) )
rusak jika ada spasi:
$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!
Kemudian beberapa orang kemudian akan merekomendasikan penggunaan IFS=$'\n'untuk memperbaikinya:
$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!
Tapi sekarang mari kita gunakan perintah lain, dengan globs :
$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?
Itu karena saya memiliki file bernama tdi direktori saat ini ... dan nama file ini cocok dengan glob [three four] ... pada titik ini beberapa orang akan merekomendasikan penggunaan set -funtuk menonaktifkan globbing: tetapi lihatlah: Anda harus mengubah IFSdan menggunakan set -funtuk dapat memperbaiki sebuah teknik rusak (dan Anda bahkan tidak benar-benar memperbaikinya)! ketika melakukan itu kita benar-benar melawan shell, bukan bekerja dengan shell .
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
di sini kami bekerja dengan cangkangnya!