Shell betiğim neden boşlukta veya diğer özel karakterlerde boğuluyor?


284

Veya, sağlam dosya adı işlemesi ve kabuk komut dosyalarından geçen diğer dizeler için tanıtım rehberi.

Çoğu zaman işe yarayan bir kabuk betiği yazdım. Ancak bazı girdilerde boğulur (örneğin, bazı dosya adlarında).

Aşağıdaki gibi bir sorunla karşılaştım:

  • Ben bir boşluk içeren bir dosya adı var hello worldve bu iki ayrı dosyalar olarak tedavi edildi hellove world.
  • İki ardışık boşluğa sahip bir giriş hattım var ve bunlar girişte birine küçüldü.
  • Önde gelen ve arkadaki boşluk, giriş satırlarından kayboluyor.
  • Bazen girdi karakterlerden birini içerdiğinde \[*?, bunlar aslında dosya adı olan bir metinle değiştirilir.
  • Girişte kesme işareti '(veya çift tırnak ") vardır ve bu noktadan sonra işler garipleşir.
  • Girişte ters eğik çizgi var (veya: Cygwin kullanıyorum ve dosya dosyalarımın bazılarında Windows stili \ayırıcılar var).

Neler oluyor ve nasıl düzeltebilirim?


16
shellcheckprogramlarınızın kalitesini yükseltmenize yardımcı olur.
aurelien

3
Cevaplarda açıklanan koruyucu tekniklerin yanı sıra çoğu okuyucunun büyük olasılıkla açık olmasına rağmen, dosyaların komut satırı araçları kullanılarak işlenmesi amaçlandığında, fantezi karakterlerinden kaçınmanın iyi bir uygulama olduğunu düşünüyorum. mümkünse ilk etapta isimleri.
bli


1
@bli Hayır, bu sadece böceklerin ortaya çıkmasının daha uzun sürmesini sağlar. Bugün böcekleri saklıyor. Ve şimdi, daha sonra kodunuzla birlikte kullanılan tüm dosya adlarını bilmiyorsunuz.
Volker Siegel

Öncelikle, eğer parametrelerinizde boşluklar varsa, o zaman girilmeden alıntı yapılmaları gerekir (komut satırında). Ancak tüm komut satırını alıp kendiniz ayrıştırabilirsiniz. İki boşluk bir boşluğa dönüşmez; Herhangi bir boşluk miktarı betiğinize bir sonraki değişken olduğunu söyler, böylece "echo $ 1 $ 2" gibi bir şey yaparsanız, aralarında bir boşluk bırakarak betiğiniz o olur. Ayrıca for döngüsü yerine boşluk içeren dosyalar üzerinde yineleme yapmak için "find (-exec)" komutunu da kullanın; boşluklarla daha kolay başa çıkabilirsin.
Patrick Taylor,

Yanıtlar:


352

Her zaman değişken değiştirmeler ve komut değiştirmelerin çift tırnak kullanın: "$foo","$(foo)"

Eğer $fooalıntılanmamış kullanıyorsanız, betiğiniz girdi veya parametreler (veya komut çıktısı ile birlikte $(foo)) boşluk veya boğulacak \[*?.

Orada okumayı bırakabilirsin. Tamam, işte birkaç tane daha:

  • read- Giriş satırını readyerleşik yapıyla satır satır okumak içinwhile IFS= read -r line; do …
    Plain readters eğik çizgi ve boşlukları özel olarak kullanın.
  • xargs- Kaçınınxargs . Eğer kullanmak zorundaysan xargs, bunu yap xargs -0. Yerine find … | xargs, tercihfind … -exec … .
    xargsboşluk ve karakterleri \"'özel olarak ele alır .

Bu cevap Bourne / POSIX tarzı kabukları (uygulanır sh, ash, dash, bash, ksh, mksh, yash...). Zsh kullanıcıları bunu atlamalı ve Çift alıntı ne zaman gereklidir? yerine. Nitty-gritty'nin tamamını istiyorsanız, standardı veya kabuğunuzun kullanım kılavuzunu okuyun.


Aşağıdaki açıklamaların birkaç yaklaşım içerdiğini unutmayın (çoğu durumda doğru olan ifadeler, ancak çevre bağlamdan veya yapılandırmadan etkilenebilir).

Neden yazmam gerekiyor "$foo"? Tırnaklar olmadan ne olur?

$foo“Değişkenin değerini al” anlamına gelmez foo. Çok daha karmaşık bir şey demektir:

  • İlk önce değişkenin değerini alın.
  • Alan bölme: bu değeri boşlukla ayrılmış alan listesi olarak ele alın ve elde edilen listeyi oluşturun. Değişken içeriyorsa, örneğin, foo * bar ​bu aşamanın sonucu 3-eleman listesi foo, *, bar.
  • Dosya adı oluşturma: Her alanı bir küreye, örneğin bir joker kalıbı olarak kabul edin ve bu kalıba uyan dosya adları listesi ile değiştirin. Desen herhangi bir dosyayla eşleşmezse, değiştirilmemiş olarak bırakılır. Örneğimizde bu foo, geçerli dizindeki dosyaların listesini izleyen ve son olarak içeren listeyle sonuçlanır bar. Geçerli dizin boşsa, sonuç foo, *, bar.

Sonuç, dizelerin bir listesidir. Kabuk sözdiziminde iki bağlam vardır: list bağlamı ve string içeriği. Alan bölme ve dosya adı oluşturma yalnızca liste bağlamında gerçekleşir, ancak çoğu zaman bu olur. Çift tırnak, bir dize bağlamını sınırlar: çift tırnaklı dizenin tamamı bölünmeyecek tek bir dizedir. (İstisna: "$@"konumsal parametreler listesine genişletmek için, örneğin üç konumsal parametre varsa buna "$@"eşdeğerdir "$1" "$2" "$3". Bkz. $ * Ve $ @ arasındaki fark nedir? )

Aynısı $(foo)veya ile ikame komutuna olur `foo`. Bir yandan, kullanmayın `foo`: alıntı kuralları tuhaf ve taşınabilirdir ve tüm modern kabuklar $(foo), sezgisel alıntı kurallarına sahip olması dışında kesinlikle eşdeğerdir.

Aritmetik ikamenin çıktısı da aynı genişlemelere maruz kalır, ancak normalde yalnızca genişletilemeyen karakterler içerdiğinden endişe IFSduymaz ( rakamlar içermez veya -).

Bkz . Çift alıntı ne zaman gereklidir? Teklifleri dışarıda bırakabileceğiniz durumlar hakkında daha fazla bilgi için

Tüm bu rigmarollerin gerçekleşmesini kastetmediğin sürece, değişken ve komut değişimlerinin etrafında her zaman çift tırnak kullanmayı unutma. Dikkatli olun: tırnak işaretleri dışında bırakmak sadece hatalara değil güvenlik deliklerine de yol açabilir .

Dosya adlarının bir listesini nasıl işlerim?

myfiles="file1 file2"Dosyaları ayırmak için boşluklarla yazarsanız , boşluk içeren dosya adlarıyla çalışamazsınız. Unix dosya adları /(her zaman bir dizin ayırıcı olan) ve boş bayt (çoğu kabuklu kabuk komut dosyalarında kullanamayacağınız) dışında herhangi bir karakter içerebilir .

Aynı sorun myfiles=*.txt; … process $myfiles. Bunu yaptığınızda, değişken myfiles5-karakter dizesi içeren *.txtve yazarken bu kadar $myfilesjoker genişletilmiş olması. Bu örnek aslında siz komut dosyasını değiştirinceye kadar çalışacaktır myfiles="$someprefix*.txt"; … process $myfiles. Eğer someprefixayarlanmışsa final report, bu işe yaramaz.

Herhangi bir türün (dosya adları gibi) işlenmesi için, bir diziye yerleştirin. Bu, mksh, ksh93, yash veya bash (ya da tüm bu alıntılama konularına sahip olmayan zsh) gerektirir; Düz bir POSIX kabuğu (kül veya çizgi gibi) dizi değişkenlerine sahip değildir.

myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"

Ksh88 farklı atama sözdizimine sahip dizi değişkenlerine sahiptir set -A myfiles "someprefix"*.txt( ksh88 / bash taşınabilirliğine ihtiyacınız varsa farklı ksh ortamı altındaki atama değişkenine bakınız ). Bourne / POSIX stili mermilerin tek bir dizisi vardır, "$@"ayarladığınız setve bir işleve yerel olan konumsal parametre dizisi :

set -- "$someprefix"*.txt
process -- "$@"

Peki ya başlayan dosya isimleri -?

İlgili bir notta, dosya adlarının -çoğu komutun bir seçeneği ifade ettiği şeklinde yorumladığı bir (kısa çizgi / eksi) ile başlayabileceğini unutmayın . Değişken bir bölümle başlayan bir dosya adınız --varsa, yukarıdaki snippet'te olduğu gibi bundan önce geçtiğinizden emin olun . Bu, komutun seçeneklerin sonuna ulaştığını gösterir, bundan sonra herhangi bir şey, başlasa bile bir dosya adıdır -.

Alternatif olarak, dosya adlarınızın başka bir karakterle başladığından emin olabilirsiniz -. Mutlak dosya adları ile başlar /ve ./göreli adların başına ekleyebilirsiniz . Aşağıdaki kod parçası, değişkenin içeriğini, fbaşlamaması garanti edilen aynı dosyaya gönderme yapmanın “güvenli” bir yoluna dönüştürür -.

case "$f" in -*) "f=./$f";; esac

Bu konuyla ilgili son bir notta, bazı komutların -, sonrasında bile standart girdi veya standart çıktı olarak yorumlandığına dikkat edin --. Adında gerçek bir dosyaya başvurmanız gerekiyorsa -veya böyle bir programı çağırıyorsanız ve stdin'den okumak veya stdout'a yazmak istemiyorsanız -, yukarıdaki gibi tekrar yazdığınızdan emin olun . Bkz. "Du -sh *" ve "du -sh ./*" arasındaki fark nedir? daha fazla tartışma için.

Bir değişkeni bir komutu nasıl saklarım?

“Komut” üç şey anlamına gelebilir: bir komut adı (tam yollu veya tam yolu olmayan yürütülebilir bir ad veya bir işlev adı, yerleşik veya diğer ad), bağımsız değişkenli bir komut adı veya bir kabuk kodu parçası. Bunları değişkende saklamanın farklı yolları vardır.

Bir komut adınız varsa, sadece saklayın ve değişkeni her zamanki gibi çift tırnaklı kullanın.

command_path="$1"

"$command_path" --option --message="hello world"

Argümanları olan bir komutunuz varsa, sorun yukarıdaki dosya adlarının listesiyle aynıdır: bu bir string değil, bir string listesidir. Argümanları yalnızca aralarında boşluk olan tek bir dizgeye dolduramazsınız, çünkü bunu yaparsanız argümanların parçası olan ve argümanları ayıran boşluklar arasındaki farkı söyleyemezsiniz. Eğer kabuğunuzda diziler varsa, onları kullanabilirsiniz.

cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

Ya dizileri olmayan bir kabuk kullanıyorsanız? Bunları değiştirmek sakıncası yoksa, pozisyon parametrelerini kullanabilirsiniz.

set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"

Ya yönlendirmeler, borular vb. Gibi karmaşık bir kabuk komutunu saklamanız gerekirse? Veya konumsal parametreleri değiştirmek istemiyorsanız? Sonra komutu içeren bir dize oluşturabilir ve evalyerleşimi kullanabilirsiniz .

code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"

İçindeki tırnakların tanımındaki notlara dikkat edin code: tekli tırnaklar '…'bir dize değişmezini sınırlandırır, böylece değişkenin değeri codedizedir /path/to/executable --option --message="hello world" -- /path/to/file1. evalBuiltin Senaryoda çıktı sanki bir argüman olarak geçirilen dize ayrıştırmak kabuk söyler, böylece bu noktada tırnak ve boru vb işlendiğinden

Kullanımı evalzor. Ne zaman ayrıştırılacağını dikkatlice düşünün. Özellikle, bir dosya adını koda yazamazsınız: bir kaynak kod dosyasında olduğu gibi alıntı yapmanız gerekir. Bunu yapmanın doğrudan yolu yok. Gibi bir şey code="$code $filename"dosya adı herhangi kabuk özel karakter içeriyorsa sonları (boşluk, $, ;, |, <, >, vb.) code="$code \"$filename\""Hala kırılıyor "$\`. code="$code '$filename'"Dosya adı a içeriyorsa, sonları bile '. İki çözüm var.

  • Dosya adının etrafına bir tırnak katmanı ekleyin. Bunu yapmanın en kolay yolu, etrafına tek tırnak işaretleri eklemek ve tek tırnak işaretleri yerine kullanmaktır '\''.

    quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
    code="$code '${quoted_filename%.}'"
    
  • Değişken genişlemesini kodun içinde tutun, böylece kod parçası oluşturulduğunda değil, kod değerlendirildiğinde aranır. Bu daha basittir ancak yalnızca değişken kodun yürütüldüğü sırada hala aynı değere sahipse çalışır, örneğin kod bir döngüde oluşturulmuşsa değil.

    code="$code \"\$filename\""

Son olarak, gerçekten kod içeren bir değişkene ihtiyacınız var mı? Bir kod bloğuna isim vermenin en doğal yolu bir işlev tanımlamaktır.

Neyin var read?

Olmadan -r, readdevam satırlarına izin verir - bu tek bir mantıksal girdi satırıdır:

hello \
world

readgiriş satırını, karakterlerle sınırlandırılmış alanlara böler $IFS(olmadan -r, ters eğik çizgi de bunlardan kaçınır). Örneğin, giriş üç kelimelik bir çizgi ise, girişin ilk kelimesine , ikinci kelimeye ve üçüncü kelimeye read first second thirdayarlanır . Daha fazla kelime varsa, son değişken öncekileri ayarladıktan sonra geriye kalan her şeyi içerir. Lider ve takip eden boşluklar kırpılıyor.firstsecondthird

IFSBoş dizeye ayarlamak herhangi bir kırpmayı önler. Bkz. Neden “` IFS = read` `, IFS = yerine; okurken ... daha uzun bir açıklama için.

Neyin var xargs?

Giriş biçimi xargs, isteğe bağlı olarak tek veya çift tırnaklı olabilen, boşlukla ayrılmış dizelerdir. Bu formatta hiçbir standart araç çıkmamaktadır.

Girdi xargs -L1veya xargs -lneredeyse bir çizgi listesidir, ancak tam olarak değil - bir satırın sonunda boşluk varsa, aşağıdaki satır bir devam satırıdır.

Uygun olan xargs -0yerlerde kullanabilirsiniz (ve mümkün olan yerlerde: GNU (Linux, Cygwin), BusyBox, BSD, OSX, ancak POSIX’de değildir). Bu güvenlidir, çünkü boş baytlar çoğu veride, özellikle de dosya adlarında görünemez. Boş bir dosya adı listesi oluşturmak için, kullanın find … -print0(veya find … -exec …aşağıda açıklandığı şekilde kullanabilirsiniz ).

Tarafından bulunan dosyaları nasıl işlerim find?

find  -exec some_command a_parameter another_parameter {} +

some_commandharici bir komut olması gerekir, bir kabuk işlevi veya diğer adı olamaz. Dosyaları işlemek için bir kabuk çağırmanız gerekirse, shaçıkça arayın .

find  -exec sh -c '
  for x do
    … # process the file "$x"
  done
' find-sh {} +

Başka bir sorum var

Bu sitedeki etiketine veya veya göz atın . (Bazı genel ipuçlarını ve el ile seçilen bir ortak sorular listesini görmek için “daha ​​fazla bilgi…” seçeneğine tıklayın.) Eğer aradıysanız ve bir cevap bulamadıysanız, sorun .


6
@ John1024 Bu sadece bir GNU özelliği, bu yüzden “standart alet yok” diyeceğim.
Gilles

2
Ayrıca, yaklaşık tırnak ihtiyaç $(( ... ))(ayrıca $[...]hariç bazı kabuklarda) zsh(hatta sh öykünmesinde) ve mksh.
Stéphane Chazelas

3
xargs -0POSIX olmadığını unutmayın . FreeBSD dışında xargs, genellikle xargs -r0yerine istiyorum xargs -0.
Stéphane Chazelas

2
@ John1024, hayır, ls --quoting-style=shell-alwaysile uyumlu değil xargs. Deneyintouch $'a\nb'; ls --quoting-style=shell-always | xargs
Stéphane Chazelas

3
Başka bir güzel (GNU okunur) özelliğidir xargs -d "\n"Eğer örneğin çalıştırmak böylece locate PATTERN1 |xargs -d "\n" grep PATTERN2eşleşen dosya adlarını aramak için pattern1 içerik eşleme ile ÖRNEK2 . GNU'suz, örneğin, şöyle yapabilirsinizlocate PATTERN1 |perl -pne 's/\n/\0/' |xargs -0 grep PATTERN1
Adam Katz,

26

Gilles cevabı mükemmel olsa da, onun ana noktasında sorunu alıyorum

Her zaman değişken değişkenler ve komut değişkenleri etrafında çift tırnak kullanın: "$ foo", "$ (foo)"

Kelime bölme işlemi yapan Bash benzeri bir kabukla başladığınızda, elbette ki evet, güvenli bir tavsiye her zaman tırnak işareti kullanmaktır. Ancak sözcük bölme her zaman gerçekleştirilmez

§ Kelime Bölme

Bu komutlar hatasız çalıştırılabilir

foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
case $foo in bar) echo bar ;; baz) echo baz ;; esac

Kullanıcıları bu davranışı benimsemeye teşvik etmiyorum, ancak biri kelime bölmenin ne zaman gerçekleştiğini kesin olarak anlarsa, ne zaman tırnak kullanacaklarına karar verebilmeliler.


19
Benim cevap söz gibi görmek unix.stackexchange.com/questions/68694/... detaylar için. “Kabuk betiğim neden boğuluyor?” Sorusunu fark etmiyor. En sık karşılaşılan sorun (bu sitede ve başka yerlerde yılların deneyiminden) çift tırnak işareti eksik. “Daima çift tırnak işareti kullanın”, hatırlanması, “gerekmediği durumlar dışında, daima çift tırnak işareti kullanın” dan daha kolaydır.
Gilles

14
Yeni başlayanlar için kuralları anlamak zordur. Örneğin foo=$bar, tamam, ama export foo=$barya env foo=$varda değil (en azından bazı mermilerde). Yeni başlayanlar için bir tavsiye: Ne yaptığınızı bilmiyorsanız ve yapmamak için iyi bir nedeniniz olmadıkça değişkenlerinizi daima belirtin .
Stéphane Chazelas

5
@StevenPenny Gerçekten daha doğru mu? Tırnakların betiği kırabileceği makul durumlar var mı? Durumlarda yarım durumlarda tırnak nerede gerekir kullanılabilir ve diğer yarısı tırnak olabilir isteğe kullanılacak - o zaman bir öneri doğru, basit ve daha az riskli olduğundan, düşünülmelidir biridir "her zaman, her ihtimale tırnak kullanabilirsiniz". Bu tür istisna listelerini yeni başlayanlara öğretmenin, gerekli / gereksiz alıntıları karıştıracağı, senaryolarını kıracağı ve daha fazla bilgi edinmek için motive ettiği için etkisiz (bağlamdan yoksun, onları hatırlamayacaklar) ve karşı üretken olduğu iyi bilinmektedir.
Peteris

6
Benim 0.02 $, her şeyi teklif etmeyi önermek iyi bir tavsiyedir. Yanlışlıkla ihtiyaç duymayan bir şeyi alıntılamak zararsızdır, yanlışlıkla ihtiyaç duyacak bir şeyi teklif etmekte başarısız olur. Bu nedenle, tam olarak kelime bölmenin gerçekleştiği zamanın karmaşıklıklarını asla anlamayacak olan kabuk senaryo yazarlarının çoğunluğu için, her şeyi alıntı yapmak, yalnızca gereken yerlerde teklif vermeye çalışmaktan çok daha güvenlidir.
godlygeek

5
@Peteris ve godlygeek: "Alıntıların senaryoyu kırabileceği makul durumlar var mı?" Bu sizin "makul" tanımınıza bağlıdır. Eğer bir komut dosyası belirlenirse criteria="-type f", o zaman find . $criteriaçalışır ama find . "$criteria"olmaz.
G-Man

22

Bildiğim kadarıyla, genişlemelerin iki katına çıkarılmasının gerekli olduğu sadece iki durum var ve bu durumlar iki özel kabuk parametresini içeriyor "$@"ve "$*"- çift tırnak içine alındığında farklı şekilde genişletilmesi belirtilen. Diğer tüm durumlarda (belki de kabuğa özgü dizi uygulamaları hariç) bir genişlemenin davranışı yapılandırılabilir bir şeydir - bunun için seçenekler vardır.

Bu, elbette, çiftli fiyatlandırmadan kaçınılması gerektiği anlamına gelmez - tam tersine, kabuğun sunduğu genişlemeyi sınırlamanın en uygun ve sağlam yöntemi budur. Ancak, alternatifler zaten ustalıkla öne sürüldüğü için, kabuğun bir değeri artırdığında ne olacağını tartışmak için mükemmel bir yer olduğunu düşünüyorum.

Kabuğu, kalbinde ve ruhunda (olanlar için) , bir komut yorumlayıcısıdır - büyük, etkileşimli bir çözümleyicidir sed. Kabuk deyimi ise boğulma üzerinde boşluk veya benzeri o zaman tamamen kabuk yorumlama sürecini anlaşılamamıştır çünkü çok muhtemeldir - özellikle nasıl ve neden bir eyleme komuta bir giriş deyimi çevirir. Kabuğun işi:

  1. girişi kabul et

  2. yorumlamak ve bölünmüş simgeleþtirilmiþ girdi içine doğru kelimeleri

    • giriş kelimeleri , $wordveya gibi kabuk sözdizimi öğeleridir.echo $words 3 4* 5

    • kelimeler her zaman boşlukta bölünür - bu sadece sözdizimidir - ancak yalnızca girdi dosyasındaki kabuğa sunulan gerçek boşluk karakterleri

  3. Gerekirse bunları birden çok alana genişlet

    • alanlar kaynaklanan kelime açılımları - onlar son yürütülebilir komutu oluşturan

    • hariç "$@", $IFS alan bölme ve yol adı genişletme giriş kelimesini her zaman tek bir alana göre değerlendirmelidir .

  4. ve sonra sonuç komutunu çalıştırmak için

    • Çoğu durumda bu, yorumlama sonuçlarının bir şekilde veya başka şekilde aktarılmasını içerir.

İnsanlar genellikle kabuğun bir yapıştırıcı olduğunu söyler ve eğer bu doğruysa, yapışan şey argümanlar listesidir - ya da alanlar - bir sürece ya da bir başkasına, bunlar olduğunda exec. Çoğu kabuk, NULbaytı iyi ele almaz - eğer öyleyse - ve bunun nedeni zaten üzerinde bölünmüş olmalarıdır. Kabuğun exec çok şey yapması gerekir ve bunu NUL, execzaman zaman sistem çekirdeğine verdiği sınırlandırılmış bir argüman dizisi ile yapmalıdır . Eğer kabuğun sınırlayıcısını sınırlandırılmış verilerle karıştırırsanız, kabuk muhtemelen onu mahveder. Dahili veri yapıları - çoğu program gibi - bu sınırlayıcıya dayanır. zsh, özellikle de, bunu mahvetmez.

Ve bu nerede $IFS. Gelir $IFSbir daima mevcuttur - ve aynı şekilde ayarlanabilir - kabuk gelen kabuk açılımlar ayrılmalıyız nasıl tanımlar kabuk parametresi kelimesi için saha özellikle bu değerleri ne - alanları sınırlamak gerekir. - $IFSdışındaki sınırlayıcılara kabuk genişlemelerini böler NULya da başka bir deyişle, kabuk , kendi iç veri dizilerinde değeri $IFSile eşleşen bir genişlemeden kaynaklanan baytları değiştirir NUL. Buna baktığınızda, her alan bölünmüş kabuk genişlemesinin $IFSsınırlı veri dizisi olduğunu görmeye başlayabilirsiniz .

O anlamak önemlidir $IFSyalnızca sınırlandıran olan genişlemeleri değil sen ile yapabilirsiniz - zaten aksi ayrılmış "çift tırnak. Bir genişleme teklifi verirken, onu en başından ve en azından değerinin kuyruğuna sınırlarsınız. Bu durumlarda $IFS, ayrılacak alan olmadığı için geçerli değildir. Aslında, çift tırnaklı bir genişleme , boş bir değere ayarlandığında kote edilmemiş bir genişlemeye aynı alan bölme davranışı sergiler IFS=.

Alıntılanan sürece, $IFSkendisi de bir olduğunu $IFSsınırlandırılmış kabuk genişleme. Varsayılan olarak <space><tab><newline>- üçü içinde bulunduğunda özel özellikler sergileyen belirli bir değere varsayılandır $IFS. Diğer herhangi bir değerin $IFS, genişleme oluşumu başına tek bir alanı değerlendirmek için belirtilmiş olmasına rağmen , boşluk - bu üçünün herhangi biri - genişleme dizisi başına tek bir alana ayrılmak üzere belirtilir ve öncü / iz dizileri tamamıyla seçilir. Bu muhtemelen örnek yoluyla anlaşılması en kolay yoldur.$IFS

slashes=///// spaces='     '
IFS=/; printf '<%s>' $slashes$spaces
<><><><><><     >
IFS=' '; printf '<%s>' $slashes$spaces
</////>
IFS=; printf '<%s>' $slashes$spaces
</////     >
unset IFS; printf '<%s>' "$slashes$spaces"
</////     >

Ama bu sadece $IFS- sorulduğu gibi sadece kelime bölme veya boşluk , özel karakterlerden ne haber ?

Kabuk - varsayılan olarak - ayrıca listede bulunmadıklarında bazı işaretsiz belirteçleri ( ?*[burada başka yerde belirtildiği gibi ) birden çok alana genişletir . Buna yol adı genişletme veya genelleme adı verilir . İnanılmaz derecede faydalı bir araçtır ve kabuğun ayrıştırma sırasındaki alana bölündükten sonra meydana geldiği için $ IFS'den etkilenmez - bir yol adı genişlemesiyle oluşturulan alanlar dosya adının başına / kuyruğuna bakılmaksızın sınırlandırılır. içerikleri, içinde şu anda olan karakterleri içeriyor $IFS. Bu davranış varsayılan olarak açık olarak ayarlanmıştır - ancak başka türlü kolayca yapılandırılabilir.

set -f

Bu kabuk talimat değil hiç topak . Yol adı genişletme işlemi en azından bu ayar bir şekilde yapılıncaya kadar gerçekleşmez - örneğin geçerli kabuk başka bir yeni kabuk işlemiyle değiştirilmişse veya ...

set +f

... kabuğa verilir. Çift tırnak - $IFS alan bölmek için de olduğu gibi - bu genel ayarı genişletme başına gereksiz kılar. Yani:

echo "*" *

... eğer pathname genişlemesi şu anda etkinse, muhtemelen argüman başına çok farklı sonuçlar üretecektir - birincisi sadece değişmez değerine genişleyecektir ( ikincisi , hiçbir şekilde söylenmeyen tek yıldız karakteri) ve ikincisi de aynıdır. Geçerli çalışma dizini, eşleşebilecek hiçbir dosya adı içermiyorsa (ve neredeyse hepsiyle eşleşirse ) . Ancak yaparsanız:

set -f; echo "*" *

... her iki argüman için de sonuçlar aynıdır - *bu durumda genişlemez.


Aslında @ StéphaneChazelas ile (çoğunlukla) işleri yardım etmekten daha fazla karıştırdığı konusunda hemfikirim. Şimdi IFSgerçekten nasıl çalıştığı hakkında daha iyi bir fikrim (ve bazı örnekler) var . Ne yok olsun o neden olduğu hiç ayarlamak için iyi bir fikir olabilir IFSvarsayılan dışında bir şeye.
Joker,

1
@Wildcard - bu bir alan sınırlayıcısı. eğer bir değişkende, genişletmek istediğiniz birden çok alana genişletmek istediğiniz bir değer varsa, onu bölün $IFS. cd /usr/bin; set -f; IFS=/; for path_component in $PWD; do echo $path_component; donebaskılar \ndaha sonra usr\nsonra bin\n. Birincisi echoboş çünkü /boş bir alan. Path_components'ın yeni satırları veya boşlukları olabilir veya ne olursa olsun - bileşenler /varsayılan değerde olmadığı için bölündüğü için farketmez . awkZaten insanlar bunu her zaman yapıyorlar . senin kabuğun da yok
mikeserv

3

Dosya adlarında boşluk, dizin adlarında boşluk olan büyük bir video projem vardı. find -type f -print0 | xargs -0Çeşitli amaçlar için ve farklı mermiler arasında çalışırken , bash kullanıyorsanız, özel bir IFS (giriş alanı ayırıcı) kullanmanın size daha fazla esneklik sağladığını biliyorum. Aşağıdaki kod parçası bash kullanır ve IFS'yi sadece yeni bir satıra ayarlar; Dosya adlarınızda yeni satırlar olmaması şartıyla:

(IFS=$'\n'; for i in $(find -type f -print) ; do
    echo ">>>$i<<<"
done)

IFS'in yeniden tanımlanmasını izole etmek için parens kullanımına dikkat edin. IFS'in nasıl kurtarılacağı hakkındaki diğer mesajları okudum, ancak bu daha kolay.

Dahası, IFS'yi yeni satıra ayarlamak, önceden kabuk değişkenlerini ayarlamanızı ve kolayca yazdırmanızı sağlar. Örneğin, yeni satırları ayırıcı olarak kullanarak artımlı olarak V değişkenini büyütebilirim:

V=""
V="./Ralphie's Camcorder/STREAM/00123.MTS,04:58,05:52,-vf yadif"
V="$V"$'\n'"./Ralphie's Camcorder/STREAM/00111.MTS,00:00,59:59,-vf yadif"
V="$V"$'\n'"next item goes here..."

ve buna göre:

(IFS=$'\n'; for v in $V ; do
    echo ">>>$v<<<"
done)

Şimdi, V echo "$V"satırını yeni satırları çıkarmak için çift tırnak kullanarak ayarlayabilirim . ( Bu konuya$'\n' açıklama için teşekkür ederiz .)


3
Ancak daha sonra, newline veya glob karakterlerini içeren dosya adlarıyla ilgili sorunlarınız devam edecektir. Ayrıca bakınız: Neden döngü bulgunun çıktısı kötü bir uygulama? . Eğer kullanıyorsanız zsh, kullanabilirsiniz IFS=$'\0've kullanabilirsiniz -print0( zshgenişlemeler üzerine globbing yapmaz, bu yüzden glob karakterleri burada sorun olmaz).
Stéphane Chazelas

1
Bu, boşluk içeren dosya adlarıyla çalışır, ancak potansiyel olarak düşmanca dosya adlarına veya yanlışlıkla “saçma” dosya adlarına karşı çalışmaz. Joker karakter içeren dosya adları sorununu ekleyerek kolayca düzeltebilirsiniz set -f. Diğer yandan, yaklaşımınız temel olarak yeni satırlar içeren dosya adlarıyla başarısız oluyor. Dosya adları dışındaki verilerle ilgilenirken, boş öğelerle de başarısız olur.
Gilles

Doğru, benim uyarım dosya isimleri newlines ile çalışmayacak olmasıdır. Ancak, sadece çılgınlıktan utangaç bir çizgi
Russ

Ve neden bu kadar düşük oy aldığından emin değilim. Bu, boşluklu dosya isimlerini yinelemek için mükemmel bir yöntemdir. -Print0 kullanmak xargs gerektirir ve bu zinciri kullanmanın zor olduğu şeyler vardır. Üzgünüm, biri cevabımı kabul etmiyor, ama bu oyu reddetmek için bir sebep değil.
Russ

0

Yukarıda belirtilen tüm güvenlik uygulamalarını göz önünde bulundurarak ve genişlediğiniz değişkenler üzerinde güvendiğinizi ve kontrol ettiğinizi varsayalım eval. Ama dikkat et!

$ FILES='"a b" c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
$ FILES='a\ b c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
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.