“IFS = read -r satırı” nı anlama


60

İç alan ayırıcı değişkenine değer katabildiğini açıkça biliyorum. Örneğin:

$ IFS=blah
$ echo "$IFS"
blah
$ 

Ayrıca read -r lineveriyi stdinisimli değişkene kaydedeceğini de biliyorum line:

$ read -r line <<< blah
$ echo "$line"
blah
$ 

Bununla birlikte, bir komut değişken değişkenini nasıl atayabilir? Ve önce veriyi stdindeğişkene depolar lineve sonra değerini lineverir IFSmi?


Yanıtlar:


104

Bazı insanlar, readbir satırı okuma komutunun hatalı olduğu fikrine sahiptir . Değil.

readsözcükleri$IFS sınırlandırılmış ve sınırlayıcılardan kaçmak için (ya da satırları devam ettirmek için) ters eğik çizgi kullanılabilecek olan (muhtemelen ters eğik çizgi ile devam eden) bir satırdaki kelimeleri okur .

Genel sözdizimi:

read word1 word2... remaining_words

reado Çıkışsız satır karakteri (veya sonu girişi), böler bulana kadar bir seferde Stdin bir bayt okur o bölünme sonucu içine karmaşık kurallar ve mağazalara göre $word1, $word2... $remaining_words.

Mesela şöyle bir girişte:

  <tab> foo bar\ baz   bl\ah   blah\
whatever whatever

ve varsayılan değeri ile $IFS, read a b catamak olacaktır:

  • $afoo
  • $bbar baz
  • $cblah blahwhatever whatever

Şimdi sadece bir argüman geçerse, bu olmaz read line. Hala var read remaining_words. Ters eğik çizgi işleme hala devam ediyor, IFS boşluk karakterleri hala baştan ve sondan kaldırılıyor.

-rSeçenek ters eğik çizgi işleme kaldırır. Böylece yukarıdaki aynı komut -ryerine atamak olur

  • $afoo
  • $bbar\
  • $cbaz bl\ah blah\

Şimdi, bölünen kısım için, iki karakter sınıfı olduğunu fark etmek önemlidir $IFS: IFS boşluk karakterleri (yani boşluk ve sekme (ve newline, burada da -d'yi kullanmadıkça önemli değil). varsayılan değerinde olmak $IFS) ve diğerleri. Bu iki karakter sınıfının tedavisi farklıdır.

İle IFS=:( :değil bir IFS boşluk karakteri olmak üzere), gibi bir girdi :foo::bar::ürüne ayrılır "", "foo", "", barve ""(ve ekstra ""bunun dışında önemli değil gerçi bazı uygulamalarına read -a). Bunu :boşlukla değiştirirsek , bölme sadece foove içine yapılır bar. Bu öncüdür ve takip edenleri görmezden gelinir ve bunların dizileri, biri gibi davranılır. Beyaz ve beyaz olmayan karakterlerin bir araya getirilmesi durumunda ek kurallar vardır $IFS. Bazı uygulamalar, IFS ( IFS=::veya IFS=' ') ' daki karakterleri ikiye katlayarak özel tedaviyi ekleyebilir / kaldırabilir .

Öyleyse burada, baştaki ve sondaki çıkmamış boşluk karakterlerinin soyulmasını istemiyorsak, bu IFS beyaz boşluk karakterlerini IFS'den kaldırmamız gerekir.

IFS boşluklu olmayan karakterlerde bile, giriş satırı bu karakterlerden birini (ve yalnızca birini) içeriyorsa ve satırdaki son karakter ( POS IFS=: read -r wordgirişlerinde olduğu gibi foo:) POSIX kabukları ile ( zshbazı pdkshsürümlerde değil ) biri olarak kabul edilir foo, bu kabuklarda, karakterler, çünkü kelime $IFSolarak kabul edilmektedir sonlandrıcı , böylece wordiçerecektir foodeğil foo:.

Yani, readyerleşik bir satır giriş okumak için kanonik yolu şudur:

IFS= read -r line

(çoğu readuygulama için, sadece NUL karakteri olarak desteklenmeyen metin satırları için geçerli olduğunu unutmayın zsh).

var=value cmdSözdizimini kullanmak IFS, yalnızca söz konusu cmdkomut süresi için farklı ayarların yapıldığından emin olur .

Geçmiş notu

readBuiltin Bourne kabuk tarafından tanıtılan ve okunması zaten oldu kelimeleri değil, çizgiler. Modern POSIX mermilerinde birkaç önemli fark var.

Bourne kabuğu readbir -rseçeneği (Korn kabuğu tarafından getirildi) desteklemedi, bu nedenle girişi sed 's/\\/&&/g'orada olduğu gibi bir şeyle ön işleme koymak dışında ters eğik çizgi işlemeyi devre dışı bırakmanın yolu yok.

Bourne kabuğu, iki karakter sınıfı nosyonuna sahip değildi (yine ksh tarafından getirildi). Bourne yılında IFS boşluk karakterleri ksh gibi tüm karakterlerin aynı tedavi altına kabuk, yani IFS=: read a b cböyle bir girişi foo::baratayın bariçin $bdeğil, boş bir dize.

Bourne kabuğunda:

var=value cmd

Eğer cmd(gibi yerleşik bir olduğu readolduğu), varayarlı kalır valuesonra cmdsona erdi. Bu özellikle kritiktir, $IFSçünkü Bourne kabuğunda, $IFSsadece açılımları değil, her şeyi bölmek için kullanılır. Ayrıca, boşluk karakterini $IFSBourne kabuğundan kaldırırsanız, "$@"artık çalışmaz.

Bourne kabuğunda, bir bileşik komutun yönlendirilmesi, bir alt kabukta çalışmasına neden olur (en eski sürümlerde, hatta çalışsa read var < fileveya exec 3< file; read var <&3çalışmadıysa bile), bu nedenle Bourne kabuğunda readterminalde kullanıcı girişi dışında herhangi bir şey için kullanılması nadirdi. (bu satır devam işleminin anlamlı olduğu yer)

Bazı Unices (HP / UX gibi, bir tane util-linuxde var) hala bir linegiriş satırı okuma komutu ( Tek UNIX Belirtimi sürüm 2'ye kadar standart bir UNIX komutu olarak kullanılmış ).

Bu, temelde, head -n 1birden fazla satır okumadığından emin olmak için bir seferde bir byte okuması dışında aynıdır . Bu sistemlerde şunları yapabilirsiniz:

line=`line`

Tabii ki bu, yeni bir sürecin oluşturulması, bir emir yürütme ve bir boru aracılığıyla çıktısının okunması anlamına gelir, bu yüzden ksh'den çok daha az verimli IFS= read -r line, ama yine de çok daha sezgiseldir.


3
+1 Space / tab'daki farklı tedavilere ilişkin bazı faydalı bilgiler için teşekkürler: IFS'de bash'daki "diğerleri" ... Farklı tedavi edildiklerini biliyordum, ancak bu açıklama hepsini çok kolaylaştırıyor. (Ve bash (ve diğer posix kabukları) ile düzenli shfarklar arasındaki içgörü , taşınabilir komut dosyaları yazmak için de faydalıdır!)
Olivier Dulac

En azından bash-4.4.19, olarak while read -r; do echo "'$REPLY'"; doneçalışır while IFS= read -r line; do echo "'$line'"; done.
x-yuri,

Bu: “... okunan yanlış kavramın bir satırı okuma komutu olduğu…” diye düşünmeme yol açıyor, eğer readbir satırı okumak kullanılıyorsa , başka bir şey olması gerektiğini düşünüyor. Hatalı olmayan bu nosyon ne olabilir? Yoksa bu ilk ifade teknik olarak doğru mu, ama gerçekte hatalı olmayan kavram şudur: "okumak, bir satırdan sözcükleri okumak için bir komuttur. Çok güçlü olduğu için bunu bir dosyadan satırları okumak için kullanabilirsiniz: IFS= read -r line"
Mike S

8

Teori

Burada oyunda iki kavram var:

  • IFSGiriş Alanı Ayırıcı, yani okunan dizenin içindeki karakterlere göre bölüneceği anlamına gelir IFS. Komut satırında IFSnormalde herhangi bir boşluk karakteri var, bu nedenle komut satırı boşluklarda böler.
  • Bunun gibi bir şey yapmak VAR=value command, "komut ortamını VARdeğere sahip olacak şekilde değiştirmek" anlamına gelir value. Temel olarak, komut değere sahip olarak commandgörecektir , ancak bundan sonra yürütülen herhangi bir komut önceki değerine sahip olarak görecektir . Başka bir deyişle, bu değişken yalnızca bu ifade için değiştirilecektir.VARvalueVAR

Bu durumda

Bu nedenle, yaptığınız IFS= read -r lineşey IFSboş bir dizgeye ayar yapmaktır (ayırmak için hiçbir karakter kullanılmaz, bu nedenle hiçbir bölme gerçekleşmez), böylece readtüm satırı okuyacak ve linedeğişkene atanacak bir kelime olarak görecektir . IFSYalnızca bu ifadeyi etkileyen değişiklikler , böylece aşağıdaki komutlar değişiklikten etkilenmez.

Yan not olarak

Komut doğruysa ve ayar, amaçlandığı gibi çalışacak olsa IFSbu durumda değil kudreti 1 olmayabilir gerekli. Yerleşim bölümündeki bashman sayfasında yazıldığı gibi read:

Standart bir girişten [...] bir satır okunur ve ilk kelime ilk adıma, ikinci kelimeyi ikinci adıma, vb. Geriye kalan sözcükler ve soyadı için atanan ayırıcıları ile atanır . Giriş akışından isimlerden daha az okunan kelimeler varsa, kalan isimlere boş değerler verilir. İçindeki karakterler IFS, çizgiyi kelimelere ayırmak için kullanılır. [...]

Yalnızca sahip olduğundan linedeğişkeni, her kelime yine de atanacak, bu nedenle yukarıdaki ve sondaki herhangi bir boşluk karakteri gerekmiyorsa 1 sadece yazabilirsiniz read -r lineve onunla yapılabilir.

[1] Bir unsetveya varsayılan $IFSdeğerin IFS boşluk alanındaread öncü / izlemede nasıl bir neden olacağına bir örnek olarak , deneyebilirsiniz:

echo ' where are my spaces? ' | { 
    unset IFS
    read -r line
    printf %s\\n "$line"
} | sed -n l

Koş ve görmeden önce önceki ve sonraki karakterlerin hayatta kalmayacağını göreceksin IFS. Ayrıca, $IFSsenaryoda daha önce bir yerde modifiye edilmek durumunda bazı garip şeyler olabilir .


5

Bu ifadeyi iki bölümden okumalısınız, ilki IFS değişkeninin değerini temizler, yani daha okunaklı IFS=""olan, ikincisi ise linedeğişkeni stdin'den okur read -r line.

Bu sözdiziminde spesifik olan şey, IFS'nin dışa vurumunun geçici ve yalnızca readkomut için geçerli olmasıdır .

Bir şeyi kaçırmadığım sürece, bu durumda temizleme işleminin IFSne olursa olsun IFSayarlandığı gibi bir etkisi olmaz , tüm satır linedeğişkende okunur . Davranışta bir değişiklik olmuş olabilirdi, ancak readtalimatlara parametre olarak birden fazla değişken iletildiğinde .

Düzenle:

-rİle biten girişi sağlamak için orada \eğik çizgi dahil edilmesi için değil, özel işlenmiş yani için linedeğişken olup çok hatlı girilmesine olanak vermesi için bir devam karakter.

$ read line; echo "[$line]"   
abc\
> def
[abcdef]
$ read -r line; echo "[$line]"  
abc\
[abc\]

IFS'nin silinmesi, potansiyel önde gelen ve sondaki boşluk veya sekme karakterlerini kısaltmak için okunmayı önleme yan etkisine sahiptir, örneğin:

$ echo "   a b c   " | { IFS= read -r line; echo "[$line]" ; }   
[   a b c   ]
$ echo "   a b c   " | { read -r line; echo "[$line]" ; }     
[a b c]

Bu farkı işaret ettiği için rici'ye teşekkür ederiz.


Eksik olan şey, eğer IFS değişmediyse, read -r linegirişi linedeğişkene atamadan önce baştaki ve sondaki boşlukları keser .
rici

@rici Ben böyle bir şeyden şüpheleniyordum ama sadece kelimeler / baştakiler değil, kelimeler arasındaki IFS karakterlerini kontrol ettim. Bu gerçeği işaret ettiğiniz için teşekkür ederiz!
jlliagre

IFS'nin silinmesi birden fazla değişkenin atanmasını da önleyecektir (yan etki). IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"gösterecektir-aa bb--
kyodev
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.