Printf neden yankıdan daha iyidir?


548

Bundan printfdaha iyi olduğunu duydum echo. Kullanmam gereken deneyimlerimden sadece bir örneği hatırlayabilirim printfçünkü echoRHEL 5.8'deki bazı programlara metin yazmak için çalışmadı, ancak printfyaptı. Fakat görünüşe göre, başka farklılıklar var ve bunların ne olduğunu ve ne zaman birinin diğerini kullanacağı konusunda özel durumlar olup olmadığını sormak istiyorum.


N'aber echo -e?
aslaMind9

1
@ neverMind9 , yalnızca (bazı nedenlerden dolayı) içinde echo -eçalışmaz ve örneğin liman işçisi, varsayılan olarak komutları için kullanırshbashshRUN
Ciprian Tomoiagă

@ CiprianTomoiagă echo -ebenim için aslında Android Terminalinde çalışıyor sh.
aslaMind9

Yanıtlar:


757

Temel olarak, bu bir taşınabilirlik (ve güvenilirlik) sorunudur.

Başlangıçta, echoherhangi bir seçeneği kabul etmedi ve hiçbir şeyi genişletmedi. Tek yaptığı, boşluk karakteriyle ayrılmış ve yeni satır karakteriyle sonlanan argümanlarını göstermekti.

Şimdi, birisi echo "\n\t"newline veya tab karakterleri çıktısı almak veya takip eden newline karakterini çıkarıp vermemek için bir seçeneğe sahip olmamızın iyi olacağını düşündü .

Daha sonra daha zor olduğunu düşünüyorlardı, ancak bu işlevselliği kabuğa eklemek yerine ( perlçift ​​tırnakların içinde, \taslında bir sekme karakteri olduğu gibi), bunu eklediler echo.

David Korn, hatayı fark etti ve yeni bir kabuk alıntı biçimi ortaya koydu: $'...'ki bu daha sonra kopyalandı bashve zsho zamana kadar çok geçti.

Şimdi standart bir UNIX echoiki karakteri içeren bir argüman aldığında \ve tbunları çıkarmak yerine, bir sekme karakteri çıkarır. \cBir argümanda görür görmez , çıktısını durdurur (böylece yeni satırın da çıkışı olmaz).

Diğer mermiler / Unix satıcıları / sürümleri farklı yapmayı seçti: -ekaçış dizilerini genişletmek için bir -nseçenek ve izleyen yeni satırı çıkarmamak için bir seçenek eklediler . Bazılarında var -Ebazılarında, çıkış sıralarını devre dışı bırakmak -ndeğil -e, bir tarafından desteklenen kaçış dizilerinin listesi echouygulaması başka desteklediği şekilde mutlaka aynı değildir.

Sven Mascheck, sorunun boyutunu gösteren hoş bir sayfaya sahiptir .

O günü echoseçeneklerini destekler uygulamaları, bir hiç destek genellikle orada --seçenekler sonunu işaretlemek için ( echodo olmayan bazı Bourne benzeri kabuklarından komutuna destek ve zsh destekleyen -buna da uygun) örneğin bu yüzden, bu çıkışa zor, "-n"ile echode birçok kabukları.

Gibi bazı kabukları üzerinde bash¹ veya ksh93² veya yash( $ECHO_STYLEdeğişken), davranış bile GNU nasıl kabuk derlenmiştir veya çevre (bağlıdır echo'eğer davranışı da değişecektir $POSIXLY_CORRECTortamda ve sürüm ile 4 , zsh' in onun ile bsd_echoseçeneği, bazı posixseçenekleri ile veya pdksh tabanlı olarak adlandırılmış olup olmadıklarını sh). Yani iki bash echoversiyonun aynı versiyondan bile aynı bashşekilde davranması garanti edilmiyor.

POSIX şöyle der: İlk argüman -nya da herhangi bir argüman ters eğik çizgi içeriyorsa, davranış belirtilmez . bashBu bağlamda yankı POSIX değil, örneğin POSIX'in gerektirdiği gibi çıktı echo -evermiyor -e<newline>. UNIX spesifikasyonu daha katıdır, çıktının durdurulması -ndahil olmak üzere bazı kaçış dizilerinin genişlemesini yasaklar ve gerektirir \c.

Bu şartnameler, birçok uygulamanın uyumlu olmadığı göz önüne alındığında, burada kurtarmaya gelmez. MacOS 5 gibi bazı sertifikalı sistemler bile uyumlu değildir.

Mevcut gerçekliği gerçekten temsil etmek için POSIX aslında şunu söylemelidir : ilk argüman ^-([eEn]*|-help|-version)$uzatılmış regexp ile eşleşirse veya herhangi bir argüman ters eğik çizgi içeriyorsa (veya kodlaması αBIG5 karakter dizisini kullanan yerellerde olduğu gibi ters eğik çizgi karakterinin kodlamasını içeren karakterler) ise, o zaman davranış belirtilmemiş.

Sonuçta, echo "$var"bunun $varters eğik çizgi karakterleri içermediğinden ve başlamadığından emin olmadıkça neyin çıkacağını bilemezsiniz -. POSIX özelliği aslında printfbu durumda kullanmamızı söylüyor .

Yani bunun anlamı echokontrolsüz verileri görüntülemek için kullanamamanızdır . Başka bir deyişle, bir komut dosyası yazıyorsanız ve harici girdi alıyorsa (kullanıcıdan argümanlar olarak veya dosya sisteminden dosya adları ...), echogörüntülemek için kullanamazsınız .

Tamamdır:

echo >&2 Invalid file.

Bu değil:

echo >&2 "Invalid file: $file"

(Her ne kadar seçenek bir şekilde veya derleme zamanında veya çevre yoluyla olduğu gibi başka bir şekilde etkinleştirilmemişse ) bazı (UNIX uyumlu olmayan) echouygulamalarla TAMAM çalışacaktır .bashxpg_echo

file=$(echo "$var" | tr ' ' _)çoğu uygulamada TAMAM değildir (istisnalar ile yashbirlikte olan ECHO_STYLE=raw( yashdeğişkenlerin baytların rasgele sıralarını tutamayacağı kadar ihtiraslı) ve zsh's echo -E - "$var"6 ).

printfÖte yandan daha güvenilir üzerinde, en azından bunun temel kullanım ile sınırlıdır ne zaman echo.

printf '%s\n' "$var"

Çıktı içeriğini Will $varolursa olsun içerebilir hangi karakteri bir satır karakteri ile izledi.

printf '%s' "$var"

Sondaki yeni satır karakteri olmadan yazacaktır.

Şimdi, printfuygulamalar arasında da farklılıklar var . POSIX tarafından belirtilen bir dizi temel özellik vardır, ancak daha sonra birçok uzantı vardır. Mesela, bazıları %qargümanları alıntılamak için bir a destekler ancak bunun nasıl yapıldığı, bazı \uxxxxunicode karakterleri destekleyen, kabuktan kabuğa değişir . Davranış printf '%10s\n' "$var", çok baytlı yerel ayarlarda değişir , bunun için en az üç farklı sonuç vardır.printf %b '\123'

Fakat sonunda, POSIX özellik kümesine bağlı printfkalırsanız ve onunla çok süslü bir şey yapmayı denemezseniz, başınız belada demektir.

Ancak ilk argümanın format olduğunu unutmayın, bu nedenle değişken / kontrolsüz veri içermemelidir.

Aşağıdakiler kullanılarak daha güvenilir bir echouygulama yapılabilir printf:

echo() ( # subshell for local scope for $IFS
  IFS=" " # needed for "$*"
  printf '%s\n' "$*"
)

echo_n() (
  IFS=" "
  printf %s "$*"
)

echo_e() (
  IFS=" "
  printf '%b\n' "$*"
)

Alt kabuk (çoğu kabuk uygulamasında fazladan bir işlem oluşturulması anlamına gelir), local IFSbirçok kabuk kullanılarak veya şöyle yazılarak önlenebilir :

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
  fi
  if [ "$#" -gt 0 ]; then
     printf ' %s' "$@"
  fi
  printf '\n'
}

notlar

1. nasıl bash'ın echodeğiştirilebilir davranışı.

İle bash, çalışma zamanında, davranışlarını kontrol iki şey vardır echo(yanında enable -n echoveya yeniden tanımlıyor echobir fonksiyonu veya takma gibi): xpg_echo bashseçeneği olup olmadığı bashPOSIX modunda. posixeğer modu etkin olabilir basholarak adlandırılır shveya eğer POSIXLY_CORRECTortamda veya ile posixopsiyon:

Çoğu sistemde varsayılan davranış:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echo UNIX'in gerektirdiği şekilde dizileri genişletiyor:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

Hala onurlandırıyor -nve -e(ve -E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

İle xpg_echove POSIX modu:

$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A

Bu sefer, bashhem POSIX hem de UNIX uyumludur. POSIX modunda, bashçıktı almadığı için hala POSIX uyumlu olmadığını unutmayın -e:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

Xpg_echo ve posix için varsayılan değerler , komut dosyası için --enable-xpg-echo-defaultve --enable-strict-posix-defaultseçenekleriyle derleme zamanında tanımlanabilir configure. Bu genellikle OS / X’in son sürümlerini oluşturmak için yapar /bin/sh. Aklı başında hiçbir Unix / Linux uygulama / dağıtım için bunu tipik olur /bin/bashgerçi . Aslında bu doğru değil, /bin/bashOracle'ın Solaris 11 ile birlikte (isteğe bağlı bir pakette) birlikte gönderildiği anlaşılıyor --enable-xpg-echo-default(Solaris 10'da böyle değildi).

2. Nasıl ksh93'ın echodavranışı değiştirilebilir.

İçinde ksh93, echokaçış dizilerini genişletip genişletmemek veya seçenekleri tanımak veya tanımak, $PATHve / veya $_AST_FEATURESortam değişkenlerinin içeriğine bağlıdır .

Eğer $PATHiçeren bir bileşeni içeren /5binveya /xpgöncesinde /binveya /usr/binsonra SysV / UNIX şekilde davranmaz bileşen (dizileri genişletir seçenekleri kabul etmez). Bulursa /ucbveya /bsdönce veya $_AST_FEATURES7 içeriyorsa UNIVERSE = ucb, BSD 3 yolunu kullanır ( -egenişlemeyi etkinleştirmek için tanır -n).

Varsayılan sistem bağımlıdır, Debian'daki BSD builtin getconf; getconf UNIVERSE(ksh93'ün son sürümlerinin çıktısına bakınız ):

$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD

3. Eko-e için BSD?

-eSeçeneğin işlenmesi için BSD referansı burada biraz yanıltıcıdır. Bu farklı ve uyumsuz echodavranışların çoğu AT&T'de tanıtıldı:

  • \n, \0ooo, \cProgramcı Work Bench'e (Unix V6 dayanarak), UNIX, ve dinlenme (içinde \b, \rUnix Sistem III ...) Ref .
  • -nUnix V7 içinde (Dennis Ritchie Ref tarafından )
  • -eUnix V8 içinde (Dennis Ritchie Ref tarafından )
  • -Ekendisi muhtemelen başlangıçta geldi bash( 1.13.5 sürümündeki CWRU / CWRU.chlog'dan Brian Fox’un 1992-10-18’de eklenmesi, GNU’nun echo10 gün sonra yayımlanan sh-utils-1.8’den kısa bir süre sonra kopyalanması)

İken echobir yerleşik shveya BSD destekledi -eonlar 90'ların bunun için Almquist kabuğunu kullanmaya başladı günden beri, tek başına echobu güne kadar yarar (orada desteklemediği FreeBSDecho hala desteklemiyor -eo destekliyor olsa da, -nbenzeri Unix V7 (ve ayrıca \cancak yalnızca son argümanın sonunda).

Ele alınması -eeklendi ksh93's echoBSD içinde zaman evrenin ve 2006 yılında piyasaya ksh93r sürümünde derleme zamanında devre dışı bırakılabilir.

4. 8.31’de GNU yankı davranış değişikliği

Coreutils 8.31 (ve yana bu taahhüt ), GNU echoşimdi POSIXLY_CORRECT ortamında olduğunda davranışını eşleştirmek için, varsayılan olarak dizilerini kaçış genişletir bash -o posix -O xpg_echo'ın echo(bkz yerleşiğini hata raporu ).

5. macOS echo

MacOS'un çoğu sürümü OpenGroup'tan UNIX sertifikası aldı .

Onların shyerleşik echoöyle olarak uyumludur bashile inşa (çok eski bir sürüm) xpg_echovarsayılan olarak etkin, ancak bunların tek başına echoyarar değildir. env echo -nbunun yerine hiçbir şey çıkarmaz -n<newline>, yerine env echo '\n'çıktı \n<newline>verir <newline><newline>.

Bu /bin/echo, ilk argüman ise newline çıktısını baskılayan FreeBSD'den gelen -nveya son argüman sona erdiğinde (1995'ten beri) \c, ancak UNIX'in gerektirdiği diğer ters eğik çizgi dizilerini bile desteklemeyendir \\.

6. echokeyfi veri verebilecek uygulamalar

Kesinlikle, ayrıca FreeBSD / MacOS o güvenebileceği konuşma /bin/echo(değil onların kabuk yukarıdaki echoyerleşiğini) nerede zsh'ler echo -E - "$var"veya yash' ler ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") yazılabilir:

/bin/echo "$var
\c"

Destekleyen -Eve -n(veya yapılandırılabilir) uygulamalar da şunları yapabilir:

echo -nE "$var
"

Ve zsh's echo -nE - "$var"( printf %s "$var") yazılabilir

/bin/echo "$var\c"

7. _AST_FEATURESve ASTUNIVERSE

_AST_FEATURESDoğrudan manipüle edilmesi anlamına gelmez, komut yürütme karşısında AST yapılandırma ayarlarını yaymak için kullanılır. Yapılandırma (belgelenmemiş) astgetconf()API üzerinden gerçekleştirilmek içindir . İçinde ksh93, getconfyerleşik ( builtin getconfçağırmakla veya çağırmakla etkindir command /opt/ast/bin/getconf) arayüzastgetconf()

Örneğin builtin getconf; getconf UNIVERSE = att, UNIVERSEayarı değiştirmek için att( echodiğer şeylerin yanı sıra SysV yöntemini kullanmaya neden olmak için) de yapardınız . Bunu yaptıktan sonra, $_AST_FEATURESiçerdiği ortam değişkenini fark edeceksiniz UNIVERSE = att.


13
İzolasyonda birçok erken unix gelişimi oldu ve 'arayüzü değiştirdiğinizde, adı değiştir' gibi iyi yazılım mühendisliği ilkeleri uygulanmadı.
Henk Langeveld

7
Bir not olarak, alıntı diziliminin bir parçası olarak kabuğun aksine dizileri echogenişletme avantajının (ve belki de yalnızca) avantajı, daha \xsonra bir NUL baytını (Unix'in başka bir yanlış tasarımının sıfır sınırlandırılmış olmasıdır) Sistemin yarısının (benzeri execve()) çağırdığı dizgiler, baytların rastgele dizilerini
alamazlar

Sven Maschek'in web sayfasında da görebileceğiniz printfgibi, çoğu uygulama yanlış yaptığından beri bir sorun gibi görünüyor. Farkında olduğum tek doğru uygulama boshiçeride ve Sven Maschek'in sayfası üzerinden gelen boş baytlarla ilgili sorunları listelemiyor \0.
schily

1
Bu harika bir cevap - yazdığınız için teşekkürler @ StéphaneChazelas.
JoshuaRLi

28

printfBiçimlendirme seçenekleri için kullanmak isteyebilirsiniz . echoBir değişkenin veya bir (basit) satırın değerini yazdırmak için faydalıdır, ancak hepsi bu kadardır. printftemelde C versiyonunun yapabildiğini yapabilir.

Örnek kullanım ve yetenekler:

Echo:

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf:

vech="bike"
printf "%s\n" "$vech"

Kaynaklar:


@ 0xC0000022L Düzeltildi Soruyu cevaplamak için acelemdeki yanlış siteye link verdiğimi fark etmedim. Katkılarınız ve düzeltmeleriniz için teşekkür ederiz.
NlightNFotis

7
Kullanılması echodeğişkeninin değeri meta karakterler içeriyorsa bir değişken yazdırmak için başarısız olabilir.
Keith Thompson,

17

Bir "avantaj", eğer böyle adlandırmak istersen, bunun gibi echobazı kaçış dizilerini yorumlamak istediğini söylemek zorunda kalmazsın \n. Onları yorumlamayı biliyor ve bunu -eyapmak zorunda kalmayacak .

printf "some\nmulti-lined\ntext\n"

(Not: Sonuncusu \ngereklidir, seçeneği echobelirtmediğiniz sürece, onu -nbelirtir)

e karşı

echo -e "some\nmulti-lined\ntext"

Geçen Not \niçinde printf. Günün sonunda, kullandığınız şey bir zevk ve ihtiyaç meselesidir : echoveya printf.


1
Doğru /usr/bin/echove bashyerleşik. dash, kshVe zshyerleşik echoihtiyaç duymaz -eters eğik çizgi bulunan karakterleri genişletmek için anahtarı.
Manatwork

Neden korku alıntılar? İfadeniz, bunun mutlaka gerçek bir avantaj olmadığı anlamına gelir.
Keith Thompson

2
@KeithThompson: aslında tüm geliyordu ima etmek, herkes onun bir avantaj düşünebilir olmasıdır.
0xC0000022L

Onu genişletebilirmiydin? Neden bir avantaj olmuyor? "Eğer öyle demek istiyorsan" tabiri, olmadığını düşündüğünüzü kuvvetle ima ediyor.
Keith Thompson,

2
Ya da sadece yapabilirsin printf '%s\n' 'foo\bar.
nyuszika7h

6

Dezavantajı printfise performanstır çünkü yerleşik kabuk echodaha hızlıdır. Bu, özellikle yeni bir komutun her birinin ağır Windows ek yüküne neden olduğu Cygwin'de devreye giriyor. Yankı-ağır programımı /bin/echokabuğun ekosundan kullanmam değiştiğinde performans neredeyse iki katına çıktı. Taşınabilirlik ve performans arasında bir takas. Her zaman kullanacağın bir smaç değil printf.


15
printfgünümüzde pek çok kabukta inşa edilmiştir (bash, dash, ksh, zsh, yash, bazı pdksh türevleri ... bu yüzden tipik olarak cygwin'de bulunan kabukları da içerir). Tek önemli istisnalar bazı pdkshtürevlerdir.
Stéphane Chazelas

Ancak bu baskı uygulamalarının çoğu bozuldu. printfBoş baytları çıktılamak için kullanmak istediğinizde bu önemlidir , ancak bazı uygulamalar, olmamasına rağmen biçim dizgisinde `\ c 'yorumunu yapar.
schily

2
@schily, format dizesinde printfkullanırsanız , davranışı POSIX tarafından belirtilmez \c, bu nedenle printfuygulamalar bu konuda istediklerini yapabilir. Örneğin, bazıları PWB'de olduğu gibi aynı davranır echo(printf'in çıkmasına neden olur), ksh bunu \cAkontrol karakterleri için kullanır (printf format argümanı için ve ne de ! $'...'İçin !). Bu NUL bayt baskı ile ilgisi var, ya da belki atıfta şeyi emin değil 's ? echoprintkshprintf '\c@'
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.