“Okurken…” kullanarak yankı ve printf farklı sonuçlar elde eder


14

Bu soruya göre " bir linux betiğinde " okurken kullanmak ... "

echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done

sonuç:

1, 2, 3 4 5 6

ama değiştirdiğimde echo ileprintf

echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done

sonuç

1, 2, 3
4, 5, 6

Birisi bana bu iki komutu neyin farklı kıldığını söyleyebilir mi? Teşekkür ~

Yanıtlar:


24

Sadece echo vs printf değil

İlk olarak, read a b ckısmen ne olduğunu anlayalım. space-tab-newline readolan IFSdeğişkenin varsayılan değerine dayalı olarak kelime ayırma gerçekleştirir ve buna dayalı her şeye uyar. Tutmak için değişkenlerden daha fazla girdi varsa, bölünmüş parçaları ilk değişkenlere sığdırır ve yerleştirilemez - en sonuncusuna gider. Demek istediğim şu:

bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four

Bu tam olarak bash kılavuzunda (cevabın sonundaki alıntıya bakınız).

Sizin durumunuzda olan şey şu ki, 1 ve 2 a ve b değişkenlerine uyuyor ve c diğer her şeyi alıyor, yani 3 4 5 6 .

Ayrıca birçok kez göreceğiniz şey, insanların while IFS= read -r line; do ... ; done < input.txtmetin dosyalarını satır satır okumak için kullandıklarıdır . Yine, IFS=kelime bölmeyi kontrol etmek veya daha spesifik olarak - devre dışı bırakmak ve tek bir metin satırını bir değişkene okumak için burada. Orada readolmasaydı , her bir kelimeyi linedeğişkene sığdırmaya çalışırdı. Ama bu, daha sonra çalışmanızı teşvik ettiğim başka bir hikaye, çünkü while IFS= read -r variableçok sık kullanılan bir yapı.

echo vs printf davranışı

echoburada beklediğinizi yapar. Değişkenlerinizi tam olarak readdüzenledikleri gibi görüntüler . Bu, daha önceki tartışmalarda zaten gösterilmiştir.

printfçok özeldir, çünkü değişkenler hepsi bitene kadar biçim dizesine sığdırmaya devam eder. Yani printf "%d, %d, %d \n" $a $b $cprintf yaptığınızda biçim dizesini 3 ondalık sayı ile görür, ancak 3'ten daha fazla argüman vardır (çünkü değişkenleriniz aslında tek tek 1,2,3,4,5,6'ya genişler). Bu kafa karıştırıcı gelebilir, ancak bir nedenden ötürü, gerçek printf() işlevin C dilinde yaptıklarından geliştirilmiş davranış olarak vardır .

Burada da çıktıyı etkileyen, değişkenlerinizin alıntılanmamasıdır, bu da kabuğun (değil printf) değişkenleri 6 ayrı öğeye ayırmasına izin verir . Bunu alıntı ile karşılaştırın:

bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3

Tam olarak $cdeğişken alıntılandığından, artık tek bir dize olarak tanınır 3 4ve %dsadece tek bir tam sayı olan biçime uymaz

Şimdi aynı şeyi alıntı yapmadan yapın:

bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0

printf tekrar söylüyor: "Tamam, orada 6 öğe var ama biçim sadece 3 gösterir, bu yüzden bir şeyler sığdırmaya devam edeceğim ve kullanıcıdan gerçek girdi ile eşleşemediğim her şeyi boş bırakacağım".

Ve tüm bu durumlarda benim sözümü almak zorunda değilsiniz. Sadece çalıştırın strace -e trace=execveve kendiniz görün, komut aslında neyi "gör"?

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: 3 4’: value not completely converted
3
+++ exited with 1 +++

Ek Notlar

Charles Duffy'nin yorumlarda düzgün bir şekilde işaret ettiği gibi, komutunuzda kullandığınız bashkendi yerleşik printfolanı strace, aslında /usr/bin/printfkabuğun sürümünü değil sürümü çağırır . Küçük farkların yanı sıra, bu özel soruya olan ilgimiz için standart format belirteçleri aynıdır ve davranış aynıdır.

Ayrıca akılda tutulması gereken , sözdiziminin C veya sözdiziminde işlevi olan herhangi bir C benzeri dile daha aşina olduğunu belirtmekten printfdaha çok taşınabilir (ve bu nedenle tercih edilir) echoolduğudur printf(). Bu Bkz Terdon tarafından mükemmel cevabı konusunda printfvs echo. Çıkışı, Ubuntu'nun belirli sürümünüzde özel kabuğunuza göre düzenleyebilirsiniz, ancak farklı sistemlerde komut dosyalarını taşıyacaksanız, muhtemelen printfyankı yerine tercih etmelisiniz . Belki de Ubuntu ve CentOS makineleriyle çalışan yeni başlayan bir sistem yöneticisisiniz, hatta belki de bildiğiniz FreeBSD bile - bu gibi durumlarda seçimler yapmanız gerekecektir.

Bash el kitabından alıntı, SHELL BUILTIN KOMUTLARI bölümü

[-ers] [-a aname] [-d ayırma] [-i metin] [-n nchars] [-N nchars] [-p istemi] [-t zaman aşımı] [-u fd] [ad ... ]

Bir satır, standart girişten veya -u seçeneğine bağımsız değişken olarak sağlanan dosya tanımlayıcı fd'den okunur ve ilk sözcük, ilk ada, ikinci ada ikinci kelimeye, vb. kelimeleri ve soyadına atanan araya giren ayırıcıları. Giriş akışından adlardan daha az okunan kelime varsa, kalan adlara boş değerler atanır. IFS'deki karakterler, kabuğun genişletme için kullandığı kuralların aynısını kullanarak satırı kelimelere bölmek için kullanılır (yukarıda Word Bölme altında açıklanmıştır).


2
straceDava ve diğeri arasındaki kayda değer bir fark - strace printfkullanırken /usr/bin/printf, printfdoğrudan bash'da aynı adla yerleşik kabuk kullanıyor. Her zaman aynı olmazlar - örneğin, bash örneğinde biçim belirleyicileri %qve yeni sürümlerde $()Tzaman biçimlendirmesi bulunur.
Charles Duffy

7

Bu sadece bir öneridir ve Sergiy'in cevabının yerini almak anlamına gelmez. Sanırım Sergiy, baskıda neden farklı olduklarına dair harika bir cevap yazdı. Okumadak değişken içine kalan atanır nasıl $colarak değişken 3 4 5 6sonra 1ve 2atanır ave b. echosizin için değişken bölmek değil printfwill ile%d s .

Bununla birlikte, komutun başındaki sayıların yankısını değiştirerek temel olarak aynı cevapları vermelerini sağlayabilirsiniz:

Şunlarda /bin/bashkullanabilirsiniz:

echo -e "1 2 3 \n4 5 6"

Sadece /bin/shşunları kullanabilirsiniz:

echo "1 2 3 \n4 5 6"

Bash, sh'nin ihtiyaç duymadığı durumlarda \ escape karakterlerini etkinleştirmek için -e'yi kullanır. \nyeni bir satır oluşturmasına neden olur, bu nedenle şimdi echo satırı, echo loop ifadeniz için iki kez kullanılabilen iki ayrı satıra bölünür:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6

Hangi sırayla printfkomutu kullanarak aynı çıktıyı üretir :

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

İçinde sh

$ echo "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6
$ echo "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

Bu yardımcı olur umarım!


echo -eyalnızca POSIX'in bir uzantısı değildir - aynı zamanda girdinin aslında siyah harf belirtimine meydan okuduğu / bozduğu göz önüne alındığında, çıktısında echoyayılmayan herhangi bir uzantıdır -e. (bash varsayılan olarak uyumlu olmayan, ancak her ikisi de, standart ile uyumlu olduğu posixve xpg_echo; ikincisi bayrakları ayarlanır yapabilirsiniz Bash değil tüm sürümleri ile çalışacaktır, yani derleme sırasında varsayılan yapılabilir echo -eburada beklediğiniz gibi).
Charles Duffy

POSIX spesifikasyonunun pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.htmlecho adresindeki POSIX spesifikasyonunun BAŞVURU KULLANIMI ve RASYONU bölümlerine bakın , girişin herhangi bir yerinde echoya -nda ters eğik çizgi varlığında davranışının açıkça belirtilmediğini ve printfyerine kullanılabilir.
Charles Duffy

@CharlesDuffy Hiç bu yazıyı bash'ın tüm sürümlerinde çalışmasını beklediğimi yazdım mı? Cevabımla ilgili ne problemin var? Daha iyi bir çözümünüz olduğunu düşünüyorsanız, bunu yanlış olduğunu kanıtlamak yerine cevap olarak yazın. Senin gibi insanlarla çok fazla bu şekilde yaşadım, bu yüzden lütfen bu gibi yorumlarla dur. Bütün söylemem gereken bu.
Terrance

3
Ask Ubuntu'da bazen diğer Linux dağıtımlarına aktarılamayan cevaplar yazıyoruz. Buradaki temsilciniz sadece 103 puan olduğu için bunu fark etmemiş olabilirsiniz. Stack Overflow'daki temsilciniz 123.3K puanıdır, bu nedenle genel olarak * NIX sistemleri hakkında bir ton şey biliyorsunuzdur. Bence sen, Teras ve Serg iyi ama ipliğe farklı açılardan bakıyorsun. * NIX taşınabilir ya da en azından Linux dağıtımlarında taşınabilir bir cevap yazmanız için yankı (cok amaçlı değil).
WinEunuuchs2Unix

2
@CharlesDuffy Düşüncelerinizin taşınabilir olması gerektiğine katılıyorum , ancak sonuçta bu, Ubuntu odaklı bir site, bu nedenle Ubuntu'ya özgü araçların kullanıcılar tarafından üstlenilmesi gerekiyor. Yani uyarı biraz ima ediliyor. Aşırı uzun tartışmalara girmeyelim. Diğer mermilerin göz önünde bulundurulması gerektiği ve cevaplardan öğrenmek için okunan yeni başlayanların da göz önünde bulundurulması gerekiyor. Kısa bir yorum muhtemelen bunu belirtmek için yeterli olacaktır.
Sergiy Kolodyazhnyy
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.