IFS'i Anlamak


71

Bu sitede ve StackOverflow'taki aşağıdaki birkaç konu, nasıl IFSçalıştığını anlamak için yardımcı oldu :

Fakat hala bazı kısa sorularım var. Gelecekte daha iyi okuyuculara yardımcı olabileceğini düşündüğümden, aynı yazıdan onlara sormaya karar verdim:

S1. IFSTipik olarak "alan bölme" bağlamında tartışılmaktadır. Alan bölme , sözcük bölme ile aynı mıdır ?

S2: POSIX özelliği şunları söylüyor :

IFS'nin değeri null ise, alan bölme işlemi yapılmaz.

Ayar null IFS=ayarıyla aynı mı IFS? Bu da onu ayarlayarak kastedilen bu empty stringmu?

S3: POSIX belirtiminde aşağıdakileri okudum :

Eğer IFS ayarlanmadıysa, kabuk IFS'nin değeriymiş gibi davranmalıdır. <space>, <tab> and <newline>

Varsayılan değerini geri yüklemek istediğimi söyle IFS. Bunu nasıl yaparım? (daha spesifik olarak, nasıl <tab>ve nasıl başvurabilirim <newline>?)

S4: Sonunda, bu kod nasıl olurdu:

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file

ilk satırı değiştirdiğimizde davran

while read -r line # Use the default IFS value

veya:

while IFS=' ' read -r line

Yanıtlar:


28
  1. Evet, onlar aynı.
  2. Evet.
  3. Kısaca ve benzer kabuklarda, bunun gibi bir şey yapabilirsin IFS=$' \t\n'. Aksi takdirde, değişmez kontrol kodlarını kullanarak ekleyebilirsiniz [space] CTRL+V [tab] CTRL+V [enter]. Bununla birlikte, bunu yapmayı planlıyorsanız, eski IFSdeğeri geçici olarak saklamak için başka bir değişken kullanmak ve ardından daha sonra geri yüklemek (veya var=foo commandsözdizimini kullanarak bir komut için geçici olarak geçersiz kılmak) daha iyidir .
    • İlk kod parçacığı, $linekelimeyi bölmek için hiçbir alan ayırıcısı olmadığı için tüm satırı okumaya başlatacak . Bununla birlikte, birçok mermi dizeleri saklamak için cstrings kullandığından, bir NUL'nin ilk örneğinin hala görünümünün zamanından önce sonlandırılmasına neden olabileceğini unutmayın.
    • İkinci kod pasajı, girişin tam bir kopyasını ekleyemez $line. Örneğin, ardı arda birden fazla alan ayırıcı varsa, bunlar ilk elemanın tek bir örneğine yapılacaktır. Bu genellikle çevreleyen boşlukların kaybı olarak kabul edilir.
    • Üçüncü kod pasajı, ikincisi ile aynı olur, ancak yalnızca bir boşlukta bölünür (normal boşluk, sekme veya yeni satır değil).

3
S2 cevabı yanlıştır: boş IFSve bir set IFSçok farklıdır. S4'ün cevabı kısmen yanlıştır: burada iç ayırıcılara dokunulmaz, sadece öncü ve takip edenlere dokunulur.
Gilles

3
@Gilles: İkinci çeyrekte, verilen üç isimden hiçbiri mutsuz IFSanlamına gelmiyor, hepsi demek IFS=.
Stéphane Gimenez

@Gilles 2. çeyrekte, aynı olduklarını söylemedim. Burada gösterildiği gibi Ve iç ayırıcılar, dokunulduğunda: IFS=' ' ; foo=( bar baz qux ) ; echo "${#foo[@]}". (Er, ne? Orada birden fazla alan sınırlayıcı olmalı, SO motoru onları soymaya devam ediyor).
Chris Down,

2
@ StéphaneGimenez, Chris: Evet, doğru, ikinci çeyrek için üzgünüm, soruyu yanlış anladım. 4. Çeyrek için bahsediyoruz read; Son değişken, son ayırıcı dışında kalan her şeyi alır ve iç ayırıcıları içeride bırakır.
Gilles

1
Gilles okuma yoluyla kaldırılmayan alanlarda kısmen doğrudur. Detaylar için cevabımı oku.

22

S1: Evet. “Alan bölme” ve “söz bölme” aynı kavram için iki terimdir.

S2: Evet. Eğer IFS(yani sonra ayarlanmazsa unset IFS), eşdeğerdir IFSolarak ayarlanan $' \t\n'(boşluk, sekme ve bir satır). Eğer IFS(yani “boş” Burada ne anlama geldiği) (yani sonra boş bir değere ayarlanır IFS=veya IFS=''veya IFS="") tüm (ve de hiçbir alan yarılma gerçekleştirilir $*normalde ilk karakterini kullanan, $IFSbir boşluk karakteri kullanır).

S3: Varsayılan IFSdavranışa sahip olmak istiyorsanız, kullanabilirsiniz unset IFS. IFSAçıkça bu varsayılan değere ayarlamak istiyorsanız , değişmez karakterleri boşluk, sekme, yeni satırı tek tırnak içine alabilirsiniz. Ksh93, bash veya zsh'da kullanabilirsiniz IFS=$' \t\n'. Taşınabilir olarak, kaynak dosyanızda değişmez bir sekme karakterine sahip olmaktan kaçınmak istiyorsanız, kullanabilirsiniz

IFS=" $(echo t | tr t \\t)
"

S4: IFSBoş bir değere ayarlandığında , sonlandırılan newline dışında tüm çizgiye read -r lineayarlar line. İle IFS=" ", satırın başındaki ve sonundaki boşluklar kırpılır. Varsayılan değerde IFS, sekmeler ve boşluklar kırpılır.


2
Q2 kısmen yanlış. Eğer IFS boş ise, "$ *" ayırıcı olmadan birleştirildi. (çünkü $@, listede olmayan bağlamlarda kabukları arasında bazı farklılıklar vardır IFS=; var=$@). IFS boş olduğunda, hiçbir kelime bölme işleminin gerçekleştirilmediği, ancak $ var hala $ var boş olduğunda ve globbing'in hala geçerli olduğu için boş değişkenler yerine boş argümanlara kadar genişlemediğine dikkat edilmelidir. globbing'i devre dışı bırak)
Stéphane Chazelas

13

S1. Alan bölme.

Alan bölme, sözcük bölme ile aynı mıdır?

Evet, ikisi de aynı fikri işaret ediyor.

S2: IFS ne zaman boş olur ?

Ayar IFS=''null ile aynı, boş bir dize ile aynı mı?

Evet, her üçü de aynı anlama geliyor: Hiçbir alan / kelime bölme işlemi gerçekleştirilmeyecek. Ayrıca, bu baskı alanlarını etkiler (olduğu gibi echo "$*") tüm alanlar boşluksuz olarak birleştirilir.

S3: (bölüm a) IFS'yi ayarlayın.

POSIX şartnamesinde aşağıdakileri okudum :

Eğer IFS ayarlanmamışsa, kabuk, IFS değeri <boşluk ><tab> <yeni satır> gibi davranır .

Hangi tam olarak eşdeğerdir:

Bir ile unset IFSkabuk, IFS varsayılanmış gibi davranır.

Bu, 'Alan bölmesinin' varsayılan bir IFS değeriyle tam olarak aynı olacağı veya ayarlanamayacağı anlamına gelir.
Bu, IFS'nin her koşulda aynı şekilde çalışacağı anlamına gelmez. Daha spesifik olmak gerekirse, çalıştırma, varsayıyı varsayılan değer yerine nullOldIFS=$IFS değerine ayarlar . Ve IFS’yi geri almaya çalışmak, bunun gibi, IFS’yi null olarak ayarlayacaktır, eskisi gibi rahatsız etmemek için. Dikkat et !!.OldIFSIFS=OldIFS

S3: (bölüm b) IFS'yi geri yükleyin.

IFS değerini varsayılan ayarlara nasıl geri getirebilirim. Varsayılan IFS değerini geri yüklemek istediğimi söyleyin. Bunu nasıl yaparım? (daha spesifik olarak, <tab> ve <newline> ' ı nasıl ifade edebilirim ?)

Zsh, ksh ve bash (AFAIK) için, IFS aşağıdaki gibi varsayılan değere ayarlanabilir:

IFS=$' \t\n'        # works with zsh, ksh, bash.

Tamam, başka bir şey okumalısın.

Ancak IFS'yi sh için yeniden ayarlamanız gerekirse, karmaşık olabilir.

En kolay şekilde tamamlayacağımız bir dezavantaj olmadan (karmaşıklık hariç) bir göz atalım.

1.-IFS'yi ayarlayın.

Biz sadece unset IFS(Yukarıdaki Q3 bölüm a.) Okuyun.

2.- Karakter değişimi.

Geçici bir çözüm olarak, tab ve newline değerlerinin değiştirilmesi, IFS değerini ayarlamayı kolaylaştırır ve ardından eşdeğer bir şekilde çalışır.

IFS'yi <boşluk ><newline> <tab> olarak ayarlayın :

sh -c 'IFS=$(echo " \n\t"); printf "%s" "$IFS"|xxd'      # Works.

3.- Basit mi? çözüm:

IFS'nin doğru ayarlanması gereken alt komut dosyaları varsa, her zaman manuel olarak yazabilirsiniz:

IFS ='   
'

Elle yazılan dizinin olduğu yer:, IFS='spacetabnewline'yukarıda doğru şekilde yazılan dizinin sırasıdır (Onaylamanız gerekiyorsa, bu yanıtı düzenleyin). Ancak tarayıcınızın kopyala / yapıştır özelliği kırılır, çünkü tarayıcı boşlukları sıkar / gizler. Kodu yukarıda yazılı şekilde paylaşmayı zorlaştırır.

4.- Komple çözüm.

Güvenli bir şekilde kopyalanabilen kodlar yazmak, genellikle kesin olarak basılabilen çıkışlardan ibarettir.

Beklenen değeri "üreten" bir koda ihtiyacımız var. Ancak, kavramsal olarak doğru olsa bile, bu kod bir iz bırakmaz \n:

sh -c 'IFS=$(echo " \t\n"); printf "%s" "$IFS"|xxd'      # wrong.

Bu, çoğu kabuk altında , genişleyen tüm yeni satırları $(...)veya `...`komut değiştirmeleri kaldırıldığı için olur.

Sh için bir numara kullanmalıyız :

sh -c 'IFS="$(printf " \t\nx")"; IFS="${IFS%x}"; printf "$IFS"|xxd'  # Correct.

Alternatif bir yol, IFS'yi bash'tan bir ortam değeri olarak ayarlamak (örneğin) ve sonra sh (IFS'yi çevre yoluyla ayarlanmasını kabul eden versiyonları) olarak ayarlamak olabilir:

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'

Kısacası, sh, IFS'yi varsayılan olarak oldukça garip bir maceraya sıfırlamayı sağlıyor.

S4: Gerçek kodda:

Son olarak, bu kod nasıl olurdu:

while IFS= read -r line
do
    echo $line
done < /path_to_text_file

ilk satırı değiştirdiğimizde davran

while read -r line # Use the default IFS value

veya:

while IFS=' ' read -r line

İlk olarak: echo $line(alıntı DEĞİLDİR) ile porpouse olup olmadığını bilmiyorum. Okunmamış olan ikinci bir 'alan bölme' seviyesini tanıtır. Yani ikisine de cevap vereceğim. :)

Bu kodla (böylece onaylayabilirsiniz). Kullanışlı xxd'ye ihtiyacınız olacak :

#!/bin/ksh
# Correctly set IFS as described above.
defIFS="$(printf " \t\nx")"; defIFS="${defIFS%x}";
IFS="$defIFS"
printf "IFS value: "
printf "%s" "$IFS"| xxd -p

a='   bar   baz   quz   '; l="${#a}"
printf "var value          : %${l}s-" "$a" ; printf "%s\n" "$a" | xxd -p

printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x--          : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf 'Values      quoted :\n' ""  # With values quoted:
printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null    quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS default quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf '%s\n' "Values unquoted :"   # Now with values unquoted:
printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x-- unquoted : "
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null  unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS defau unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

Alırım:

$ ./stackexchange-Understanding-IFS.sh
IFS value: 20090a
var value          :    bar   baz   quz   -20202062617220202062617a20202071757a2020200a
IFS --x--          :    bar   baz   quz   -20202062617220202062617a20202071757a202020
Values      quoted :
IFS null    quoted :    bar   baz   quz   -20202062617220202062617a20202071757a202020
IFS default quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS unset   quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS space   quoted :       bar   baz   quz-62617220202062617a20202071757a
Values unquoted :
IFS --x-- unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS null  unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS defau unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS unset unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS space unquoted : bar, baz, quz, 6261722c62617a2c71757a2c

İlk değer sadece doğru değerdir IFS='spacetabnewline'

Sonraki satır, var değerinin tüm altıgen değerleri $ave sonunda her okuma komutuna verilecek olan yeni bir '0a' satırıdır.

IFS'nin boş olduğu bir sonraki satır, herhangi bir 'alan ayırma' gerçekleştirmez, ancak yeni satır kaldırılır (beklendiği gibi).

Sonraki üç satır, IFS bir boşluk içerdiğinden, ilk boşlukları kaldırın ve var çizgisini kalan bakiyeye ayarlayın.

Son dört satır, alıntılanmamış bir değişkenin ne yapacağını gösterir. Değerler (birkaç) boşlukta bölünecek ve şöyle yazdırılacaktır:bar,baz,qux,


4

unset IFS IFS'nin daha sonra "\ t \ n" olduğu varsayılsa bile IFS'i temizler:

$ echo "'$IFS'"
'   
'
$ IFS=""
$ echo "'$IFS'"
''
$ unset IFS
$ echo "'$IFS'"
''
$ IFS=$' \t\n'
$ echo "'$IFS'"
'   
'
$

Aynı davranışla bash 4.2.45 ve 3.2.25 sürümlerinde test edilmiştir.


Soru hakkında konuşmak yok bağlantılı dokümantasyon unsetarasında IFSburada kabul cevap yorumlarında açıkladığı gibi,.
ILMostro_7 10:18
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.