Uyumlu cevap
Bunu yapmanın birçok farklı yolu var darbe.
Bununla birlikte, ilk olarak bash
, diğerlerinde çalışmayan birçok özel özelliğe (sözde bashisms ) sahip olduğunu not etmek önemlidir.kabuk.
Özellikle, bu yazıdaki çözümlerde ve iplikteki diğerlerinde kullanılan diziler , ilişkilendirilebilir diziler ve kalıp ikamesi , bashislerdir ve birçok insanın kullandığı diğer kabuklar altında çalışmayabilir .
Mesela: Benim üzerinde Debian GNU / Linux , bir var standart denilen kabuktire; Başka bir kabuk kullanmayı seven birçok insan tanıyorumksh; ve ayrıca özel bir araç varbusybox kendi kabuk yorumlayıcısıyla (kül).
İstenen dize
Yukarıdaki soruya bölünecek dize:
IN="bla@some.com;john@home.com"
Çözümümün diğer çözümleri kırabilecek boşluk içeren dizelere sağlam olmasını sağlamak için bu dizenin değiştirilmiş bir sürümünü kullanacağım:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Dizeyi ayırıcıya dayalı olarak ayır darbe (sürüm> = 4.2)
Olarak saf bash
, biz oluşturabilir dizi için geçici bir değer ile elemanlarının bölünmüş IFS ( giriş alanı ayırıcı ). IFS, diğer şeylerin yanı sıra, bash
bir diziyi tanımlarken öğeler arasında hangi karakterlere sınırlayıcı olarak davranması gerektiğini söyler :
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
bash
Komutunun daha yeni sürümlerinde , bir komutun bir IFS tanımıyla öneklendirilmesi, yalnızca bu komutun IFS'sini değiştirir ve hemen ardından önceki değerine sıfırlar. Bu, yukarıdakileri yalnızca bir satırda yapabileceğimiz anlamına gelir:
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'
Dizenin noktalı virgülle bölünmüş IN
adlı bir dizide saklandığını görebiliriz fields
:
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>'
(Bu değişkenlerin içeriğini de kullanarak görüntüleyebiliriz 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>")
Bölme yapmanın read
en hızlı yolunun bu olduğuna dikkat edin çünkü çağrılan çatal veya dış kaynak yoktur .
Dizi tanımlandıktan sonra, her alanı (veya dizideki şimdi tanımladığınız her öğeyi) işlemek için basit bir döngü kullanabilirsiniz:
# `"${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>]
Ya da benim gibi bir kaydırma yaklaşımı kullanarak işledikten sonra dizi her alanı bırakabilirsiniz :
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Ve dizinin basit bir çıktısını almak istiyorsanız, bunun üzerinde döngü yapmanız bile gerekmez:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Güncelleme: son darbe > = 4.4
Yeni sürümlerinde, bash
şu komutla da oynayabilirsiniz mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Bu sözdizimi özel karakterleri, yeni satırları ve boş alanları korur!
Boş alanlar eklemek istemiyorsanız aşağıdakileri yapabilirsiniz:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
İle mapfile
, bir dizi bildirmeyi atlayabilir ve her birinde bir işlev çağırarak sınırlandırılmış öğeler üzerinde dolaylı olarak "döngü" oluşturabilirsiniz:
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
(Not: \0
Dizenin sonundaki boş alanları umursamıyorsanız veya yoksa, biçim dizesinin sonundaki işe yaramaz.)
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.
Veya kullanabilirsiniz <<<
ve işlev gövdesinde eklediği satırsonu bırakmak için bazı işlemler içerir:
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.
Dizeyi ayırıcıya dayalı olarak ayır kabuk
Eğer kullanamıyorsanız bash
size yazma için birçok farklı kabuklar kullanılabilir şey istiyorsanız, ya, sık sık can not kullanmak bashisms - ve bu yukarıda çözümlerde kullanıyorum dizileri içerir.
Ancak, bir dizenin "öğeleri" üzerinde döngü oluşturmak için diziler kullanmamız gerekmez. Bir desenin ilk veya son oluşumundan bir dizenin alt dizelerini silmek için birçok kabukta kullanılan bir sözdizimi vardır . *
Sıfır veya daha fazla karakter anlamına gelen joker karakter olduğunu unutmayın :
(Şimdiye kadar yayınlanan herhangi bir çözümde bu yaklaşımın olmaması, bu cevabı yazmamın ana nedenidir;)
${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
Score_Under tarafından açıklandığı gibi :
#
ve sırasıyla dizenin başlangıcından ve sonundan%
mümkün olan en kısa eşleşen alt dizeyi silin ve
##
ve %%
mümkün olan en uzun alt dizeyi silin.
Yukarıdaki sözdizimini kullanarak, alt dizeyi ayırıcıya kadar veya sonra silerek alt dizge "elemanları" dizeden ayıkladığımız bir yaklaşım oluşturabiliriz.
Aşağıdaki kod bloğu, darbe(Mac OS'ler dahil bash
),tire, ksh, ve busybox'ler kül:
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>]
İyi eğlenceler!