Jawaban yang kompatibel
Ada banyak cara berbeda untuk melakukan ini pesta.
Namun, penting untuk diketahui terlebih dahulu yang bash
memiliki banyak fitur khusus (disebut bashism ) yang tidak akan berfungsi di fitur lainnyakulit.
Secara khusus, array , array asosiatif , dan substitusi pola , yang digunakan dalam solusi dalam posting ini serta yang lain di utas, adalah bashism dan mungkin tidak berfungsi di bawah cangkang lain yang banyak digunakan orang.
Sebagai contoh: pada Debian GNU / Linux saya , ada shell standar yang disebutberlari; Saya tahu banyak orang yang suka menggunakan shell lain yang disebutksh; dan ada juga alat khusus yang disebutbusybox dengan penerjemah shellnya sendiri (Abu).
String yang diminta
String yang akan dibagi dalam pertanyaan di atas adalah:
IN="bla@some.com;john@home.com"
Saya akan menggunakan versi modifikasi dari string ini untuk memastikan bahwa solusi saya kuat untuk string yang berisi spasi putih, yang dapat memecahkan solusi lain:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Split string berdasarkan pembatas di pesta (versi> = 4.2)
Secara murni bash
, kita bisa membuat array dengan elemen yang dipisahkan oleh nilai sementara untuk IFS ( pemisah bidang input ). IFS, antara lain, memberi tahu bash
karakter mana yang harus diperlakukan sebagai pembatas antara elemen ketika mendefinisikan array:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
Dalam versi yang lebih baru dari bash
, awalan perintah dengan definisi IFS mengubah IFS untuk perintah yang hanya dan me-reset ke nilai sebelumnya segera setelah itu. Ini berarti kita dapat melakukan hal di atas hanya dalam satu baris:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
Kita dapat melihat bahwa string IN
telah disimpan ke dalam array bernama fields
, dipisah pada titik koma:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(Kami juga dapat menampilkan konten dari variabel-variabel ini menggunakan declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
Perhatikan bahwa read
ini adalah cara tercepat untuk melakukan pemecahan karena tidak ada garpu atau sumber daya eksternal yang disebut.
Setelah array didefinisikan, Anda dapat menggunakan loop sederhana untuk memproses setiap bidang (atau, lebih tepatnya, setiap elemen dalam array yang sekarang telah Anda tetapkan):
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Atau Anda bisa menjatuhkan setiap bidang dari array setelah diproses menggunakan pendekatan pergeseran , yang saya suka:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Dan jika Anda hanya menginginkan cetakan array yang sederhana, Anda bahkan tidak perlu mengulanginya:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Perbarui: baru-baru ini pesta > = 4.4
Di versi yang lebih baru bash
, Anda juga dapat bermain dengan perintah mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Sintaks ini mempertahankan karakter khusus, baris baru, dan bidang kosong!
Jika Anda tidak ingin memasukkan bidang kosong, Anda dapat melakukan hal berikut:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Dengan mapfile
, Anda juga dapat melewati mendeklarasikan array dan secara implisit "loop" di atas elemen yang dibatasi, memanggil fungsi pada masing-masing:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(Catatan: \0
di akhir string format tidak berguna jika Anda tidak peduli dengan bidang kosong di akhir string atau mereka tidak ada.)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Atau Anda bisa menggunakan <<<
, dan di badan fungsi menyertakan beberapa pemrosesan untuk menghapus baris baru yang ditambahkan:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Split string berdasarkan pembatas di kulit
Jika Anda tidak dapat menggunakan bash
, atau jika Anda ingin menulis sesuatu yang dapat digunakan di banyak shell yang berbeda, Anda sering tidak dapat menggunakan bashism - dan ini termasuk array yang telah kami gunakan dalam solusi di atas.
Namun, kita tidak perlu menggunakan array untuk mengulang "elemen" string. Ada sintaks yang digunakan dalam banyak shell untuk menghapus substring dari string dari kemunculan pertama atau terakhir suatu pola. Perhatikan bahwa *
wildcard yang mewakili nol atau lebih karakter:
(Kurangnya pendekatan ini dalam solusi yang diposting sejauh ini adalah alasan utama saya menulis jawaban ini;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
Seperti yang dijelaskan oleh Score_Under :
#
dan %
hapus substring pencocokan yang sesingkat mungkin dari awal dan akhir string, dan
##
dan %%
hapus substring pencocokan yang terpanjang.
Menggunakan sintaks di atas, kita dapat membuat pendekatan di mana kita mengekstraksi "elemen" substring dari string dengan menghapus substring hingga atau setelah pembatas.
Kode kunci di bawah berfungsi dengan baik di pesta(termasuk Mac OS bash
),berlari, ksh, dan busyboxini Abu:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Selamat bersenang-senang!