$ IFS değişkenini “yedeklemek” akıllıca bir yaklaşım mıdır?


19

Ben her zaman ile $IFSuğraşmak için gerçekten tereddüt ediyorum çünkü küresel bir ürkütücü.

Ancak çoğu zaman, bir dizgiyi bir bash dizisine yükleme işlemini hoş ve özlü hale getirir ve bash komut dosyası oluşturma için, özlü olması zor olur.

Bu yüzden $IFSbaşka bir değişkenin başlangıç ​​içeriğini "kaydetmeye" ve sonra bir $IFSşey için kullanarak bitirdikten hemen sonra geri yüklemek çalışırsanız hiçbir şey daha iyi olabilir anlamaya .

Bu pratik mi? Yoksa aslında anlamsız mı ve doğrudan IFSsonraki kullanımları için ne gerekiyorsa ona geri dönmeliyim?


Neden pratik olmaz?
Bratchley

Çünkü IFS'nin ayarlanması işi iyi yapar.
llua

1
Ayarsız IFS'nin iyi çalışacağını söyleyenler için durumsal olduğunu unutmayın: stackoverflow.com/questions/39545837/… . Deneyimlerime göre, IFS'yi kabuk yorumlayıcınız için manuel olarak varsayılan olarak ayarlamak, yani $' \t\n'bash kullanıyorsanız en iyisidir . unset $IFSher zaman varsayılan olmasını beklediğiniz şekilde geri yüklemez.
Darrel Holt

Yanıtlar:


9

Gerektiğinde IFS'ye kaydedebilir ve atayabilirsiniz. Bunu yapmakta yanlış bir şey yok. Dizi atama örneğiniz gibi, geçici, hızlı bir modifikasyonun ardından restorasyon değerini kaydetmek nadir değildir.

@Llua, sorunuza yaptığı yorumda belirtildiği gibi, IFS'nin ayarının kaldırılması, boşluk sekmesi-satırsonu atamaya eşdeğer olan varsayılan davranışı geri yükleyecektir.

O daha sorunlu olabilir nasıl dikkate It yetmeyecek değil bunu yapmak için daha açık bir şekilde ayarlanabilir / tanımsız IFS.

POSIX 2013 sürümünden, 2.5.3 Kabuk Değişkenleri :

Uygulamalar, kabuk çağrıldığında ortamdaki IFS'nin değerini veya ortamdan IFS'nin yokluğunu göz ardı edebilir; bu durumda kabuk, çağrıldığında IFS'yi <boşluk> <tab> <newline> olarak ayarlayacaktır. .

POSIX uyumlu, çağrılan bir kabuk IFS'yi ortamından devralabilir veya devralmayabilir. Bundan:

  • Taşınabilir bir komut dosyası, ortam üzerinden IFS'yi güvenilir bir şekilde devralamaz.
  • Yalnızca varsayılan bölme davranışını kullanmayı amaçlayan (veya durumda katılma "$*") ancak IFS'yi ortamdan başlatan bir kabuk altında çalışabilen bir komut dosyasının, IFS'yi çevresel saldırılara karşı savunmak için açıkça ayarlaması / ayarlaması gerekir.

Not: Bu tartışma için "çağrılan" kelimesinin özel bir anlamı olduğunu anlamak önemlidir. Bir kabuk sadece ismini kullanarak açıkça adlandırıldığında (bir #!/path/to/shellshebang dahil ) çağrılır . Alt kabuk - $(...)veya tarafından oluşturulabileceği gibi cmd1 || cmd2 &- çağrılan bir kabuk değildir ve IFS (yürütme ortamının çoğuyla birlikte) ebeveynininkiyle aynıdır. Çağrılan bir kabuk, değerini kabuklarına ayarlarken, $alt kabuklar onu devralır.


Bu sadece bilgiçliksel bir keşif değildir; bu alanda gerçek farklılıklar var. İşte senaryoyu birkaç farklı kabuk kullanarak test eden kısa bir komut dosyası. Değiştirilmiş bir IFS'yi (olarak ayarlanmış :) çağrılan bir kabuğa verir ve daha sonra varsayılan IFS'yi yazdırır.

$ cat export-IFS.sh
export IFS=:
for sh in bash ksh93 mksh dash busybox:sh; do
    printf '\n%s\n' "$sh"
    $sh -c 'printf %s "$IFS"' | hexdump -C
done

IFS genellikle dışa aktarma için işaretlenmez, ancak öyleyse bash, ksh93 ve mksh'nin ortamlarını nasıl yok saydığına dikkat edin IFS=:, çizgi ve meşgul kutusu onurlandırır.

$ sh export-IFS.sh

bash
00000000  20 09 0a                                          | ..|
00000003

ksh93
00000000  20 09 0a                                          | ..|
00000003

mksh
00000000  20 09 0a                                          | ..|
00000003

dash
00000000  3a                                                |:|
00000001

busybox:sh
00000000  3a                                                |:|
00000001

Bazı sürüm bilgileri:

bash: GNU bash, version 4.3.11(1)-release
ksh93: sh (AT&T Research) 93u+ 2012-08-01
mksh: KSH_VERSION='@(#)MIRBSD KSH R46 2013/05/02'
dash: 0.5.7
busybox: BusyBox v1.21.1

Bash, ksh93 ve mksh, IFS'yi ortamdan başlatmasalar da, değiştirilmiş IFS'lerini yeniden dışa aktarırlar.

Herhangi bir nedenden ötürü IFS'yi çevre yoluyla portatif olarak geçirmeniz gerekiyorsa, IFS'nin kendisini kullanarak bunu yapamazsınız; değeri farklı bir değişkene atamanız ve bu değişkeni dışa aktarma için işaretlemeniz gerekir. Çocukların bu değeri IFS'lerine açıkça atamaları gerekecektir.


Görüyorum ki, eğer ifade edersem, kullanılacak olan çoğu durumda değeri açıkça belirtmek tartışmasız daha taşınabilirdir IFSve bu nedenle orijinal değerini "koruma" girişiminde bulunmak genellikle çok verimli değildir.
Steven Lu

1
En önemli sorun, komut dosyanız IFS kullanıyorsa, değerinin olmasını istediğiniz şey olduğundan emin olmak için IFS'yi açıkça ayarlaması / ayarını kaldırmasıdır. Tipik olarak, herhangi bir sıralanmamış parametre genişletmesi, sıralanmamış komut ikamesi, sıralanmamış aritmetik genişletmeler, reads veya çift tırnaklı referanslar varsa komut dosyanızın davranışı IFS'ye bağlıdır $*. Bu liste kafamın üstünde değil, bu yüzden kapsamlı olmayabilir (özellikle modern kabukların POSIX uzantılarını düşünürken).
Barefoot IO

10

Genel olarak, koşulları varsayılana döndürmek iyi bir uygulamadır.

Ancak, bu durumda, çok fazla değil.

Neden?:

Ayrıca, IFS değerinin depolanmasında bir sorun vardır.
Orijinal IFS ayarlanmamışsa, kod IFS="$OldIFS"IFS olarak ayarlanır "", ayarlanmaz.

IFS'nin değerini (ayarlanmamış olsa bile) gerçekte tutmak için şunu kullanın:

${IFS+"false"} && unset oldifs || oldifs="$IFS"    # correctly store IFS.

IFS="error"                 ### change and use IFS as needed.

${oldifs+"false"} && unset IFS || IFS="$oldifs"    # restore IFS.

IFS gerçekten ayarlanamaz. Ayarlamayı kaldırırsanız, kabuk varsayılan değere geri döner. Yani bunu kaydederken gerçekten kontrol etmeniz gerekmez.
filbranden

Dikkat ki bash, unset IFSbunun bir üst bağlam (işlev bağlamında) yerel ve olmayan geçerli bağlamda ilan edilmiş olsaydı tanımsız IFS başarısız olur.
Stéphane Chazelas

5

Global bir dünyayı terketme konusunda tereddüt etme hakkınız var. Korkma, gerçek küresel değişikliği değiştirmeden IFSveya hantal ve hataya eğilimli bir kaydetme / geri yükleme dansı yapmadan temiz çalışma kodu yazmak mümkündür .

Yapabilirsin:

  • IFS'yi tek bir çağrı için ayarlayın:

    IFS=value command_or_function

    veya

  • IFS'yi bir alt kabuğun içine yerleştirin:

    (IFS=value; statement)
    $(IFS=value; statement)

Örnekler

  • Bir diziden virgülle ayrılmış bir dize elde etmek için:

    str="$(IFS=, ; echo "${array[*]-}")"

    Not: -Boş bir diziyi yalnızca ayarlanmadığında varsayılan bir değerset -u sağlayarak korumaktır (bu değer bu durumda boş dizedir) .

    IFSModifikasyonu tarafından kökenli alt dış kabuğunun içinde geçerlidir $() komut ikamesi . Bunun nedeni, alt kabukların çağıran kabuğun değişkenlerinin kopyalarına sahip olmasıdır ve bu nedenle değerlerini okuyabilir, ancak alt kabuk tarafından yapılan herhangi bir değişiklik, alt kabuğun kopyasını etkiler, üst değişkenini etkilemez.

    Ayrıca düşünüyor olabilirsiniz: neden alt kabuğu atlamıyorsunuz ve sadece bunu yapmıyorsunuz:

    IFS=, str="${array[*]-}"  # Don't do this!

    Burada komut çağırma yoktur ve bu satır, sanki sanki birbirini izleyen iki bağımsız değişken ataması olarak yorumlanır:

    IFS=,                     # Oops, global IFS was modified
    str="${array[*]-}"

    Son olarak, bu varyantın neden çalışmadığını açıklayalım:

    # Notice missing ';' before echo
    str="$(IFS=, echo "${array[*]-}")" # Don't do this! 

    echoKomut gerçekten onun ile adı verilecek IFSdeğişken seti ,, ancak echobakım veya kullanım vermez IFS. "${array[*]}"Bir dizgeye genişleme büyüsü echo, daha önce çağrılmadan önce (alt-) kabuğunun kendisi tarafından yapılır .

  • Tüm dosyayı ( NULLbayt içermeyen ) tek bir değişkene okumak için VAR:

    IFS= read -r -d '' VAR < "${filepath}"

    Not: ve IFS'yi boş dizeye ayarlayan ile IFS=aynıdır; bu, aşağıdakilerden çok farklıdır : ayarlanmazsa, dahili olarak kullanılan tüm bash işlevlerinin davranışı , varsayılan değerinin aynısıdır .IFS=""IFS=''unset IFSIFSIFSIFS$' \t\n'

    IFSBoş dizginin ayarlanması , ön ve arka boşlukların korunmasını sağlar.

    -d ''Veya -d ""sadece üzerinde bugünkü çağırmayı durdurmak için okunan anlatıyor NULLyerine zamanki yeni satır, byte.

  • Sınırlayıcıları $PATHboyunca bölmek için ::

    IFS=":" read -r -d '' -a paths <<< "$PATH"

    Bu örnek tamamen açıklayıcıdır. Bir sınırlayıcı boyunca bölündüğünüz genel durumda, münferit alanların bu sınırlayıcıyı içermesi (kaçan bir sürümü) olabilir. .csvSütunları virgül içerebilecek (bir şekilde kaçan veya alıntılanan) bir dosya satırını okumaya çalışmayı düşünün . Yukarıdaki pasaj bu gibi durumlarda amaçlandığı gibi çalışmaz.

    Bununla birlikte, :içinde böyle -içeren yollar ile karşılaşmanız pek olası değildir $PATH. UNIX / Linux yol adlarının a içermesine izin verilirken :, $PATHkaçan / alıntılanmış sütunları ayrıştıracak kod olmadığından, bash bu yollara zaten ekleyemez ve yürütülebilir dosyaları depolamaya çalışırsanız bu yolları kullanamaz gibi görünüyor. : bash'ın kaynak kodu 4.4 .

    Son olarak, snippet'in sonuç dizisinin son öğesine bir son satır eklediğini (şimdi silinmiş yorumlarda @ StéphaneChazelas tarafından çağrıldığı gibi) ve girişin boş dize olması durumunda çıktının tek bir öğe olacağını unutmayın. dizi, burada eleman bir satırsonu ( $'\n') içerecektir .

Motivasyon

old_IFS="${IFS}"; command; IFS="${old_IFS}"Dünyaya dokunan temel yaklaşım, IFSen basit senaryolar için beklendiği gibi çalışacaktır. Bununla birlikte, herhangi bir karmaşıklık eklediğinizde, kolayca ayrılabilir ve ince sorunlara neden olabilir:

  • Eğer commandküresel değiştiren bir bash fonksiyonudur IFS(doğrudan veya yerine getirmekte içerde bir başka fonksiyon, görünümden gizli) ve böylece yanlışlıkla yaparken aynı küresel kullanır old_IFSrestore / kaydetme yapmak değişkeni çıkmadan hata olsun.
  • @Gilles tarafından yapılan bu yorumda belirtildiği gibi , orijinal durumu IFSayarlanmamışsa, naif kaydet ve geri yükle çalışmaz ve hatta yaygın olarak kullanılan set -u( yanlış set -o nounset) kabuk seçeneği kullanılırsa bile doğrudan başarısızlıklara neden olur. yürürlüktedir.
  • Bazı kabuk kodlarının, sinyal işleyicileri gibi, ana yürütme akışıyla eşzamansız olarak yürütülmesi mümkündür (bkz. help trap). Bu kod ayrıca genel değişikliği yapar IFSveya belirli bir değere sahip olduğunu varsayarsa, küçük hatalar alabilirsiniz.

Bu sorunların bir kısmını veya tümünü önlemek için daha sağlam bir kaydetme / geri yükleme dizisi ( bu diğer cevapta önerilen gibi) oluşturabilirsiniz.Ancak , geçici olarak bir özelliğe ihtiyaç duyduğunuz her yerde bu gürültülü kazan plakası kodunu tekrarlamanız gerekir IFS. kod okunabilirliğini ve sürdürülebilirliğini azaltır.

Kütüphane benzeri komut dosyaları için dikkate alınması gereken ek noktalar

IFSözellikle IFS, kodlayıcıları tarafından çağrılan küresel durumdan ( , kabuk seçeneklerinden, ...) bağımsız olarak ve bu durumu hiç rahatsız etmeden (çalışanların güvenebileceği gibi) sağlam bir şekilde çalışmasını sağlaması gereken kabuk işlev kütüphanelerinin yazarları için bir endişe kaynağı her zaman statik kalması için).

Kütüphane kodu yazarken, IFSbelirli bir değere (varsayılan değer bile değil) veya hatta ayarlanmaya güvenemezsiniz . Bunun yerine, IFSdavranışı bağlı olan herhangi bir snippet'i açıkça ayarlamanız gerekir IFS.

Eğer IFSaçıkça gerekli değeri değeri etkisini yerelleştirilmesine uygun olan bu yanıt açıklandığı iki mekanizmadan hangisi kullanılarak konularda kodun her satırında (varsayılan olmak olur bile) olarak ayarlandığında, daha sonra kod hem küresel devletten bağımsız olarak toparlanmaktan kaçınır. Bu yaklaşım, IFSasgari metin maliyetiyle (en temel kaydetme / geri yükleme ile karşılaştırıldığında) tam olarak bu bir komut / genişletme için önemli olan komut dosyasını okuyan bir kişiyi çok açık hale getirmenin avantajına sahiptir .

IFSZaten hangi kod etkileniyor ?

Neyse ki, IFSönemli olan pek çok senaryo yoktur ( her zaman genişlemelerinizi alıntıladığınızı varsayarsak ):

  • "$*"ve "${array[*]}"genişlemeler
  • arasında çağrıları readdahili birden çok değişken (hedef read VAR1 VAR2 VAR3) veya bir dizi değişken ( read -a ARRAY_VAR_NAME)
  • readortaya çıkan boşluk / boşluk veya boşluk olmayan karakterler söz konusu olduğunda tek bir değişkeni hedefleme çağrıları IFS.
  • kelime bölme ( veba gibi kaçınmak isteyebileceğiniz alıntılanmamış açılımlar gibi )
  • bazı daha az yaygın senaryolar (Bkz: IFS @ Greg's Wiki )

Bileşenlerin hiçbirinin a: kendilerini cümle içerdiğini varsayarak $ PATH'ı onun boyunca ayırmak için anlayamıyorum : sınırlayıcılar . Sınırlayıcı :ne zaman bileşenler içerebilir :?
Stéphane Chazelas

@ StéphaneChazelas Pek :çok UNIX / Linux dosya sisteminde dosya adında kullanılacak geçerli bir karakter olduğundan, ad içeren bir dizine sahip olmak tamamen mümkündür :. Belki bazı kabukları kaçmak için bir hüküm var :gibi bir şey kullanarak PATH \:ve sonra sütunlar gerçek sınırlayıcı olmadığını görünen görecekti (O bash böyle kaçışa izin vermiyor gibi görünüyor. Düşük seviyeli fonksiyon kullanıldığında ilerlerken üzerinde $PATHsadece aramalar için :de a C dizesi: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).
sls

Cevabı yarma $PATHörneğini :daha net hale getirmek için gözden geçirdim .
sls

1
SO hoş geldiniz! Böyle derin bir cevap için teşekkürler :)
Steven Lu

1

Bu pratik mi? Yoksa aslında anlamsız mı ve doğrudan IFS'yi sonraki kullanımları için olması gereken her şeye geri ayarlamalıyım?

Tek $' \t\n'yapmanız gereken IFS yazım ayarını yapmak için tek yapmanız gereken

OIFS=$IFS
do_your_thing
IFS=$OIFS

Alternatif olarak, içinde ayarlanmış / değiştirilmiş herhangi bir değişkene ihtiyacınız yoksa bir alt kabuğu çağırabilirsiniz:

( IFS=:; do_your_thing; )

Bu tehlikelidir çünkü IFSbaşlangıçta ayarlanmamışsa çalışmaz .
Gilles 'SO- kötü olmayı bırak'
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.