Az önce bir değişken atadım ama echo $ değişkeni başka bir şey gösteriyor


104

Aşağıda, echo $varatanmış olandan farklı bir değer gösterebilecek bir dizi durum verilmiştir. Bu, atanan değerin "çift tırnaklı", "tek tırnaklı" veya tırnaksız olmasına bakılmaksızın gerçekleşir.

Kabuğun değişkenimi doğru ayarlamasını nasıl sağlayabilirim?

Yıldız işaretleri

Beklenen çıktı /* Foobar is free software */, ancak bunun yerine bir dosya adı listesi alıyorum:

$ var="/* Foobar is free software */"
$ echo $var 
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...

Köşeli parantez

Beklenen değer [a-z], ancak bazen bunun yerine tek bir harf alıyorum!

$ var=[a-z]
$ echo $var
c

Satır beslemeleri (yeni satırlar)

Beklenen değer, ayrı satırlardan oluşan bir listedir, ancak bunun yerine tüm değerler tek satırdadır!

$ cat file
foo
bar
baz

$ var=$(cat file)
$ echo $var
foo bar baz

Birden çok boşluk

Dikkatle hizalanmış bir tablo başlığı bekliyordum, ancak bunun yerine birden çok boşluk ya kayboluyor ya da tek bir boşluk olacak!

$ var="       title     |    count"
$ echo $var
title | count

Sekmeler

Sekmeyle ayrılmış iki değer bekliyordum, bunun yerine boşlukla ayrılmış iki değer alıyorum!

$ var=$'key\tvalue'
$ echo $var
key value

2
Bunu yaptığınız için teşekkürler. Hat beslemelerinde sık karşılaşıyorum. Yani var=$(cat file)iyi, ama echo "$var"gerekli.
snd



Yanıtlar:


139

Yukarıdaki tüm durumlarda, değişken doğru ayarlanmış ancak doğru okunmamış! Doğru yol, referans verirken çift ​​tırnak kullanmaktır :

echo "$var"

Bu, verilen tüm örneklerde beklenen değeri verir. Daima değişken referansları alın!


Neden?

Bir değişken tırnaksız olduğunda:

  1. Değerin boşlukta birden çok kelimeye bölündüğü (varsayılan olarak) alan bölmeye geç :

    Önce: /* Foobar is free software */

    Sonra: /*, Foobar, is, free, software,*/

  2. Bu kelimelerin her biri , kalıpların eşleşen dosyalara genişletildiği yol adı genişlemesinden geçecektir :

    Önce: /*

    Sonra: /bin, /boot, /dev, /etc, /home, ...

  3. Son olarak, tüm argümanlar echo'ya aktarılır, bu da onları tek boşluklarla ayırarak yazar ve

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/

    değişkenin değeri yerine.

Değişken alıntı yapıldığında:

  1. Değeri ile ikame olun.
  2. 2. adım yok.

Bu nedenle , özellikle sözcük bölme ve yol adı genişletme gerektirmedikçe, her zaman tüm değişken referanslarını alıntı yapmalısınız . Shellcheck gibi araçlar yardımcı olmak için var ve yukarıdaki tüm durumlarda eksik alıntılar konusunda uyarıda bulunacaklar.


her zaman işe yaramıyor. Bir örnek verebilirim: paste.ubuntu.com/p/8RjR6CS668
recolic

1
Evet, $(..)sondaki satır beslemelerini çıkarır. var=$(cat file; printf x); var="${var%x}"Etrafında çalışmak için kullanabilirsiniz .
diğer adam

17

Bunun neden olduğunu bilmek isteyebilirsiniz. Birlikte ile diğer adam tarafından büyük bir açıklama , bir referans bulmak Why boşluk veya diğer özel karakterler benim kabuk komut dosyası jikleyi yapar? Gilles tarafından Unix ve Linux'ta yazılmıştır :

Neden yazmam gerekiyor "$foo"? Alıntılar olmadan ne olur?

$foo"değişkenin değerini al" anlamına gelmez foo. Çok daha karmaşık bir şey anlamına gelir:

  • İlk önce değişkenin değerini alın.
  • Alan bölme: bu değeri boşluklarla ayrılmış bir alan listesi olarak değerlendirin ve sonuçta ortaya çıkan 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 glob, yani bir joker karakter kalıbı olarak ele alın ve bu kalıpla eşleşen dosya adları listesiyle değiştirin. Kalıp herhangi bir dosyayla eşleşmezse, değiştirilmeden bırakılır. Örneğimizde, bu foo, geçerli dizindeki dosyaların listesini içeren listeyle sonuçlanır ve son olarak bar. Geçerli dizin boşsa, sonuç foo, *, bar.

Sonucun bir dizi listesi olduğuna dikkat edin. Kabuk söz diziminde iki bağlam vardır: liste bağlamı ve dize bağlamı. Alan bölme ve dosya adı oluşturma yalnızca liste bağlamında gerçekleşir, ancak çoğu zaman budur. Çift tırnak işaretleri bir dize bağlamını sınırlar: çift tırnaklı dizenin tamamı tek bir dizedir, bölünmemelidir. (İstisna: "$@"Konumsal parametreler listesine genişletmek için, örneğin üç konumsal parametre varsa "$@"eşdeğerdir "$1" "$2" "$3". Bkz. $ * Ve $ @ arasındaki fark nedir? )

Aynı şey ile $(foo)veya ile komut ikamesi için de geçerlidir `foo`. Bir yan notta kullanmayın `foo`: alıntı kuralları tuhaftır ve taşınabilir değildir ve tüm modern mermiler $(foo), sezgisel alıntı kuralları dışında kesinlikle eşdeğer olan desteği destekler .

Aritmetik ikamenin çıktısı da aynı genişletmelerden geçer, ancak bu normalde yalnızca genişletilemeyen karakterler IFSiçerdiğinden (rakamlar veya sayılar içermediği varsayılarak -) bir sorun değildir.

Bkz . Çift alıntı yapmak ne zaman gereklidir? Teklifleri atlayabileceğiniz durumlar hakkında daha fazla ayrıntı için.

Tüm bu kesinliklerin olmasını istemediğiniz sürece, değişken ve komut ikamelerinin etrafında her zaman çift tırnak kullanmayı unutmayın. Dikkatli olun: tırnak işaretlerini atlamak yalnızca hatalara değil, güvenlik açıklarına da yol açabilir .


7

Alıntı yapmamanın neden olduğu diğer sorunlara ek olarak -nve argüman -eolarak tüketilebilir echo. (Yalnızca birincisi POSIX spesifikasyonuna göre yasaldır echo, ancak birkaç yaygın uygulama spesifikasyonu ihlal eder ve tüketir -e).

Bundan kaçınmak için ayrıntılar önemli olduğunda kullanmak printfyerine kullanın echo.

Böylece:

$ vars="-e -n -a"
$ echo $vars      # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a

Ancak, şunları kullanırken doğru alıntı yapmak sizi her zaman kurtarmaz echo:

$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed

... o oysa olacak olan kurtaracak printf:

$ vars="-n"
$ printf '%s\n' "$vars"
-n

Yaşasın, bunun için iyi bir tekilleştirmeye ihtiyacımız var! Bunun soru başlığına uyduğuna katılıyorum, ancak burada hak ettiği görünürlüğü elde edeceğini sanmıyorum. " -e/ -n/ Ters eğik çizgim neden görünmüyor?" À la yeni bir soruya ne dersiniz ? Buradan uygun şekilde bağlantılar ekleyebiliriz.
diğer adam

Şunu mu demek istediniz tüketmek -nkuyu olarak ?
PesaThe

1
@PesaThe, hayır, demek istedim -e. Standardı echo, ilk argümanı olduğu zaman çıktıyı belirtmez -n, bu durumda herhangi bir / tüm olası çıktıyı yasal kılar; için böyle bir hüküm yok -e.
Charles Duffy

Oh ... okuyamıyorum. Bunun için İngilizcemi suçlayalım. Açıklama için teşekkürler.
PesaThe

6

tam değeri almak için kullanıcı çift tırnak. bunun gibi:

echo "${var}"

ve değerinizi doğru okuyacaktır.


Çalışıyor .. Teşekkürler
Tshilidzi Mudau

2

echo $varçıktı büyük ölçüde IFSdeğişkenin değerine bağlıdır . Varsayılan olarak boşluk, sekme ve yeni satır karakterleri içerir:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$

Bu, kabuğun alan bölme (veya sözcük bölme) yaparken tüm bu karakterleri sözcük ayırıcı olarak kullandığı anlamına gelir. Bu, bir değişkeni çift tırnak işareti olmadan yankılamak için başvurulduğunda olan şeydir $varve bu nedenle beklenen çıktı değiştirilir.

Sözcük bölünmesini önlemenin bir yolu (çift tırnak kullanmanın yanı sıra) IFSnull olarak ayarlamaktır . Bkz. Http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :

IFS'nin değeri boş ise, alan ayırma yapılmayacaktır.

Null olarak ayarlamak, boş değere ayarlamak anlamına gelir:

IFS=

Ölçek:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$ 

2
Ayrıca set -fglobbing'i de önlemeniz gerekir
o diğer adam

@thatotherguy, yol genişletme ile ilk örneğiniz için gerçekten gerekli mi? İle IFSboş olarak ayarlandığında, echo $vargenişletilmiş olacak echo '/* Foobar is free software */'ve yol genişletme tek tırnaklı dizge içinde gerçekleştirilmez.
ks1322

1
Evet. Eğer mkdir "/this thing called Foobar is free software etc/"siz hala genişlediğini göreceksiniz. Açıkça [a-z]örnek için daha pratik .
diğer adam

Görüyorum, bu [a-z]örneğin mantıklı .
ks1322

2

Ks1322 gelen cevap kullanırken sorunu tanımlamak için bana yardımcı oldu docker-compose exec:

-TBayrağı atlarsanız, docker-compose execçıktıyı kesen özel bir karakter ekleyin, bbunun yerine şunu görürüz 1b:

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$

İle -Tbayrak, docker-compose execbeklendiği gibi çalışır:

$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b

-2

Değişkeni tırnak içine koymanın yanı sıra, değişkenin çıktısı trboşluklar kullanılarak ve satırsonu satırlarına dönüştürülerek de çevrilebilir.

$ echo $var | tr " " "\n"
foo
bar
baz

Bu biraz daha kıvrımlı olsa da, herhangi bir karakteri dizi değişkenleri arasındaki ayırıcı olarak değiştirebileceğiniz için çıktıyla daha fazla çeşitlilik katar.


2
Ancak bu, tüm boşlukları yeni satırlarla değiştirir. Alıntı yapmak, mevcut satırsonu ve boşlukları korur.
user000001

Doğru, evet. Sanırım bu değişken içinde ne olduğuna bağlı. Aslında trmetin dosyalarından diziler oluşturmak için diğer yolu kullanıyorum .
Alek

3
Değişkeni düzgün bir şekilde alıntı yapmayarak ve sonra onun etrafında karmaşık bir ekstra işlemle çalışarak bir problem yaratmak iyi bir programlama değildir.
üçlü

@Alek, ... err, ne? trBir metin dosyasından düzgün / doğru bir dizi oluşturmanıza gerek yoktur - IFS'yi ayarlayarak istediğiniz ayırıcıyı belirtebilirsiniz. Örneğin: IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')bash 3.2'ye (geniş dolaşımdaki en eski sürüm) kadar geriye doğru çalışır ve catbaşarısız olursanız çıkış durumunu doğru şekilde yanlış olarak ayarlar . Eğer isteseydim, diyelim ki, sekmeler yerine yeni satırlar, sadece değiştirecekti $'\n'ile $'\t'.
Charles Duffy

1
@Alek, ... eğer böyle bir şey yapıyorsanız arrayname=( $( cat file | tr '\n' ' ' ) ), o zaman bu birden çok katmana bölünmüştür: Sonuçlarınızı özetlemektedir (yani *, mevcut dizindeki dosyaların bir listesine dönüşür) ve bu,tr ( ya da cat, bu konuda; kişi sadece kullanılabilir arrayname=$( $(<file) )ve aynı yollarla kırılabilir, ancak daha az verimsiz bir şekilde).
Charles Duffy
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.