Kesim zsh ile değil bash ile neden başarısız oluyor?


10

Sekmeyle ayrılmış alanlara sahip bir dosya oluşturuyorum.

echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input

Aşağıdaki komut dosyası var zsh.sh

#!/usr/bin/env zsh
while read line; do
    <<<$line cut -f 2
done < "$1"

Test ediyorum.

$ ./zsh.sh input
bar
bar

Bu iyi çalışıyor. Ancak, bashbunun yerine çağırmak için ilk satırı değiştirdiğimde başarısız olur.

$ ./bash.sh input
foo bar baz
foo bar baz

Bu neden başarısız oluyor bashve birlikte çalışıyor zsh?

Ek sorun giderme

  • Mesken yerine doğrudan yolları kullanmak envaynı davranışı üretir.
  • echoBurada dizgiyi kullanmak yerine borulama <<<$lineda aynı davranışı üretir. yani echo $line | cut -f 2.
  • Her iki mermi için işlerawk yerine kullanmak . yani .cut <<<$line awk '{print $2}'

4
Bu arada, bunlardan birini yaparak daha basit test dosyası yapabilirsiniz: echo -e 'foo\tbar\tbaz\n...', echo $'foo\tbar\tbaz\n...'ya da printf 'foo\tbar\tbaz\n...\n'bunların veya varyasyonları. Her sekmeyi veya yeni satırı ayrı ayrı sarmak zorunda kalmazsınız.
sonraki duyuruya kadar duraklatıldı.

Yanıtlar:


13

Olan şey bashsekmeleri boşluklarla değiştiriyor. Bunun "$line"yerine söyleyerek veya boşlukları açıkça keserek bu sorunu önleyebilirsiniz .


1
Bash'ın a'yı görmesinin \tve bir boşlukla değiştirmesinin bir nedeni var mı ?
user1717828

@ user1717828 evet, spit + glob operatörü denir . Bu, bash ve benzer kabuklarda sıralanmamış bir değişken kullandığınızda olur.
terdon

1
@terdon, içinde <<< $line, bashbölünmüş yapar ama topak değil. <<<Tek bir kelime beklediği için buraya bölünmesinin bir nedeni yok . Bu durumda yarılır ve birleştirilir, bu da çok az mantıklıdır ve daha <<<önce veya sonra desteklenmiş diğer tüm kabuk uygulamalarına karşıdır bash. IMO bir hata.
Stéphane Chazelas

@ StéphaneChazelas yeterince adil, sorun zaten bölünmüş kısmı ile ilgili.
terdon

2
@ StéphaneChazelas Bash 4.4'te bölünme (ne de glob) olmaz

17

Çünkü <<< $line, burada bashbölünmediği için (bölme olmasa da) kelime bölme işlemi gerçekleşir $lineve sonuçta elde edilen kelimeleri boşluk karakteriyle birleştirir (ve bunu geçici bir dosyaya yeni satır karakteri koyar ve bunu yapar cut).

$ a=a,b,,c bash -c 'IFS=","; sed -n l <<< $a'
a b  c$

tabvarsayılan değerinde olur $IFS:

$ a=$'a\tb'  bash -c 'sed -n l <<< $a'
a b$

Çözüm bash, değişkeni alıntılamaktır.

$ a=$'a\tb' bash -c 'sed -n l <<< "$a"'
a\tb$

Bunu yapan tek kabuk olduğunu unutmayın. zsh(nereden <<<geliyor, Unix portundan esinlenerek rc) ksh93, mkshve yashbunu da desteklemiyor <<<.

O diziler söz konusu olduğunda, mksh, yashve zshilk karakterinden katılmak $IFS, bashve ksh93uzay.

$ mksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ yash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ ksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ bash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$

Boş olduğunda zsh/ yashve mksh(en azından R52 sürümü) arasında bir fark vardır $IFS:

$ mksh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
12$

Davranışı kullandığınızda kabuklar arasında daha tutarlıdır "${a[*]}"(ancak boşken mkshhala bir hata vardır $IFS).

Bu echo $line | ..., Bourne benzeri tüm mermilerde olağan split + glob operatörüdür, ancak zsh(ve ilişkili normal problemler echo).


1
Mükemmel cevap! Teşekkürler (+1). Yine de en düşük soruyu cevaplayanı kabul edeceğim, çünkü soruyu aptallığımı ortaya çıkaracak kadar iyi cevapladılar.
Sparhawk

10

Sorun, alıntı yapmadığınızdır $line. Araştırmak için iki betiği yalnızca yazdırılacak şekilde değiştirin $line:

#!/usr/bin/env bash
while read line; do
    echo $line
done < "$1"

ve

#!/usr/bin/env zsh
while read line; do
    echo $line
done < "$1"

Şimdi çıktılarını karşılaştırın:

$ bash.sh input 
foo bar baz
foo bar baz
$ zsh.sh input 
foo    bar    baz
foo    bar    baz

Gördüğünüz gibi, alıntı yapmadığınız $lineiçin sekmeler bash tarafından doğru şekilde yorumlanmıyor. Zsh bununla daha iyi başa çıkıyor gibi görünüyor. Şimdi, varsayılan olarak alan sınırlayıcı olarak cutkullanır \t. Bu nedenle, bashkomut dosyanız sekmeleri yediğinden (split + glob operatörü nedeniyle), cutyalnızca bir alan görür ve buna göre hareket eder. Gerçekten koştuğunuz şey:

$ echo "foo bar baz" | cut -f 2
foo bar baz

Bu nedenle, komut dosyanızın her iki kabukta da beklendiği gibi çalışmasını sağlamak için değişkeninizi belirtin:

while read line; do
    <<<"$line" cut -f 2
done < "$1"

Ardından, her ikisi de aynı çıktıyı üretir:

$ bash.sh input 
bar
bar
$ zsh.sh input 
bar
bar

Mükemmel cevap! Teşekkürler (+1). Yine de en düşük soruyu cevaplayanı kabul edeceğim, çünkü soruyu aptallığımı ortaya çıkaracak kadar iyi cevapladılar.
Sparhawk

^ Düzeltilenleri içerecek tek cevap (henüz) olmak için oy verinbash.sh
lauir

1

Daha önce yanıtlandığı gibi, bir değişkeni kullanmanın daha taşınabilir bir yolu onu alıntılamaktır:

$ printf '%s\t%s\t%s\n' foo bar baz
foo    bar    baz
$ l="$(printf '%s\t%s\t%s\n' foo bar baz)"
$ <<<$l     sed -n l
foo bar baz$

$ <<<"$l"   sed -n l
foo\tbar\tbaz$

Çizgide bash'da uygulama farkı var:

l="$(printf '%s\t%s\t%s\n' foo bar baz)"; <<<$l  sed -n l

Bu çoğu merminin sonucudur:

/bin/sh         : foo bar baz$
/bin/b43sh      : foo bar baz$
/bin/bash       : foo bar baz$
/bin/b44sh      : foo\tbar\tbaz$
/bin/y2sh       : foo\tbar\tbaz$
/bin/ksh        : foo\tbar\tbaz$
/bin/ksh93      : foo\tbar\tbaz$
/bin/lksh       : foo\tbar\tbaz$
/bin/mksh       : foo\tbar\tbaz$
/bin/mksh-static: foo\tbar\tbaz$
/usr/bin/ksh    : foo\tbar\tbaz$
/bin/zsh        : foo\tbar\tbaz$
/bin/zsh4       : foo\tbar\tbaz$

Değişken değerinin sağında yalnızca bash bölünür <<<.
Ancak bu, bash 4.4 sürümünde düzeltilmiştir.
Bu, değerinin $IFSsonucunu etkilediği anlamına gelir <<<.


Çizgi ile:

l=(1 2 3); IFS=:; sed -n l <<<"${l[*]}"

Tüm mermiler, değerleri birleştirmek için IFS'in ilk karakterini kullanır.

/bin/y2sh       : 1:2:3$
/bin/sh         : 1:2:3$
/bin/b43sh      : 1:2:3$
/bin/b44sh      : 1:2:3$
/bin/bash       : 1:2:3$
/bin/ksh        : 1:2:3$
/bin/ksh93      : 1:2:3$
/bin/lksh       : 1:2:3$
/bin/mksh       : 1:2:3$
/bin/zsh        : 1:2:3$
/bin/zsh4       : 1:2:3$

İle "${l[@]}", farklı argümanları ayırmak için bir boşluk gerekir, ancak bazı kabuklar değeri IFS'den kullanmayı seçer (Bu doğru mu?).

/bin/y2sh       : 1:2:3$
/bin/sh         : 1 2 3$
/bin/b43sh      : 1 2 3$
/bin/b44sh      : 1 2 3$
/bin/bash       : 1 2 3$
/bin/ksh        : 1 2 3$
/bin/ksh93      : 1 2 3$
/bin/lksh       : 1:2:3$
/bin/mksh       : 1:2:3$
/bin/zsh        : 1:2:3$
/bin/zsh4       : 1:2:3$

Boş bir IFS ile değerler, şu satırda olduğu gibi birleştirilmelidir:

a=(1 2 3); IFS=''; sed -n l <<<"${a[*]}"

/bin/y2sh       : 123$
/bin/sh         : 123$
/bin/b43sh      : 123$
/bin/b44sh      : 123$
/bin/bash       : 123$
/bin/ksh        : 123$
/bin/ksh93      : 123$
/bin/lksh       : 1 2 3$
/bin/mksh       : 1 2 3$
/bin/zsh        : 123$
/bin/zsh4       : 123$

Ama hem lksh hem de mksh bunu başaramıyor.

Bir bağımsız değişkenler listesine geçersek:

l=(1 2 3); IFS=''; sed -n l <<<"${l[@]}"

/bin/y2sh       : 123$
/bin/sh         : 1 2 3$
/bin/b43sh      : 1 2 3$
/bin/b44sh      : 1 2 3$
/bin/bash       : 1 2 3$
/bin/ksh        : 1 2 3$
/bin/ksh93      : 1 2 3$
/bin/lksh       : 1 2 3$
/bin/mksh       : 1 2 3$
/bin/zsh        : 123$
/bin/zsh4       : 123$

Hem yash hem de zsh, bağımsız değişkenleri ayrı tutamaz. Bu bir hata mı?


Hakkında zsh/ yashve "${l[@]}"liste dışı bağlamda, liste tasarımında "${l[@]}"yalnızca özel olan tasarım gereğidir . Liste dışı bağlamlarda, ayrılık mümkün değildir, öğelere bir şekilde katılmanız gerekir. $ IFS'in ilk karakterine katılmak, boşluk karakteri IMO'suna katılmaktan daha tutarlıdır. dashbunu da yapar ( dash -c 'IFS=; a=$@; echo "$a"' x a b). Ancak POSIX, IIRC'yi değiştirmeyi planlıyor. Bkz bu (uzun) tartışma
Stéphane Chazelas


Kendime cevap veriyorum, hayır, ikinci bir bakışı olan POSIX, davranışı var=$@belirtilmemiş olarak bırakacaktır .
Stéphane Chazelas
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.