Bu sorunun tüm cevapları şu ya da bu şekilde yanlıştır.
Yanlış cevap # 1
IFS=', ' read -r -a array <<< "$string"
1: Bu bir yanlış kullanımdır $IFS
. Değeri $IFS
değişken olduğu olmayan bir alınan tek bir değişken uzunlukta daha ziyade olarak alınır, dizge ayırıcı grubu arasında tek karakterlik her alanda bu dizge ayırıcılar, read
giriş hattı kapalı böler sonlandırılabilir bir dizi karakter ( bu örnekte virgül veya boşluk).
Aslında, dışarıdaki gerçek sopalayıcılar için, tam anlamı $IFS
biraz daha fazladır. Gönderen bash kılavuzu :
Kabuk, IFS'nin her karakterine bir sınırlayıcı gibi davranır ve diğer genişletmelerin sonuçlarını bu karakterleri alan sonlandırıcıları olarak kullanarak kelimelere böler. Eğer IFS kaldırılırsa ya da değeri tam olarak <boşluk> <sekmesi> <satır> , varsayılan, sonra diziler <boşluk> , <tab> ve <satır> başında ve önceki açılımları sonuçlarına sonunda yok sayılır ve başında veya sonunda olmayan herhangi bir IFS karakteri dizisi sözcükleri sınırlandırmaya yarar. Eğer IFS varsayılan dışında bir değere sahiptir, ardından boşluk karakterleri arasında dizileri <boşluk> , <tab> ve <boşluk karakteri IFS ( IFS boşluk karakteri) değerinde olduğu sürece sözcüğün başında ve sonunda yoksayılır . Herhangi bir karakter IFS değil IFS herhangi bitişik birlikte boşluk IFS , bir alanı sınırlandırır boşluk karakterleri. Bir dizi IFS boşluk karakteri de sınırlayıcı olarak ele alınır. IFS değeri null olursa, kelime ayırma gerçekleşmez.
Temel olarak, varsayılan olmayan null olmayan değerler için $IFS
alanlardan biri (1) "IFS boşluk karakterleri" (yani <boşluk> , <tab> ve <newline> ( satır beslemesi (LF) anlamına gelen "yeni satır" ),$IFS
) veya (2) mevcut olan herhangi bir sivil "boşluk karakteri IFS" $IFS
onu çevreleyen her ne "boşluk karakterleri IFS" ile birlikte giriş satırında.
OP için, bir önceki paragrafta tarif ettiğim ikinci ayırma modunun giriş dizesi için tam olarak istediği şey olması mümkündür, ancak tarif ettiğim ilk ayırma modunun hiç de doğru olmadığından emin olabiliriz. Örneğin, giriş dizesi olsaydı ne olurdu 'Los Angeles, United States, North America'
?
IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")
2: Bu çözümü tek karakterli bir ayırıcıyla (tek başına virgül, yani takip eden boşluk veya başka bir bagaj olmadan) kullanacak olsanız bile, $string
değişkenin değeri herhangi bir LF içeriyorsa read
, ilk LF ile karşılaştığında işlemeyi durdurur. read
Builtin sadece çağrı başına bir satır işler. Bu boru veya girdinin bile doğrudur ancak etmek read
açıklamada, biz bu örnekte yapıyorsun gibi burada-string mekanizması ve böylece işlenmemiş girişi kaybolmasına garantilidir. Güç veren kodread
Yerleşikliğe , içerdiği komut yapısı içindeki veri akışı hakkında hiçbir bilgiye sahip değildir.
Bunun bir soruna neden olma olasılığının düşük olduğunu iddia edebilirsiniz, ancak yine de mümkünse kaçınılması gereken ince bir tehlike. Bunun nedeni, read
yerleşkenin aslında iki düzey girdi ayrımı yapmasıdır: önce satırlara, sonra alanlara. OP sadece bir seviye bölünme istediğinden,read
yerleşikin uygun değildir ve bundan kaçınmalıyız.
3: Bu çözümle ilgili açık olmayan potansiyel bir sorun, read
boş alanları her zaman boş bırakıyorsa da, boş alanları başka şekilde koruyor olmasıdır. İşte bir demo:
string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")
Belki OP bunu umursamaz, ama hala bilinmeye değer bir sınırlamadır. Çözeltinin sağlamlığını ve genelliğini azaltır.
Bu sorun, read
daha sonra göstereceğim gibi , giriş dizesine beslemeden hemen önce kukla bir izleyen sınırlayıcı ekleyerek çözülebilir .
Yanlış cevap # 2
string="1:2:3:4:5"
set -f # avoid globbing (expansion of *).
array=(${string//:/ })
Benzer fikir:
t="one,two,three"
a=($(echo $t | tr ',' "\n"))
(Not: Yanıtlayanın atladığı anlaşılan ikame komutunun etrafına eksik parantezleri ekledim.)
Benzer fikir:
string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
Bu çözümler, dizeyi alanlara bölmek için dizi atamasında sözcük bölünmesini kullanır. Ne kadar tuhaf ki, read
genel kelime bölme de $IFS
özel değişkeni kullanıyor , ancak bu durumda varsayılan <space><tab> <newline> değerine ayarlandığı ima ediliyor ve bir veya daha fazla IFS dolayısıyla herhangi dizisi karakterler (şu anda tümü boşluk karakteri olan) bir alan sınırlayıcısı olarak kabul edilir.
Bu read
, kelime bölünmesinin tek başına bir bölünme seviyesi oluşturduğundan, taahhüt edilen iki bölünme seviyesi sorununu çözer . Ancak daha önce olduğu gibi, buradaki sorun, giriş dizesindeki tek tek alanların zaten $IFS
karakterler içerebilmesidir ve bu nedenle kelime bölme işlemi sırasında yanlış bölünürler. Bu, bu yanıt verenler tarafından sağlanan örnek giriş dizelerinden herhangi biri için geçerli değildir (ne kadar uygun ...), ancak elbette bu deyimi kullanan herhangi bir kod tabanının, bu varsayım çizginin bir noktasında ihlal edildiğinde patlar. Bir kez daha, 'Los Angeles, United States, North America'
(veya 'Los Angeles:United States:North America'
) karşı örneğimi düşünün .
Ayrıca, kelime bölme normal olarak takip eder Dosyaismi ( aka dosyayolu aka , yapılırsa, karakter içeren potansiyel bozulmuş kelime olacaktır globbing) *
, ?
ya da [
bunu takiben ]
(ve eğer extglob
ayarlanmış, parantez fragmanları öncesinde ?
, *
, +
, @
, veya !
) bunları dosya sistemi nesneleriyle eşleştirerek ve kelimeleri ("globs") uygun şekilde genişleterek. Bu üç yanıtlayıcıdan ilki, set -f
globbing'i devre dışı bırakmak için önceden çalışarak bu sorunu akıllıca azalttı . Teknik olarak bu işe yarıyor (muhtemelen eklemeniz gerekirset +f
daha sonra buna bağlı olabilecek sonraki kod için globbing'i yeniden etkinleştirin), ancak yerel kodda temel bir dizeden diziye ayrıştırma işlemini kesmek için genel kabuk ayarlarıyla uğraşmak istenmez.
Bu cevapla ilgili bir başka sorun da tüm boş alanların kaybolmasıdır. Uygulamaya bağlı olarak bu bir sorun olabilir veya olmayabilir.
Not: Bu çözümü kullanacaksanız, bir komut değiştirme (kabuk çatalı) başlatma, bir boru hattı başlatma ve bir boru hattı başlatma sorununa gitmek yerine ${string//:/ }
, parametre genişletme "kalıp değiştirme" biçimini kullanmak daha iyidir ve parametre genişletme tamamen kabuk-dahili bir işlem olduğundan, harici bir yürütülebilir dosya ( tr
veya sed
) çalıştırılır. (Ayrıca, tr
ve sed
çözümleri için, giriş değişkeni komut ikamesi içinde çift tırnak içine alınmalıdır; aksi takdirde, kelime bölme echo
komutta etkili olur ve alan değerlerini potansiyel olarak karıştırır. Ayrıca, $(...)
komut ikamesi biçimi eskisine göre tercih edilir`...`
komut ikamelerinin yuvalanmasını basitleştirdiğinden ve metin editörleri tarafından daha iyi sözdizimi vurgulamasına izin verdiğinden, form.
Yanlış cevap # 3
str="a, b, c, d" # assuming there is a space after ',' as in Q
arr=(${str//,/}) # delete all occurrences of ','
Bu cevap neredeyse # 2 ile aynı . Aradaki fark, cevaplayıcının alanların biri varsayılan olarak temsil edilirken $IFS
diğeri değil, iki karakterle sınırlandırıldığı varsayımını yapmış olmasıdır . IFS ile temsil edilmeyen karakteri bir desen değiştirme genişletmesi kullanarak kaldırarak ve daha sonra hayatta kalan IFS ile temsil edilen sınırlayıcı karakterdeki alanları bölmek için kelime bölme kullanarak bu oldukça özel durumu çözdü.
Bu çok genel bir çözüm değil. Ayrıca, virgülün burada gerçekten "birincil" sınırlayıcı karakter olduğu ve onu sıyırıp alan ayırma için boşluk karakterine bağlı olarak basitçe yanlış olduğu söylenebilir. Bir kez daha, benim counterexample göz önünde bulundurun: 'Los Angeles, United States, North America'
.
Ayrıca, dosya adı genişletmesi genişletilmiş sözcükleri bozabilir, ancak bu, ödev için globbing'i set -f
ve ardından geçici olarak devre dışı bırakarak önlenebilir set +f
.
Ayrıca, yine, uygulamaya bağlı bir sorun olabilecek veya olmayabilecek tüm boş alanlar kaybolacaktır.
Yanlış cevap # 4
string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"
Bu, # 2 ve # 3'e benzerdir , çünkü işi yapmak için kelime bölmeyi kullanır, ancak şimdi kod açıkça $IFS
yalnızca giriş dizesinde bulunan tek karakterlik alan sınırlayıcıyı içerecek şekilde ayarlanır . Bunun, OP'nin virgül alanı sınırlayıcısı gibi çok karakterli alan sınırlayıcıları için çalışamayacağı tekrarlanmalıdır. Ancak bu örnekte kullanılan LF gibi tek karakterli bir sınırlayıcı için, aslında mükemmel olmaya yaklaşır. Alanlar, daha önce yanlış cevaplarla gördüğümüz gibi istemeden ortada bölünemez ve gerektiğinde yalnızca bir seviye ayrılır.
Bir sorun, dosya adı genişletmesinin etkilenen kelimeleri daha önce açıklandığı gibi bozmasıdır, ancak bir kez daha bu, kritik ifadeyi set -f
ve içine sararak çözülebilir set +f
.
Bir başka potansiyel problem, LF'nin daha önce tanımlandığı gibi bir "IFS boşluk karakteri" olarak nitelendirilmesi nedeniyle, # 2 ve # 3'teki gibi tüm boş alanların kaybolacağıdır . Sınırlayıcı "IFS boşluk karakteri" değilse bu sorun olmaz ve uygulamaya bağlı olarak yine de önemli olmayabilir, ancak çözümün genelliğini bozar.
Yani, bir tek karakteri sınırlayıcı var varsayarak, özetlemek gerekirse, ve o da olmayan bir "boşluk karakteri IFS" dır ya da boş alanlara umurumda değil ve kritik açıklama sarın set -f
ve set +f
daha sonra bu çözüm çalışmaları , ama başka türlü değil.
(Ayrıca, bilgi uğruna, bash içindeki bir değişkene bir LF atamak $'...'
sözdizimi ile daha kolay yapılabilir , örneğin IFS=$'\n';
.)
Yanlış cevap # 5
countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"
Benzer fikir:
IFS=', ' eval 'array=($string)'
Bu çözüm etkili bir şekilde # 1 ( $IFS
virgül-boşluğuna ayarlandığı için) ve # 2-4 (dizeyi alanlara bölmek için kelime bölme kullandığından ) arasında bir çaprazlamadır . Bu nedenle, yukarıdaki yanlış cevapların tümünü etkileyen, çoğu dünyanın en kötüsü gibi sorunların çoğundan muzdariptir.
Ayrıca, ikinci varyant ile ilgili olarak, eval
argümanın tek tırnaklı bir dize değişmezi olduğu için çağrı tamamen gereksiz gibi görünebilir ve bu nedenle statik olarak bilinir. Ancak aslında eval
bu şekilde kullanmanın çok açık bir yararı yoktur . Normalde, yalnızca değişken bir atamadan oluşan basit bir komut çalıştırdığınızda , yani onu takip eden gerçek bir komut kelimesi olmadan, atama kabuk ortamında etkili olur:
IFS=', '; ## changes $IFS in the shell environment
Basit komut birden çok değişken ataması içeriyor olsa bile bu doğrudur ; yine, komut kelimesi olmadığı sürece, tüm değişken atamaları kabuk ortamını etkiler:
IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment
Değişken atama komut adı takılır Ama eğer, o zaman yok (ben bu bir "ön eki atama" diyoruz) değil , bir yerleşik olup olmadığını kabuk ortamını etkileyen ve bunun yerine sadece bakılmaksızın, komutun çevreyi etkiler veya harici:
IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it
Bash el kitabından ilgili alıntı :
Komut adı sonuçlanmazsa, değişken atamaları geçerli kabuk ortamını etkiler. Aksi takdirde, değişkenler yürütülen komutun ortamına eklenir ve geçerli kabuk ortamını etkilemez.
Değişken atamanın bu özelliğinden $IFS
yalnızca geçici olarak değiştirmek mümkündür , bu $OIFS
da ilk varyanttaki değişkenle yapılan gibi tüm kaydet ve geri yükle oyunundan kaçınmamızı sağlar . Ancak burada karşılaştığımız zorluk, çalıştırmamız gereken komutun kendisinin sadece değişken bir atama olması ve bu nedenle $IFS
atamayı geçici hale getirmek için bir komut kelimesi içermemesidir . Kendinizi düşünebilirsiniz, neden ödevi geçici : builtin
yapmak için ifadeye bir no-op komut kelimesi $IFS
eklemiyorsunuz? Bu işe yaramaz çünkü $array
atamayı geçici hale getirir :
IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command
Yani, etkili bir çıkmazdayız, biraz yakalama-22. Ancak, eval
kodunu çalıştırdığında, normal, statik kaynak kodu gibi kabuk ortamında çalıştırır ve bu nedenle , önek ataması , kabuk ortamında etkili olması $array
için eval
bağımsız değişken içindeki atamayı çalıştırabiliriz. komutun $IFS
önüne eval
eklenirse, eval
komuttan daha uzun süre kullanılmaz . Bu, bu çözümün ikinci varyantında kullanılan hiledir:
IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does
Gördüğünüz gibi, aslında oldukça zekice bir hile ve tam olarak neyin gerekli olduğunu (en azından atama etkisi ile ilgili) oldukça açık olmayan bir şekilde gerçekleştiriyor. Aslında buna rağmen genel olarak bu hile karşı değilim eval
; güvenlik tehditlerine karşı koruma sağlamak için bağımsız değişken dizesini tek tırnak içine almaya dikkat edin.
Fakat yine de, "tüm dünyaların en kötüsü" sorunların toplanması nedeniyle, bu OP'nin gerekliliğine hala yanlış bir cevaptır.
Yanlış cevap no.
IFS=', '; array=(Paris, France, Europe)
IFS=' ';declare -a array=(Paris France Europe)
Um ... ne? OP bir diziye ayrıştırılması gereken bir dize değişkenine sahiptir. Bu "cevap", bir dizi değişmezine yapıştırılan giriş dizesinin sözcük içeriği ile başlar. Sanırım bunu yapmanın bir yolu var.
Yanıtlayan, $IFS
değişkenin tüm bağlamlardaki tüm bash ayrıştırmalarını etkilediğini varsaymış olabilir , ki bu doğru değildir. Bash el kitabından:
IFS Genişletmeden sonra sözcük bölme ve read builtin komutuyla satırları kelimelere bölme için kullanılan Dahili Alan Ayırıcı . Varsayılan değer şudur: <boşluk ><tab> <newline> .
Yani $IFS
özel değişken aslında sadece iki bağlamda kullanılır: gerçekleştirilir (1) kelimesi bölünme genişleme sonrasında (yani değil tarafından kelimelere bölme girdi hatları için ve (2) bash kaynak kodunu ayrıştırılırken) read
yerleşiğini.
Bunu daha netleştirmeye çalışayım. Ayrıştırma ile yürütme arasında bir ayrım yapmak iyi olabilir diye düşünüyorum . Bash ilk gerekir ayrıştırmak Açıkçası olan kaynak kodunu, ayrıştırma olayı ve daha sonra durum yürütür genişleme resmin içine geldiğinde hangi kod. Genişletme gerçekten bir yürütme olayıdır. Dahası, $IFS
yukarıda alıntıladığım değişkenin tanımıyla ilgili sorun yaşıyorum; yerine kelime bölme yapılır söyleyerek daha genişlemeden sonra , o kelime bölme yapılır söyleyebilirim sırasında belki de daha doğrusu, kelime bölme olduğunu genişleme veya bir kısmıgenişleme süreci. "Sözcük bölme" ifadesi yalnızca bu genişleme adımına karşılık gelir; ne yazık ki dokümanlar "split" ve "words" kelimelerini çok fazla atmış gibi görünse de, bash kaynak kodunun ayrıştırılmasına atıfta bulunmak için asla kullanılmamalıdır. İşte bash kılavuzunun linux.die.net sürümünden ilgili bir alıntı :
Genişletme, komut satırında sözcüklere ayrıldıktan sonra gerçekleştirilir. Gerçekleştirilen yedi tür genişletme vardır: küme ayracı genişletme , tilde genişletme , parametre ve değişken genişletme , komut ikamesi , aritmetik genişletme , sözcük bölme ve yol adı genişletme .
Genişlemelerin sırası: küme ayracı genişlemesi; tilde genişletme, parametre ve değişken genişletme, aritmetik genişletme ve komut değiştirme (soldan sağa şekilde yapılır); sözcük bölme; ve yol adı genişletmesi.
Genişleme bölümünün ilk cümlesinde "kelimeler" yerine "jetonlar" kelimesini tercih ettiğinden, kılavuzun GNU sürümünün biraz daha iyi olduğunu iddia edebilirsiniz :
Genişletme, belirteçlere ayrıldıktan sonra komut satırında gerçekleştirilir.
Önemli olan, $IFS
bash'ın kaynak kodunu ayrıştırma şeklini değiştirmemesi. Bash kaynak kodunun ayrıştırılması aslında komut dizileri, komut listeleri, boru hatları, parametre genişletmeleri, aritmetik ikameler ve komut ikameleri gibi kabuk gramerinin çeşitli öğelerinin tanınmasını içeren çok karmaşık bir süreçtir. Çoğunlukla, bash ayrıştırma işlemi, değişken atamaları gibi kullanıcı düzeyindeki eylemlerle değiştirilemez (aslında, bu kuralın bazı küçük istisnaları vardır; örneğin, çeşitli compatxx
kabuk ayarlarına bakınanında davranışı ayrıştırma işleminin belirli yönlerini değiştirebilir). Bu karmaşık ayrıştırma işleminden kaynaklanan akış yukarı "sözcükler" / "belirteçleri", daha sonra, genişletilmiş (genişleyen?) Metnin sözcük akışının aşağı akış kısmına sözcük bölünmesi olarak genel "genişleme" işlemine göre genişletilir. kelimeler sadece bu sürecin bir adımıdır. Sözcük bölme yalnızca önceki genişletme adımından tükenmiş metne dokunur; kaynak bytestream öğesinden hemen ayrıştırılan değişmez metni etkilemez.
Yanlış cevap no. 7
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
Bu en iyi çözümlerden biridir. Kullanmaya geri döndüğümüze dikkat edin read
. Daha önce söylemedim mi read
, çünkü bu sadece bir tane ihtiyacımız olduğunda iki seviyede bölme gerçekleştiriyor mu? Buradaki hile, read
etkili bir şekilde sadece bir seviye bölme yapacak şekilde, özellikle çağrı başına sadece bir alanı bölerek çağırabilirsiniz, bu da onu bir döngüde tekrar tekrar çağırmanın maliyetini gerektirir. Biraz çabuk bir el, ama işe yarıyor.
Ama sorunlar var. Birincisi: En az bir NAME bağımsız değişkeni sağladığınızda read
, giriş dizesinden ayrılmış her alanda önde gelen ve arkadaki boşlukları otomatik olarak yok sayar. Bu, $IFS
bu yayında daha önce açıklandığı gibi varsayılan değerine ayarlanmış olsun veya olmasın oluşur . Şimdi, OP spesifik kullanım durumu için bunu önemsemeyebilir ve aslında, ayrıştırma davranışının arzu edilen bir özelliği olabilir. Ancak bir dizeyi alanlara ayrıştırmak isteyen herkes bunu istemeyecektir. Ancak bir çözüm var: Açıkça görülmeyen bir kullanımı read
sıfır NAME argümanını iletmektir. Bu durumda, read
o adında bir değişkende giriş akımından aldığını tüm giriş hattını saklayacaktır $REPLY
öyle bir bonus olarak, ve değildeğerden baş ve sondaki boşlukları sıyırın. Bu, read
kabuk programlama kariyerimde sık sık kullandığım çok sağlam bir kullanımdır . İşte davranıştaki farklılığın bir gösterimi:
string=$' a b \n c d \n e f '; ## input string
a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a b" [1]="c d" [2]="e f") ## read trimmed surrounding whitespace
a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]=" a b " [1]=" c d " [2]=" e f ") ## no trimming
Bu çözümle ilgili ikinci sorun, OP'nin virgül alanı gibi özel bir alan ayırıcısının durumunu ele almamasıdır. Daha önce olduğu gibi, çok çözümlü ayırıcılar desteklenmez, bu da bu çözümün talihsiz bir sınırlamasıdır. Seçeneğe ayırıcı belirterek en azından virgül üzerinde bölünmeye çalışabiliriz -d
, ama ne olduğuna bakın:
string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")
Tahmin edilebileceği gibi, hesaplanmamış çevreleyen beyaz alan alan değerlerine çekilmiştir ve bu nedenle daha sonra düzeltme işlemleri ile düzeltilmesi gerekecektir (bu doğrudan while döngüsü içinde de yapılabilir). Ancak başka bir açık hata daha var: Avrupa eksik! Ona ne oldu? Yanıt, read
son alanda bir son alan sonlandırıcısıyla karşılaşmadan dosya sonuna (bu durumda dize sonu diyebiliriz) isabet ederse başarısız bir dönüş kodu döndürür. Bu while döngüsünün erken kırılmasına neden olur ve son alanı kaybederiz.
Teknik olarak aynı hata önceki örnekleri de etkiledi; aradaki fark, alan ayırıcısının LF olarak alınmasıdır, bu -d
seçeneği belirtmediğinizde varsayılan değerdir ve <<<
("here-string") mekanizması, dizeyi beslemeden hemen önce otomatik olarak bir LF ekler komuta giriş. Bu nedenle, bu durumlarda, yanlışlıkla girişe ek bir kukla sonlandırıcı ekleyerek bırakılan bir son alan sorununu yanlışlıkla çözdük. Bu çözüme "kukla sonlandırıcı" çözümü diyelim. Kukla-sonlandırıcı çözümünü herhangi bir özel sınırlayıcı için, burada dizede başlatırken giriş dizesiyle birleştirerek kendimiz uygulayabiliriz:
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
Orada sorun çözüldü. Başka bir çözüm ise while döngüsünü sadece (1) read
döndürülen başarısızlık ve (2) $REPLY
boşsa kırmaktır , yani read
dosya sonuna gelmeden önce herhangi bir karakteri okuyamazdı. Demo:
a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
Bu yaklaşım ayrıca <<<
yeniden yönlendirme operatörü tarafından burada dizeye otomatik olarak eklenen gizli LF'yi de ortaya çıkarır . Elbette, bir an önce açıklandığı gibi açık bir kırpma işlemi ile ayrı ayrı çıkarılabilir, ancak açıkçası manuel kukla sonlandırıcı yaklaşımı doğrudan çözer, bu yüzden bununla gidebiliriz. Manuel kukla-sonlandırıcı çözümü aslında bu iki problemi (düşülen son alan problemi ve ekli LF problemi) bir seferde çözmesi açısından oldukça uygundur.
Yani, genel olarak, bu oldukça güçlü bir çözümdür. Sadece kalan zayıflık, daha sonra ele alacağım çok karakterli sınırlayıcılar için destek eksikliğidir.
Yanlış cevap no.
string='first line
second line
third line'
readarray -t lines <<<"$string"
(Bu aslında # 7 ile aynı mesajdan; cevaplayan aynı mesajda iki çözüm sundu.)
readarray
Eşanlamlıdır yerleşik, mapfile
idealdir. Bir çekim akışını bir çekimde bir dizi değişkenine ayrıştıran yerleşik bir komuttur; döngülerle, koşullarla, değişikliklerle veya başka bir şeyle uğraşmak yok. Ve girdi dizesindeki boşlukları gizlice gizlemez. Ve ( -O
verilmezse), atamadan önce hedef diziyi temizler. Ama yine de mükemmel değil, bu yüzden ona "yanlış cevap" olarak eleştirim.
İlk olarak, bunu yoldan çıkarmak için, tıpkı read
alan ayrıştırma işlemi readarray
gibi, boşsa arka alanı bıraktığını unutmayın . Yine, bu muhtemelen OP için bir endişe değildir, ancak bazı kullanım durumları için olabilir. Birazdan buna geri döneceğim.
İkincisi, daha önce olduğu gibi, çok karakterli sınırlayıcıları desteklemez. Bir an için bunun için bir düzeltme yapacağım.
Üçüncüsü, yazılı çözüm OP'nin girdi dizesini ayrıştırmaz ve aslında onu ayrıştırmak için olduğu gibi kullanılamaz. Ben de bu konuyu birazdan genişleteceğim.
Yukarıdaki nedenlerden dolayı, hala OP'nin sorusuna "yanlış bir cevap" olduğunu düşünüyorum. Aşağıda doğru cevap olduğunu düşündüğüm şeyi vereceğim.
Doğru cevap
Sadece seçeneği belirterek # 8'in çalışması için naif bir girişim -d
:
string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
Sonucun # 7'deread
tartışılan döngü çözümünün çift koşullu yaklaşımından elde ettiğimiz sonuçla aynı olduğunu görüyoruz . Bunu manuel kukla sonlandırıcı hile ile neredeyse çözebiliriz:
readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')
Buradaki sorun readarray
, <<<
yönlendirme operatörünün LF'yi giriş dizesine eklediği ve dolayısıyla arka alanın boş olmadığı (aksi takdirde bırakılacağı) , arka alanın korunmasıdır . Gerçekte son dizi elemanını açıkça ayarlayarak bununla ilgilenebiliriz:
readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
Aslında ilişkili olan geriye kalan sadece iki sorun, (1) kesilmesi gereken yabancı boşluk ve (2) çok karakterli sınırlayıcılar için destek eksikliğidir.
Beyaz alan elbette daha sonra kesilebilir (örneğin, Bash değişkeninden beyaz alan nasıl kırpılır? Bölümüne bakın ). Ancak çok karakterli bir sınırlayıcıyı hackleyebiliyorsak, bu her iki sorunu da tek seferde çözecektir.
Ne yazık ki, çok karakterli bir sınırlayıcının çalışmasını sağlamanın doğrudan bir yolu yoktur . Düşündüğüm en iyi çözüm, çok karakterli sınırlayıcıyı, giriş dizesinin içeriğiyle çarpışmayacağı garanti edilecek tek karakterli bir ayırıcıyla değiştirmek için giriş dizesini önceden işlemektir. Bu garantiye sahip tek karakter NUL baytıdır . Bunun nedeni, bash (zsh içinde olmasa da, tesadüfen) değişkenlerin NUL baytını içerememesidir. Bu ön işleme aşaması, bir proses ikamesinde satır içi olarak gerçekleştirilebilir. Awk kullanarak nasıl yapılacağı aşağıda açıklanmıştır :
readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
Nihayet orada! Bu çözüm hatalı olarak ortadaki alanları bölmeyecek, erken kesilmeyecek, boş alanları bırakmayacak, dosya adı genişletmelerinde kendini bozmayacak, otomatik olarak önde ve arkadaki boşlukları şeritlemeyecek, sonunda bir kaçak yolcu LF bırakmayacak, döngü gerektirmez ve tek karakterli sınırlayıcıya yerleşmez.
Düzeltme çözümü
Son olarak, belirsiz -C callback
seçeneğini kullanarak kendi oldukça karmaşık kırpma çözümümü göstermek istedim readarray
. Ne yazık ki, Stack Overflow'un acımasız 30.000 karakter yazı sınırına karşı yerim bitti, bu yüzden açıklayamayacağım. Bunu okuyucu için bir egzersiz olarak bırakacağım.
function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
,
virgül gibi tek bir karakter değil (virgül-boşluk) sınırlama hakkında soruyor . Sadece ikincisiyle ilgileniyorsanız, yanıtları takip etmek daha kolaydır: stackoverflow.com/questions/918886/…