Dalam skrip Bash saya ingin membagi satu baris menjadi beberapa bagian dan menyimpannya dalam sebuah array.
Garis:
Paris, France, Europe
Saya ingin memilikinya dalam array seperti ini:
array[0] = Paris
array[1] = France
array[2] = Europe
Saya ingin menggunakan kode sederhana, kecepatan perintah tidak masalah. Bagaimana saya bisa melakukannya?
,
(koma-ruang) dan bukan karakter tunggal seperti koma. Jika Anda hanya tertarik pada yang terakhir, jawaban di sini lebih mudah diikuti: stackoverflow.com/questions/918886/…cut
adalah perintah bash yang berguna untuk diingat juga. Pemisah dapat didefinisikan en.wikibooks.org/wiki/Cut Anda juga dapat mengekstrak data dari struktur catatan lebar tetap juga. en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmJawaban:
Perhatikan bahwa karakter dalam
$IFS
diperlakukan secara individual sebagai pemisah sehingga dalam hal ini bidang dapat dipisahkan dengan baik koma atau spasi daripada urutan dua karakter. Menariknya, bidang kosong tidak dibuat ketika koma-ruang muncul di input karena ruang diperlakukan secara khusus.Untuk mengakses elemen individual:
Untuk beralih ke elemen:
Untuk mendapatkan indeks dan nilainya:
Contoh terakhir berguna karena array Bash jarang. Dengan kata lain, Anda dapat menghapus elemen atau menambahkan elemen dan indeksnya tidak bersebelahan.
Untuk mendapatkan jumlah elemen dalam array:
Seperti disebutkan di atas, array bisa jadi jarang sehingga Anda tidak harus menggunakan panjang untuk mendapatkan elemen terakhir. Begini caranya di Bash 4.2 dan yang lebih baru:
di semua versi Bash (dari suatu tempat setelah 2.05b):
Offset negatif yang lebih besar pilih lebih jauh dari ujung array. Catat spasi sebelum tanda minus dalam formulir yang lebih lama. Itu wajib.
sumber
IFS=', '
, maka Anda tidak perlu menghapus spasi secara terpisah. Tes:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
Ngomong-ngomong, saya suka menggunakan untuk hasil tes.France, Europe, "Congo, The Democratic Republic of the"
ini akan terpecah setelah congo.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
akan dibagi menjadiarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
catatan. Jadi ini hanya berfungsi dengan bidang tanpa spasi karenaIFS=', '
merupakan kumpulan karakter individu - bukan pembatas string.Semua jawaban untuk pertanyaan ini salah dalam satu atau lain cara.
Jawaban salah # 1
1: Ini adalah penyalahgunaan
$IFS
. Nilai dari$IFS
variabel tidak diambil sebagai variabel-panjang tunggal separator string, melainkan diambil sebagai set dari karakter tunggal pemisah tali, di mana masing-masing bidang yangread
perpecahan off dari garis masukan dapat dihentikan oleh setiap karakter dalam set (koma atau spasi, dalam contoh ini).Sebenarnya, untuk stickler nyata di luar sana, makna penuh
$IFS
sedikit lebih terlibat. Dari manual bash :Pada dasarnya, untuk nilai non-null non-default
$IFS
, bidang dapat dipisahkan dengan (1) urutan satu atau lebih karakter yang semuanya dari set "karakter spasi spasi IFS" (yaitu, yang mana dari <spasi> , <tab> , dan <newline> ("baris baru" umpan garis makna (LF) ) hadir di mana saja di$IFS
), atau (2) non- "karakter spasi IFS" yang hadir$IFS
bersama dengan "karakter spasi IFS" apa pun yang mengelilinginya pada baris input.Untuk OP, ada kemungkinan bahwa mode pemisahan kedua yang saya jelaskan di paragraf sebelumnya adalah persis apa yang dia inginkan untuk string inputnya, tetapi kita dapat cukup yakin bahwa mode pemisahan pertama yang saya jelaskan tidak benar sama sekali. Misalnya, bagaimana jika string inputnya
'Los Angeles, United States, North America'
?2: Bahkan jika Anda menggunakan solusi ini dengan pemisah satu karakter (seperti koma dengan sendirinya, yaitu, tanpa ruang berikut atau bagasi lain), jika nilai
$string
variabel kebetulan mengandung LF, makaread
akan berhenti memproses setelah bertemu LF pertama. Theread
builtin hanya memproses satu baris per doa. Ini benar bahkan jika Anda memipihkan atau mengarahkan input hanya keread
pernyataan, seperti yang kita lakukan dalam contoh ini dengan mekanisme di sini-string , dan dengan demikian input yang tidak diproses dijamin akan hilang. Kode yang mendukungread
builtin tidak memiliki pengetahuan tentang aliran data dalam struktur perintah yang mengandungnya.Anda bisa berpendapat bahwa ini tidak mungkin menyebabkan masalah, tetapi tetap saja, itu adalah bahaya halus yang harus dihindari jika mungkin. Hal ini disebabkan oleh fakta bahwa
read
builtin sebenarnya melakukan dua level pemisahan input: pertama menjadi garis, kemudian ke bidang. Karena OP hanya ingin satu tingkat pemisahan, penggunaanread
builtin ini tidak tepat, dan kita harus menghindarinya.3: Masalah potensial yang tidak jelas dengan solusi ini adalah bahwa
read
selalu menjatuhkan bidang trailing jika kosong, meskipun ia mempertahankan bidang kosong sebaliknya. Ini demo:Mungkin OP tidak akan peduli tentang ini, tapi masih ada batasan yang perlu diketahui. Ini mengurangi kekokohan dan generalisasi solusi.
Masalah ini dapat diatasi dengan menambahkan pembatas dummy trailing ke string input sesaat sebelum mengumpankannya
read
, seperti yang akan saya tunjukkan nanti.Jawaban salah # 2
Ide serupa:
(Catatan: Saya menambahkan tanda kurung yang hilang di sekitar substitusi perintah yang tampaknya dihilangkan oleh penjawab.)
Ide serupa:
Solusi ini memanfaatkan pemisahan kata dalam penugasan array untuk membagi string menjadi bidang. Lucunya, sama seperti
read
, pemisahan kata umum juga menggunakan$IFS
variabel khusus, meskipun dalam hal ini tersirat bahwa itu diatur ke nilai default dari <spasi><tab> <newline> , dan oleh karena itu setiap urutan satu atau lebih IFS karakter (yang semuanya merupakan karakter spasi sekarang) dianggap sebagai pembatas bidang.Ini memecahkan masalah dua tingkat pemisahan yang dilakukan oleh
read
, karena pemisahan kata dengan sendirinya merupakan satu tingkat pemisahan. Tapi seperti sebelumnya, masalahnya di sini adalah bahwa masing-masing bidang dalam string input sudah dapat berisi$IFS
karakter, dan dengan demikian mereka akan terpecah secara tidak benar selama operasi pemisahan kata. Ini tidak terjadi pada salah satu string input sampel yang disediakan oleh penjawab ini (betapa nyamannya ...), tetapi tentu saja itu tidak mengubah fakta bahwa basis kode apa pun yang menggunakan idiom ini kemudian akan berisiko. meledak jika asumsi ini pernah dilanggar di beberapa titik di telepon. Sekali lagi, pertimbangkan sampel tandingan saya dari'Los Angeles, United States, North America'
(atau'Los Angeles:United States:North America'
).Juga, kata membelah biasanya diikuti dengan ekspansi nama file ( alias ekspansi pathname alias globbing), yang, jika dilakukan, akan kata-kata yang berpotensi korup yang berisi karakter
*
,?
atau[
diikuti oleh]
(dan, jikaextglob
diatur, fragmen kurung didahului oleh?
,*
,+
,@
, atau!
) dengan mencocokkannya dengan objek sistem file dan memperluas kata-kata ("gumpalan") sesuai. Yang pertama dari tiga penjawab ini telah secara cerdik melemahkan masalah ini dengan menjalankanset -f
sebelumnya untuk menonaktifkan globbing. Secara teknis ini berfungsi (walaupun Anda mungkin harus menambahkanset +f
setelah itu untuk mengaktifkan kembali globbing untuk kode selanjutnya yang mungkin bergantung padanya), tetapi tidak diinginkan untuk mengacaukan pengaturan global shell untuk meretas operasi parsing string-to-array dasar dalam kode lokal.Masalah lain dengan jawaban ini adalah bahwa semua bidang kosong akan hilang. Ini mungkin atau mungkin tidak menjadi masalah, tergantung pada aplikasi.
Catatan: Jika Anda akan menggunakan solusi ini, lebih baik menggunakan
${string//:/ }
bentuk " parameter substitusi" dari ekspansi parameter , daripada pergi ke kesulitan menerapkan substitusi perintah (yang bercabang shell), memulai pipa, dan menjalankan executable eksternal (tr
ataused
), karena ekspansi parameter adalah murni operasi shell-internal. (Juga, untuktr
dansed
solusi, variabel input harus dikutip ganda di dalam substitusi perintah; jika tidak, pemisahan kata akan berpengaruh padaecho
perintah dan berpotensi mengacaukan nilai-nilai bidang. Juga,$(...)
bentuk substitusi perintah lebih disukai daripada yang lama).`...`
formulir karena menyederhanakan bersarangnya penggantian perintah dan memungkinkan penyorotan sintaksis yang lebih baik oleh editor teks.)Jawaban salah # 3
Jawaban ini hampir sama dengan # 2 . Perbedaannya adalah bahwa penjawab telah membuat asumsi bahwa bidang dibatasi oleh dua karakter, yang salah diwakili dalam default
$IFS
, dan yang lainnya tidak. Dia telah memecahkan kasus yang agak spesifik ini dengan menghapus karakter yang diwakili non-IFS menggunakan ekspansi substitusi pola dan kemudian menggunakan pemisahan kata untuk membagi bidang pada karakter pembatas yang diwakili IFS yang masih hidup.Ini bukan solusi yang sangat umum. Lebih lanjut, dapat diperdebatkan bahwa koma benar-benar karakter pembatas "primer" di sini, dan melepasnya lalu bergantung pada karakter spasi untuk pemisahan bidang adalah salah. Sekali lagi, pertimbangkan counterexample saya:
'Los Angeles, United States, North America'
.Juga, sekali lagi, ekspansi nama file dapat merusak kata-kata yang diperluas, tetapi ini dapat dicegah dengan menonaktifkan sementara penggumpalan untuk tugas dengan
set -f
dan kemudianset +f
.Juga, sekali lagi, semua bidang kosong akan hilang, yang mungkin atau mungkin tidak menjadi masalah tergantung pada aplikasi.
Jawaban salah # 4
Ini mirip dengan # 2 dan # 3 karena menggunakan pemisahan kata untuk menyelesaikan pekerjaan, hanya sekarang kode secara eksplisit mengatur
$IFS
untuk berisi hanya pembatas bidang karakter tunggal yang ada dalam string input. Harus diulangi bahwa ini tidak dapat berfungsi untuk pembatas bidang multicharacter seperti pembatas koma-ruang OP. Tetapi untuk pembatas satu karakter seperti LF yang digunakan dalam contoh ini, sebenarnya mendekati sempurna. Kolom tidak dapat dibagi secara tidak sengaja di tengah seperti yang kita lihat dengan jawaban yang salah sebelumnya, dan hanya ada satu tingkat pemisahan, seperti yang diperlukan.Satu masalah adalah bahwa ekspansi nama file akan merusak kata-kata yang terpengaruh seperti yang dijelaskan sebelumnya, meskipun sekali lagi ini dapat diselesaikan dengan membungkus pernyataan kritis di
set -f
danset +f
.Masalah potensial lainnya adalah bahwa, karena LF memenuhi syarat sebagai "karakter spasi IFS" sebagaimana didefinisikan sebelumnya, semua bidang kosong akan hilang, seperti pada # 2 dan # 3 . Ini tentu saja tidak akan menjadi masalah jika pembatas kebetulan bukan "ruang karakter spasi IFS", dan tergantung pada aplikasi itu mungkin tidak masalah, tapi itu merusak generalisasi dari solusi.
Jadi, singkatnya, dengan asumsi Anda memiliki pembatas satu karakter, dan itu adalah non-"karakter spasi putih IFS" atau Anda tidak peduli dengan bidang kosong, dan Anda membungkus pernyataan kritis
set -f
danset +f
, maka solusi ini berfungsi , tetapi sebaliknya tidak.(Juga, demi informasi, menugaskan LF ke variabel dalam bash dapat dilakukan dengan lebih mudah dengan
$'...'
sintaks, misalnyaIFS=$'\n';
.)Jawaban salah # 5
Ide serupa:
Solusi ini secara efektif merupakan persilangan antara # 1 (dalam hal ini ditetapkan
$IFS
ke koma-ruang) dan # 2-4 (dalam hal ini menggunakan pemisahan kata untuk membagi string menjadi bidang). Karena itu, ia menderita sebagian besar masalah yang menimpa semua jawaban yang salah di atas, semacam yang terburuk dari semua dunia.Juga, mengenai varian kedua, sepertinya
eval
panggilan itu sama sekali tidak perlu, karena argumennya adalah string literal yang dikutip tunggal, dan oleh karena itu diketahui secara statis. Tetapi sebenarnya ada manfaat yang sangat tidak jelas untuk digunakaneval
dengan cara ini. Biasanya, ketika Anda menjalankan perintah sederhana yang terdiri dari variabel tugas hanya , yang berarti tanpa kata perintah yang sebenarnya berikut, tugas tersebut berlaku dalam lingkungan shell:Ini benar bahkan jika perintah sederhana melibatkan banyak penugasan variabel; lagi, selama tidak ada kata perintah, semua tugas variabel mempengaruhi lingkungan shell:
Tapi, jika penugasan variabel dilampirkan ke nama perintah (saya suka menyebutnya "penugasan awalan") maka itu tidak mempengaruhi lingkungan shell, dan sebaliknya hanya mempengaruhi lingkungan dari perintah yang dieksekusi, terlepas apakah itu adalah builtin atau eksternal:
Kutipan yang relevan dari manual bash :
Dimungkinkan untuk mengeksploitasi fitur penugasan variabel ini untuk mengubah
$IFS
hanya sementara, yang memungkinkan kita untuk menghindari keseluruhan save-and-restore gambit seperti yang sedang dilakukan dengan$OIFS
variabel dalam varian pertama. Tetapi tantangan yang kita hadapi di sini adalah bahwa perintah yang perlu kita jalankan itu sendiri hanyalah tugas variabel, dan karenanya tidak akan melibatkan kata perintah untuk membuat$IFS
penugasan sementara. Anda mungkin berpikir sendiri, mengapa tidak menambahkan kata perintah no-op pada pernyataan seperti: builtin
membuat$IFS
tugas sementara? Ini tidak berfungsi karena itu akan membuat$array
tugas sementara juga:Jadi, kita secara efektif menemui jalan buntu, sedikit tangkapan-22. Tapi, ketika
eval
menjalankan kodenya, ia menjalankannya di lingkungan shell, seolah-olah itu normal, kode sumber statis, dan oleh karena itu kita dapat menjalankan$array
tugas di dalameval
argumen untuk membuatnya berlaku di lingkungan shell, sementara$IFS
tugas awalan yang diawali denganeval
perintah tidak akan hidup lebih lama darieval
perintah. Ini persis trik yang sedang digunakan dalam varian kedua dari solusi ini:Jadi, seperti yang Anda lihat, itu sebenarnya trik yang cukup pintar, dan menyelesaikan apa yang diperlukan (setidaknya berkenaan dengan efek penugasan) dengan cara yang agak tidak jelas. Saya sebenarnya tidak menentang trik ini secara umum, meskipun ada keterlibatan
eval
; hanya berhati-hatilah untuk mengutip argumen string untuk menjaga terhadap ancaman keamanan.Tetapi sekali lagi, karena aglomerasi masalah "terburuk dari semua dunia", ini masih merupakan jawaban yang salah terhadap persyaratan OP.
Jawaban salah # 6
Um ... apa? OP memiliki variabel string yang perlu diuraikan menjadi array. "Jawaban" ini dimulai dengan konten kata demi kata dari string input yang disisipkan ke dalam array literal. Saya kira itu salah satu cara untuk melakukannya.
Sepertinya penjawab mungkin berasumsi bahwa
$IFS
variabel mempengaruhi semua parsing bash dalam semua konteks, yang tidak benar. Dari manual bash:Jadi
$IFS
variabel khusus sebenarnya hanya digunakan dalam dua konteks: (1) pemisahan kata yang dilakukan setelah ekspansi (artinya tidak ketika mengurai kode sumber bash) dan (2) untuk memisahkan jalur input menjadi kata-kata olehread
builtin.Biarkan saya mencoba membuat ini lebih jelas. Saya pikir mungkin ada baiknya untuk membedakan antara parsing dan eksekusi . Bash pertama-tama harus mem - parsing kode sumber, yang jelas merupakan peristiwa parsing , dan kemudian mengeksekusi kode tersebut, yaitu ketika ekspansi muncul di dalam gambar. Ekspansi benar-benar acara eksekusi . Selanjutnya, saya mengambil masalah dengan deskripsi
$IFS
variabel yang baru saja saya kutip di atas; Daripada mengatakan bahwa pemisahan kata dilakukan setelah ekspansi , saya akan mengatakan bahwa pemisahan kata dilakukan selama ekspansi, atau, mungkin bahkan lebih tepatnya, pemisahan kata adalah bagian dariproses ekspansi. Frasa "pemisahan kata" hanya merujuk pada langkah ekspansi ini; itu tidak boleh digunakan untuk merujuk pada parsing dari kode sumber bash, meskipun sayangnya dokumen tampaknya melemparkan sekitar kata "split" dan "kata" banyak. Berikut kutipan yang relevan dari versi bash manual linux.die.net :Anda bisa berpendapat bahwa versi manual GNU sedikit lebih baik, karena ia memilih kata "token" daripada "kata" di kalimat pertama bagian Ekspansi:
Poin pentingnya adalah,
$IFS
jangan mengubah cara bash mem-parsing kode sumber. Parsing kode sumber bash sebenarnya adalah proses yang sangat kompleks yang melibatkan pengenalan berbagai elemen tata bahasa shell, seperti urutan perintah, daftar perintah, pipa, ekspansi parameter, penggantian aritmatika, dan penggantian perintah. Untuk sebagian besar, proses parsing bash tidak dapat diubah oleh tindakan tingkat pengguna seperti tugas variabel (sebenarnya, ada beberapa pengecualian kecil untuk aturan ini; misalnya, lihat berbagaicompatxx
pengaturan shell, yang dapat mengubah aspek tertentu dari perilaku parsing on-the-fly). "Kata" / "token" hulu yang dihasilkan dari proses penguraian kompleks ini kemudian diperluas sesuai dengan proses umum "ekspansi" sebagaimana dirinci dalam kutipan dokumentasi di atas, di mana pemisahan kata dari teks yang diperluas (yang diperluas?) Ke dalam hilir kata-kata hanyalah satu langkah dari proses itu. Pemisahan kata hanya menyentuh teks yang telah dimuntahkan dari langkah ekspansi sebelumnya; itu tidak mempengaruhi teks literal yang diuraikan langsung dari sumber bytestream.Jawaban salah # 7
Ini adalah salah satu solusi terbaik. Perhatikan bahwa kita kembali menggunakan
read
. Bukankah saya katakan sebelumnya bahwaread
itu tidak tepat karena melakukan dua tingkat pemisahan, ketika kita hanya membutuhkan satu? Kuncinya di sini adalah bahwa Anda dapat memanggilread
sedemikian rupa sehingga secara efektif hanya melakukan satu tingkat pemisahan, khususnya dengan memisahkan hanya satu bidang per doa, yang mengharuskan biaya harus memanggilnya berulang kali dalam satu lingkaran. Ini sedikit sulap, tapi berhasil.Tapi ada masalah. Pertama: Ketika Anda memberikan setidaknya satu argumen NAME untuk
read
, secara otomatis mengabaikan spasi spasi awal dan akhir di setiap bidang yang terpisah dari string input. Ini terjadi apakah$IFS
diatur ke nilai default atau tidak, seperti dijelaskan sebelumnya dalam posting ini. Sekarang, OP mungkin tidak peduli dengan kasus penggunaan spesifiknya, dan pada kenyataannya, ini mungkin fitur yang diinginkan dari perilaku parsing. Tetapi tidak semua orang yang ingin mengurai string ke bidang akan menginginkan ini. Namun ada solusinya: Penggunaan yang agak tidak jelasread
adalah untuk meloloskan nol argumen NAMA . Dalam hal ini,read
akan menyimpan seluruh jalur input yang didapat dari aliran input dalam variabel bernama$REPLY
, dan, sebagai bonus, itu tidakstrip whitespace terkemuka dan tertinggal dari nilai. Ini adalah penggunaan yang sangat kuatread
yang sering saya manfaatkan dalam karier pemrograman shell saya. Inilah demonstrasi perbedaan perilaku:Masalah kedua dengan solusi ini adalah tidak benar-benar mengatasi kasus pemisah bidang khusus, seperti koma-ruang OP. Seperti sebelumnya, pemisah multicharacter tidak didukung, yang merupakan batasan yang disayangkan dari solusi ini. Kami dapat mencoba setidaknya membagi pada koma dengan menentukan pemisah untuk
-d
opsi, tetapi lihat apa yang terjadi:Dapat diprediksi, spasi putih di sekitarnya yang tidak terhitung dapat ditarik ke dalam nilai-nilai lapangan, dan karenanya ini harus diperbaiki selanjutnya melalui operasi pemangkasan (ini juga bisa dilakukan langsung dalam loop-sementara). Tapi ada kesalahan lain yang jelas: Eropa hilang! Apa yang terjadi dengannya? Jawabannya adalah
read
mengembalikan kode pengembalian yang gagal jika hits akhir file (dalam hal ini kita dapat menyebutnya end-of-string) tanpa menemui terminator bidang terakhir pada bidang terakhir. Hal ini menyebabkan loop sementara rusak sebelum waktunya dan kami kehilangan bidang terakhir.Secara teknis kesalahan yang sama ini juga menimpa contoh-contoh sebelumnya; perbedaannya adalah bahwa pemisah bidang dianggap LF, yang merupakan default ketika Anda tidak menentukan
-d
opsi, dan<<<
mekanisme ("di sini-string") secara otomatis menambahkan LF ke string tepat sebelum ia memasukkannya sebagai masukan ke perintah. Oleh karena itu, dalam kasus tersebut, kami semacam secara tidak sengaja memecahkan masalah bidang akhir yang dijatuhkan dengan tanpa sengaja menambahkan terminator dummy tambahan ke input. Sebut solusi ini sebagai solusi "dummy-terminator". Kita dapat menerapkan solusi dummy-terminator secara manual untuk setiap pembatas khusus dengan menggabungkannya sendiri dengan string input ketika membuat instance dalam string di sini:Di sana, masalah terpecahkan. Solusi lain adalah dengan hanya mematahkan while-loop jika kedua (1)
read
kembali gagal dan (2)$REPLY
kosong, artinyaread
tidak dapat membaca karakter apa pun sebelum memukul file akhir. Demo:Pendekatan ini juga mengungkapkan LF rahasia yang secara otomatis ditambahkan ke string di sini oleh
<<<
operator pengalihan. Tentu saja bisa dilucuti secara terpisah melalui operasi pemangkasan eksplisit seperti yang dijelaskan beberapa saat yang lalu, tetapi jelas pendekatan dummy-terminator manual menyelesaikannya secara langsung, jadi kita bisa langsung melakukannya. Solusi dummy-terminator manual sebenarnya cukup nyaman karena dapat menyelesaikan kedua masalah ini (masalah field-final yang dijatuhkan dan masalah LF yang ditambahkan) dalam sekali jalan.Jadi, secara keseluruhan, ini adalah solusi yang sangat kuat. Hanya saja kelemahan yang tersisa adalah kurangnya dukungan untuk pembatas multicharacter, yang akan saya bahas nanti.
Jawaban salah # 8
(Ini sebenarnya dari pos yang sama dengan # 7 ; penjawab menyediakan dua solusi di pos yang sama.)
The
readarray
builtin, yang merupakan sinonim untukmapfile
, sangat ideal. Ini adalah perintah bawaan yang mem-parsing bytestream menjadi variabel array dalam satu shot; tidak main-main dengan loop, conditional, substitusi, atau apa pun. Dan itu tidak secara diam-diam menghapus spasi putih dari string input. Dan (jika-O
tidak diberikan) itu dengan mudah menghapus array target sebelum menetapkan untuk itu. Tapi itu masih belum sempurna, karenanya kritik saya tentang itu sebagai "jawaban yang salah".Pertama, hanya untuk menghilangkan hal ini, perhatikan bahwa, sama seperti perilaku
read
ketika melakukan field-parsing,readarray
turunkan trailing field jika kosong. Sekali lagi, ini mungkin bukan masalah bagi OP, tetapi bisa untuk beberapa kasus penggunaan. Saya akan kembali ke sini sebentar lagi.Kedua, seperti sebelumnya, itu tidak mendukung pembatas multicharacter. Saya akan memberikan perbaikan untuk ini sebentar lagi.
Ketiga, solusi seperti yang tertulis tidak menguraikan string input OP, dan pada kenyataannya, itu tidak dapat digunakan apa adanya untuk menguraikannya. Saya akan memperluas ini sebentar juga.
Untuk alasan di atas, saya masih menganggap ini sebagai "jawaban yang salah" untuk pertanyaan OP. Di bawah ini saya akan memberikan apa yang saya anggap sebagai jawaban yang tepat.
Jawaban benar
Berikut ini adalah upaya naif untuk membuat # 8 berfungsi dengan hanya menentukan
-d
opsi:Kami melihat hasilnya identik dengan hasil yang kami dapatkan dari pendekatan kondisional ganda dari
read
solusi looping yang dibahas dalam # 7 . Kita hampir dapat menyelesaikan ini dengan trik dummy-terminator manual:Masalahnya di sini adalah bahwa
readarray
mempertahankan bidang trailing, karena<<<
operator pengalihan menambahkan LF ke string input, dan oleh karena itu bidang trailing tidak kosong (jika tidak maka akan dijatuhkan). Kita dapat mengatasinya dengan secara eksplisit membatalkan elemen array akhir setelah fakta:Hanya dua masalah yang tersisa, yang sebenarnya terkait, adalah (1) ruang putih asing yang perlu dipangkas, dan (2) kurangnya dukungan untuk pembatas multicharacter.
Ruang kosong tentu saja dapat dipangkas sesudahnya (misalnya, lihat Cara memangkas ruang kosong dari variabel Bash? ). Tetapi jika kita dapat meretas pembatas multicharacter, maka itu akan menyelesaikan kedua masalah dalam satu kesempatan.
Sayangnya, tidak ada cara langsung untuk membuat pembatas multicharacter berfungsi. Solusi terbaik yang saya pikirkan adalah preprocess string input untuk menggantikan pembatas multicharacter dengan pembatas karakter tunggal yang akan dijamin tidak akan bertabrakan dengan isi dari string input. Satu-satunya karakter yang memiliki jaminan ini adalah byte NUL . Ini karena, dalam bash (meskipun tidak dalam zsh, kebetulan), variabel tidak dapat berisi byte NUL. Langkah preprocessing ini dapat dilakukan secara inline dalam proses substitusi. Berikut cara melakukannya menggunakan awk :
Akhirnya! Solusi ini tidak akan secara keliru membagi bidang di tengah, tidak akan memotong sebelum waktunya, tidak akan menjatuhkan bidang kosong, tidak akan merusak dirinya sendiri pada ekspansi nama file, tidak akan secara otomatis menghapus spasi spasi awal dan akhir, tidak akan meninggalkan LF penumpang gelap pada akhirnya, tidak memerlukan loop, dan tidak puas dengan pembatas satu karakter.
Solusi pemangkasan
Terakhir, saya ingin menunjukkan solusi pemangkasan saya sendiri yang cukup rumit dengan menggunakan
-C callback
opsi yang tidak jelasreadarray
. Sayangnya, saya sudah kehabisan ruang melawan batas posting 30.000 karakter Stack Overflow, jadi saya tidak akan bisa menjelaskannya. Saya akan meninggalkan itu sebagai latihan untuk pembaca.sumber
-d
opsi untukreadarray
pertama kali muncul di Bash 4.4.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
dan menghilangkan rangkaian final,", "
maka Anda tidak harus melalui senam untuk menghilangkan rekor akhir. Jadi:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
pada Bash yang mendukungreadarray
. Perhatikan metode Anda adalah Bash 4.4+ Saya pikir karena-d
direadarray
readarray
. Dalam hal ini, Anda dapat menggunakan solusi terbaik kedua yang ada di dalamnyaread
. Saya mengacu pada ini:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(denganawk
substitusi jika Anda memerlukan dukungan multicharacter delimiter). Beri tahu saya jika Anda mengalami masalah; Saya cukup yakin solusi ini harus bekerja pada versi bash yang cukup lama, kembali ke versi 2-sesuatu, dirilis seperti dua dekade lalu.Berikut ini cara tanpa menetapkan IFS:
Idenya menggunakan penggantian string:
untuk mengganti semua kecocokan $ substring dengan spasi putih dan kemudian menggunakan string yang diganti untuk menginisialisasi array:
Catatan: jawaban ini menggunakan operator split + glob . Dengan demikian, untuk mencegah perluasan beberapa karakter (seperti
*
), adalah ide yang bagus untuk menghentikan globbing untuk skrip ini.sumber
${string//:/ }
mencegah ekspansi shellarray=(${string//:/ })
Mencetak tiga
sumber
a=($(echo $t | tr ',' "\n"))
. Hasil yang sama dengana=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
dalambash
shell, dan yang terakhirecho
hanya mencetak baris kosong. Apa versi Linux dan cangkang mana yang Anda gunakan? Sayangnya, tidak dapat menampilkan sesi terminal dalam komentar.Kadang-kadang terjadi pada saya bahwa metode yang dijelaskan dalam jawaban yang diterima tidak bekerja, terutama jika pemisahnya adalah carriage return.
Dalam kasus-kasus itu saya memecahkan dengan cara ini:
sumber
read -a arr <<< "$strings"
tidak berfungsiIFS=$'\n'
.Jawaban yang diterima berfungsi untuk nilai dalam satu baris.
Jika variabel memiliki beberapa baris:
Kami membutuhkan perintah yang sangat berbeda untuk mendapatkan semua baris:
while read -r line; do lines+=("$line"); done <<<"$string"
Atau bash readarray yang jauh lebih sederhana :
Mencetak semua garis sangat mudah dengan memanfaatkan fitur printf:
sumber
Ini mirip dengan pendekatan oleh Jmoney38 , tetapi menggunakan sed:
Cetakan 1
sumber
Kunci untuk memisahkan string Anda menjadi array adalah pembatas multi karakter
", "
. Setiap solusi menggunakanIFS
untuk pembatas multi karakter secara inheren salah karena IFS adalah seperangkat karakter tersebut, bukan string.Jika Anda menetapkan
IFS=", "
maka string akan patah pada BAIK","
ATAU" "
atau kombinasi mereka yang bukan merupakan representasi akurat dari pembatas dua karakter dari", "
.Anda dapat menggunakan
awk
ataused
untuk memisahkan string, dengan substitusi proses:Lebih efisien menggunakan regex Anda langsung di Bash:
Dengan bentuk kedua, tidak ada sub shell dan itu akan secara inheren lebih cepat.
Sunting oleh bgoldst: Berikut adalah beberapa tolok ukur yang membandingkan
readarray
solusi saya dengan solusi regex dawg, dan saya juga memasukkanread
solusi untuk hal itu (catatan: Saya sedikit mengubah solusi regex untuk keselarasan yang lebih besar dengan solusi saya) (juga lihat komentar saya di bawah ini pos):sumber
$BASH_REMATCH
. Ini bekerja, dan memang menghindari subshell pemijahan. +1 dari saya. Namun, dengan kritik, regex itu sendiri sedikit tidak ideal, dalam hal ini Anda dipaksa untuk menggandakan bagian dari token pembatas (khususnya koma) untuk mengatasi kurangnya dukungan untuk pengganda yang tidak rakus. (juga lookarounds) dalam ERE (rasa regex "extended" yang dibangun menjadi bash). Ini membuatnya sedikit kurang generik dan kuat.\n
garis teks terbatas) yang terdiri dari bidang-bidang tersebut sehingga pelambatan bencana besar kemungkinan tidak akan terjadi. Jika Anda memiliki string dengan 100.000 bidang - mungkin Bash tidak ideal ;-) Terima kasih atas tolok ukurnya. Saya belajar satu atau dua hal.Solusi pembatas multi-karakter bash murni.
Seperti yang telah ditunjukkan orang lain di utas ini, pertanyaan OP memberikan contoh string yang dibatasi koma untuk diuraikan menjadi array, tetapi tidak menunjukkan apakah ia hanya tertarik pada pembatas koma, pembatas karakter tunggal, atau multi-karakter pembatas.
Karena Google cenderung memberi peringkat jawaban ini pada atau di dekat bagian atas hasil pencarian, saya ingin memberikan jawaban yang kuat kepada pembaca tentang pertanyaan beberapa pembatas karakter, karena itu juga disebutkan dalam setidaknya satu tanggapan.
Jika Anda mencari solusi untuk masalah pembatas multi-karakter, saya sarankan meninjau posting Mallikarjun M , khususnya respons dari gniourf_gniourf yang menyediakan solusi BASH murni yang elegan ini menggunakan ekspansi parameter:
Tautan ke komentar / posting referensi yang dikutip
Tautan ke pertanyaan yang dikutip: Bagaimana cara membagi string pada pembatas multi-karakter di bash?
sumber
Ini berfungsi untuk saya di OSX:
Jika string Anda memiliki pembatas yang berbeda, ganti saja yang pertama dengan spasi:
Sederhana :-)
sumber
Cara lain untuk melakukannya tanpa memodifikasi IFS:
Daripada mengubah IFS agar sesuai dengan pembatas yang kita inginkan, kita dapat mengganti semua kejadian pembatas yang kita inginkan
", "
dengan isi$IFS
via"${string//, /$IFS}"
.Mungkin ini akan lambat untuk string yang sangat besar?
Ini didasarkan pada jawaban Dennis Williamson.
sumber
Saya menemukan posting ini ketika mencari untuk mengurai input seperti: word1, word2, ...
tidak ada di atas yang membantu saya. dipecahkan dengan menggunakan awk. Jika itu membantu seseorang:
sumber
Coba ini
Itu mudah. Jika ingin, Anda juga dapat menambahkan deklarasi (dan juga menghapus koma):
IFS ditambahkan untuk membatalkan hal di atas tetapi berfungsi tanpa itu dalam contoh bash baru
sumber
Kita dapat menggunakan perintah tr untuk membagi string ke objek array. Ini berfungsi baik MacOS dan Linux
Opsi lain menggunakan perintah IFS
sumber
Gunakan ini:
sumber
array=( $string )
adalah (sayangnya sangat umum) antipattern: Kata membelah terjadi:string='Prague, Czech Republic, Europe'
; Perluasan pathname terjadi:string='foo[abcd],bar[efgh]'
akan gagal jika Anda memiliki file bernama, misalnya,food
ataubarf
di direktori Anda. Satu-satunya penggunaan konstruksi semacam itu yang valid adalah kapanstring
adalah sebuah bola.UPDATE: Jangan lakukan ini, karena masalah dengan eval.
Dengan upacara yang sedikit kurang:
misalnya
sumber
$
di variabel Anda dan Anda akan melihat ... Saya menulis banyak skrip dan saya tidak pernah harus menggunakan satueval
Ini hack saya!
Memisahkan string dengan string adalah hal yang cukup membosankan untuk dilakukan menggunakan bash. Apa yang terjadi adalah bahwa kami memiliki pendekatan terbatas yang hanya berfungsi dalam beberapa kasus (dipisah oleh ";", "/", "." Dan seterusnya) atau kami memiliki berbagai efek samping dalam output.
Pendekatan di bawah ini membutuhkan sejumlah manuver, tetapi saya yakin itu akan berhasil untuk sebagian besar kebutuhan kita!
sumber
Untuk elemen multiline, mengapa tidak seperti itu
sumber
Cara lain adalah:
Sekarang elemen Anda disimpan dalam array "arr". Untuk beralih melalui elemen:
sumber
eval
trik ini). Solusi Anda membiarkan$IFS
nilai koma ruang setelah fakta.Karena ada banyak cara untuk menyelesaikan ini, mari kita mulai dengan mendefinisikan apa yang ingin kita lihat dalam solusi kami.
readarray
untuk tujuan ini. Mari kita gunakan.IFS
, mengulang, dan menggunakaneval
, atau menambahkan elemen tambahan lalu menghapusnya.The
readarray
perintah paling mudah digunakan dengan baris baru sebagai pembatas. Dengan pembatas lainnya, ini dapat menambahkan elemen tambahan ke array. Pendekatan terbersih adalah pertama-tama menyesuaikan input kami ke dalam bentuk yang berfungsi baikreadarray
sebelum meneruskannya.Input dalam contoh ini tidak memiliki pembatas multicharacter. Jika kita menerapkan sedikit akal sehat, sebaiknya dipahami sebagai input yang dipisahkan koma yang setiap elemen mungkin perlu dipangkas. Solusi saya adalah dengan membagi input dengan koma menjadi beberapa baris, memotong setiap elemen, dan meneruskannya ke semua
readarray
.sumber
Pendekatan lain dapat:
Setelah ini 'arr' adalah array dengan empat string. Ini tidak memerlukan berurusan IFS atau membaca atau hal-hal khusus lainnya karenanya lebih sederhana dan langsung.
sumber