Mengapa printf lebih baik daripada gema?


547

Saya pernah mendengar bahwa printfitu lebih baik daripada echo. Saya hanya dapat mengingat satu contoh dari pengalaman saya di mana saya harus menggunakan printfkarena echotidak bekerja untuk memasukkan beberapa teks ke dalam beberapa program di RHEL 5.8 tetapi printfmelakukannya. Tapi ternyata, ada perbedaan lain, dan saya ingin menanyakan apa itu juga apakah ada kasus tertentu kapan harus menggunakan satu vs yang lain.


Tentang echo -e?
neverMind9

1
@ neverMind9 echo -etidak berfungsi sh, hanya di bash(untuk beberapa alasan), dan buruh pelabuhan, misalnya, menggunakan shsecara default untuk RUNperintahnya
Ciprian Tomoiagă

@ CiprianTomoiagă echo -ebagi saya bekerja di Terminal Android, yang pada dasarnya sh.
neverMind9

Jawaban:


756

Pada dasarnya, ini masalah portabilitas (dan keandalan).

Awalnya, echotidak menerima opsi apa pun dan tidak memperluas apa pun. Yang dilakukannya hanyalah mengeluarkan argumennya yang dipisahkan oleh karakter spasi dan diakhiri oleh karakter baris baru.

Sekarang, seseorang berpikir akan lebih baik jika kita bisa melakukan hal-hal seperti echo "\n\t"menampilkan karakter baris baru atau tab, atau memiliki opsi untuk tidak menampilkan karakter baris baru yang tertinggal.

Mereka kemudian berpikir lebih keras tetapi alih-alih menambahkan fungsionalitas itu ke shell (seperti di perlmana di dalam tanda kutip ganda, \tsebenarnya berarti karakter tab), mereka menambahkannya echo.

David Korn menyadari kesalahan itu dan memperkenalkan bentuk baru dari kutipan shell: $'...'yang kemudian disalin oleh bashdan zshtapi itu sudah sangat terlambat pada saat itu.

Sekarang ketika UNIX standar echomenerima argumen yang berisi dua karakter \dan t, bukannya menghasilkan mereka, itu menghasilkan karakter tab. Dan begitu ia melihat \cdalam argumen, ia berhenti mengeluarkan (jadi baris baru yang tertinggal juga bukan keluaran).

Kerang / vendor Unix lain / versi memilih untuk melakukannya secara berbeda: mereka menambahkan -eopsi untuk memperluas urutan melarikan diri, dan -nopsi untuk tidak menampilkan baris baru. Beberapa memiliki -Euntuk menonaktifkan urutan melarikan diri, beberapa memiliki -ntetapi tidak -e, daftar urutan melarikan diri yang didukung oleh satu echoimplementasi tidak harus sama dengan yang didukung oleh yang lain.

Sven Mascheck memiliki halaman yang bagus yang menunjukkan tingkat masalah .

Pada echoimplementasi yang mendukung opsi, umumnya tidak ada dukungan dari --untuk menandai akhir dari opsi ( echobuiltin dari beberapa shell yang tidak seperti Bourne lakukan, dan zsh mendukung -untuk itu), jadi misalnya, sulit untuk menghasilkan "-n"dengan echodi banyak kerang.

Pada beberapa shell seperti bash¹ atau ksh93² atau yash( $ECHO_STYLEvariabel), perilaku bahkan tergantung pada bagaimana shell dikompilasi atau lingkungan ( echoPerilaku GNU juga akan berubah jika $POSIXLY_CORRECTada di lingkungan dan dengan versi 4 , zshdengan bsd_echopilihannya, beberapa berbasis pdksh dengan posixopsi mereka atau apakah mereka dipanggil shatau tidak). Jadi dua bash echos, bahkan dari versi yang sama bashtidak dijamin berperilaku sama.

POSIX mengatakan: jika argumen pertama adalah -natau argumen apa pun mengandung garis miring terbalik, maka perilaku tersebut tidak ditentukan . bashgema dalam hal itu bukanlah POSIX dalam hal itu misalnya echo -etidak menghasilkan -e<newline>seperti yang diperlukan POSIX. Spesifikasi UNIX lebih ketat, melarang -ndan membutuhkan perluasan beberapa urutan pelarian termasuk yang \cberhenti mengeluarkan.

Spesifikasi itu tidak benar-benar datang ke penyelamatan di sini mengingat bahwa banyak implementasi tidak sesuai. Bahkan beberapa sistem bersertifikat seperti macOS 5 tidak sesuai.

Untuk benar-benar mewakili kenyataan saat ini, POSIX harus benar-benar mengatakan : jika argumen pertama cocok dengan ^-([eEn]*|-help|-version)$regexp yang diperluas atau argumen apa pun berisi garis miring terbalik (atau karakter yang pengkodeannya berisi pengkodean karakter garis miring terbalik seperti αdi lokal menggunakan charset BIG5), maka perilakunya adalah tidak ditentukan.

Secara keseluruhan, Anda tidak tahu apa yang echo "$var"akan dihasilkan kecuali Anda dapat memastikan bahwa $varitu tidak mengandung karakter backslash dan tidak dimulai dengan -. Spesifikasi POSIX sebenarnya memberitahu kita untuk menggunakannya printfsebagai gantinya.

Jadi itu artinya Anda tidak dapat menggunakan echountuk menampilkan data yang tidak terkontrol. Dengan kata lain, jika Anda menulis skrip dan sedang mengambil input eksternal (dari pengguna sebagai argumen, atau nama file dari sistem file ...), Anda tidak dapat menggunakannya echountuk menampilkannya.

Ini bagus:

echo >&2 Invalid file.

Ini bukan:

echo >&2 "Invalid file: $file"

(Meskipun itu akan bekerja OK dengan beberapa echoimplementasi (non-UNIX compliant) seperti bashketika xpg_echoopsi tidak diaktifkan dengan satu atau lain cara seperti pada waktu kompilasi atau melalui lingkungan).

file=$(echo "$var" | tr ' ' _)tidak OK di sebagian besar implementasi (pengecualian menjadi yashdengan ECHO_STYLE=raw(dengan peringatan bahwa yash's variabel tidak bisa menahan urutan sewenang-wenang dari byte sehingga tidak nama file sewenang-wenang) dan zsh' s echo -E - "$var"6 ).

printf, di sisi lain lebih dapat diandalkan, setidaknya ketika itu terbatas pada penggunaan dasar echo.

printf '%s\n' "$var"

Akan menampilkan konten $vardiikuti oleh karakter baris baru terlepas dari karakter apa yang dikandungnya.

printf '%s' "$var"

Akan menampilkannya tanpa karakter baris baru.

Sekarang, ada juga perbedaan antara printfimplementasi. Ada inti fitur yang ditentukan oleh POSIX, tetapi kemudian ada banyak ekstensi. Sebagai contoh, beberapa dukungan %quntuk mengutip argumen tetapi bagaimana itu dilakukan bervariasi dari shell ke shell, beberapa dukungan \uxxxxuntuk karakter unicode. Perilaku bervariasi untuk printf '%10s\n' "$var"di multi-byte locales, setidaknya ada tiga hasil yang berbeda untukprintf %b '\123'

Tetapi pada akhirnya, jika Anda tetap menggunakan set fitur POSIX printfdan tidak mencoba melakukan sesuatu yang terlalu mewah, Anda keluar dari masalah.

Tapi ingat argumen pertama adalah format, jadi tidak boleh berisi data variabel / tidak terkendali.

Yang lebih andal echodapat diimplementasikan menggunakan printf, seperti:

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

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

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

Subkulit (yang menyiratkan pemijahan proses ekstra di sebagian besar implementasi shell) dapat dihindari menggunakan local IFSdengan banyak shell, atau dengan menuliskannya seperti:

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

Catatan

1. bagaimana bash's echoperilaku bisa diubah.

Dengan bash, pada saat dijalankan, ada dua hal yang mengontrol perilaku echo(di samping enable -n echoatau mendefinisikan kembali echosebagai fungsi atau alias): xpg_echo bashopsi dan apakah bashdalam mode posix. posixmode dapat diaktifkan jika bashdipanggil sebagai shatau jika POSIXLY_CORRECTada di lingkungan atau dengan posixopsi:

Perilaku default pada kebanyakan sistem:

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

xpg_echo perluas urutan sesuai kebutuhan UNIX:

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

Itu masih menghormati -ndan -e(dan -E):

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

Dengan xpg_echodan mode POSIX:

$ 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

Kali ini, bashadalah POSIX dan UNIX yang sesuai. Perhatikan bahwa dalam mode POSIX, bashmasih belum memenuhi POSIX karena tidak menghasilkan -e:

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

$

Nilai default untuk xpg_echo dan posix dapat didefinisikan pada waktu kompilasi dengan opsi --enable-xpg-echo-defaultdan --enable-strict-posix-defaultke configureskrip. Biasanya itulah yang dilakukan versi OS / X terbaru untuk membuatnya /bin/sh. Tidak ada implementasi / distribusi Unix / Linux yang waras biasanya akan melakukannya untuk /bin/bashsaat ini . Sebenarnya, itu tidak benar, /bin/bashbahwa Oracle dikirimkan bersama Solaris 11 (dalam paket opsional) tampaknya dibangun dengan --enable-xpg-echo-default(itu tidak terjadi di Solaris 10).

2. Bagaimana ksh93's echoperilaku bisa diubah.

Dalam ksh93, apakah echomemperluas urutan lolos atau tidak dan mengenali opsi tergantung pada konten variabel $PATHdan / atau $_AST_FEATURESlingkungan.

Jika $PATHberisi komponen yang mengandung /5binatau /xpgsebelum /binatau /usr/binkomponen maka itu berperilaku cara SysV / UNIX (memperluas urutan, tidak menerima opsi). Jika ia menemukan /ucbatau /bsdpertama atau jika $_AST_FEATURES7 berisi UNIVERSE = ucb, maka ia berperilaku dengan cara BSD 3 ( -euntuk memungkinkan ekspansi, mengenali -n).

Standarnya tergantung pada sistem, BSD pada Debian (lihat output builtin getconf; getconf UNIVERSEdalam versi terbaru dari ksh93):

$ 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. BSD untuk echo -e?

Referensi ke BSD untuk penanganan -eopsi agak menyesatkan di sini. Sebagian besar echoperilaku yang berbeda dan tidak kompatibel itu semua diperkenalkan di AT&T:

  • \n, \0ooo, \cDi Programmer Kerja Bench UNIX (berdasarkan Unix V6), dan sisanya ( \b, \r...) di Unix System III Ref .
  • -ndalam Unix V7 (oleh Dennis Ritchie Ref )
  • -edalam Unix V8 (oleh Dennis Ritchie Ref )
  • -Esendiri mungkin awalnya berasal dari bash(CWRU / CWRU.chlog dalam versi 1.13.5 menyebutkan Brian Fox menambahkannya pada 1992-10-18, GNU echomenyalinnya segera setelah di sh-utils-1.8 dirilis 10 hari kemudian)

Sementara echobuiltin dari shatau BSD telah mendukung -esejak hari mereka mulai menggunakan shell Almquist untuk itu di awal 90-an, echoutilitas mandiri hingga hari ini tidak mendukungnya di sana ( FreeBSDecho masih tidak mendukung -e, meskipun ia mendukung -nseperti Unix V7 (dan juga \ctetapi hanya pada akhir argumen terakhir)).

Penanganan -editambahkan ke ksh93's echoketika di BSD alam semesta dalam versi ksh93r dirilis pada tahun 2006 dan dapat dinonaktifkan pada saat kompilasi.

4. GNU gema perubahan perilaku di 8.31

Sejak coreutils 8.31 (dan ini komit ), GNU echosekarang memperluas escape sequence secara default ketika POSIXLY_CORRECT adalah di lingkungan, untuk mencocokkan perilaku bash -o posix -O xpg_echo's echobuiltin (lihat laporan bug ).

5. macOS echo

Sebagian besar versi macOS telah menerima sertifikasi UNIX dari OpenGroup .

shBuiltin mereka echokompatibel karena bash(versi yang sangat lama) dibangun dengan xpg_echodiaktifkan secara default, tetapi echoutilitas yang berdiri sendiri tidak. env echo -ntidak menghasilkan apa-apa -n<newline>, env echo '\n'output \n<newline>bukan <newline><newline>.

Itu /bin/echoadalah salah satu dari FreeBSD yang menekan output baris baru jika argumen pertama adalah -natau (sejak 1995) jika argumen terakhir berakhir \c, tetapi tidak mendukung urutan backslash lainnya yang diperlukan oleh UNIX, bahkan tidak \\.

6. echoimplementasi yang dapat menghasilkan data sembarang kata demi kata

Sebenarnya, Anda juga dapat menghitung bahwa FreeBSD / macOS di /bin/echoatas (bukan bawaan shell mereka echo) di mana zsh's echo -E - "$var"atau yash' ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") dapat ditulis:

/bin/echo "$var
\c"

Implementasi yang mendukung -Edan -n(atau dapat dikonfigurasi untuk) juga dapat melakukan:

echo -nE "$var
"

Dan zsh's echo -nE - "$var"( printf %s "$var") dapat ditulis

/bin/echo "$var\c"

7. _AST_FEATURESdan ASTUNIVERSE

Itu _AST_FEATUREStidak dimaksudkan untuk dimanipulasi secara langsung, itu digunakan untuk menyebarkan pengaturan konfigurasi AST di seluruh eksekusi perintah. Konfigurasi ini dimaksudkan untuk dilakukan melalui astgetconf()API (tidak berdokumen) . Di dalam ksh93, getconfbuiltin (diaktifkan dengan builtin getconfatau dengan memanggil command /opt/ast/bin/getconf) adalah antarmuka untukastgetconf()

Misalnya, Anda harus builtin getconf; getconf UNIVERSE = attmengubah UNIVERSEpengaturan ke att(menyebabkan echoberperilaku SysV antara lain). Setelah melakukan itu, Anda akan melihat $_AST_FEATURESvariabel lingkungan berisi UNIVERSE = att.


13
Banyak pengembangan unix awal terjadi secara terpisah, dan prinsip-prinsip rekayasa perangkat lunak yang baik seperti 'ketika Anda mengubah antarmuka, mengubah nama' tidak diterapkan.
Henk Langeveld

7
Sebagai catatan, satu-satunya (dan mungkin hanya) keuntungan dari echomemperluas \xurutan yang bertentangan dengan shell sebagai bagian dari sintaks kutipan adalah bahwa Anda kemudian dapat menghasilkan byte NUL (desain lain yang bisa dibilang salah dari Unix adalah mereka yang tidak dibatasi oleh nol string, di mana separuh dari panggilan sistem (seperti execve()) tidak dapat mengambil urutan byte yang sewenang-wenang)
Stéphane Chazelas

Seperti yang Anda lihat di halaman web dari Sven Maschek, bahkan printftampaknya menjadi masalah karena sebagian besar implementasi salah. Satu-satunya implementasi yang benar yang saya ketahui ada di boshdan halaman dari Sven Maschek tidak mencantumkan masalah dengan null bytes \0.
schily

1
Ini adalah jawaban yang bagus - terima kasih @ StéphaneChazelas untuk menulisnya.
JoshuaRLi

28

Anda mungkin ingin menggunakan printfopsi pemformatannya. echoberguna ketika datang untuk mencetak nilai dari suatu variabel atau (sederhana) baris, tapi hanya itu yang ada untuk itu. printfpada dasarnya dapat melakukan apa yang dapat dilakukan versi C.

Contoh penggunaan dan kemampuan:

Echo:

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

printf:

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

Sumber:


@ 0xC0000022L Saya berdiri dikoreksi terima kasih. Saya tidak menyadari bahwa saya terhubung ke situs yang salah dalam kesibukan saya untuk menjawab pertanyaan itu. Terima kasih atas kontribusi dan koreksi Anda.
NlightNFotis

7
Menggunakan echountuk mencetak variabel bisa gagal jika nilai variabel berisi meta-karakter.
Keith Thompson

17

Satu "keuntungan", jika Anda ingin menyebutnya demikian, adalah Anda tidak perlu mengatakannya echountuk menafsirkan urutan pelarian tertentu seperti \n. Ia tahu untuk menafsirkannya dan tidak akan memerlukan -euntuk melakukannya.

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

(NB: yang terakhir \ndiperlukan, echomenyiratkannya, kecuali jika Anda memberi -npilihan)

melawan

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

Perhatikan yang terakhir \ndi printf. Pada akhirnya, itu soal selera dan persyaratan apa yang Anda gunakan: echoatau printf.


1
Benar untuk /usr/bin/echodan bashbuiltin. The dash, kshdan zshbuiltin echotidak perlu -eberalih untuk memperluas karakter backslash-escape.
manatwork

Mengapa kutipan menakut-nakuti? Kata-kata Anda menyiratkan bahwa itu belum tentu merupakan keuntungan nyata.
Keith Thompson

2
@KeithThompson: sebenarnya yang mereka maksudkan adalah tidak semua orang menganggapnya sebagai keuntungan.
0xC0000022L

Bisakah Anda mengembangkannya? Mengapa itu tidak menguntungkan? Ungkapan "jika Anda ingin menyebutnya" menyiratkan cukup kuat bahwa Anda pikir itu tidak benar.
Keith Thompson

2
Atau Anda bisa melakukannya printf '%s\n' 'foo\bar.
nyuszika7h

6

Salah satu kelemahan printfadalah kinerja karena shell bawaan echojauh lebih cepat. Ini mulai berlaku khususnya di Cygwin di mana setiap instance dari perintah baru menyebabkan overhead Windows yang besar. Ketika saya mengubah program gema-berat saya dari menggunakan /bin/echoke gema shell kinerja hampir dua kali lipat. Ini merupakan trade off antara portabilitas dan kinerja. Ini bukan slam dunk untuk selalu digunakan printf.


15
printfdibangun di sebagian besar shell saat ini (bash, dash, ksh, zsh, yash, beberapa turunan pdksh ... jadi sertakan juga shell yang biasanya ditemukan di cygwin). Satu-satunya pengecualian adalah beberapa pdkshturunan.
Stéphane Chazelas

Tetapi banyak dari implementasi printf yang rusak. Ini sangat penting ketika Anda ingin menggunakan printfuntuk menghasilkan byte byte, tetapi beberapa implementasi menafsirkan `\ c 'dalam format string walaupun seharusnya tidak.
schily

2
@schily, perilaku printftidak ditentukan oleh POSIX jika Anda menggunakan \cdalam format string, sehingga printfimplementasi dapat melakukan apapun yang mereka inginkan dalam hal itu. Sebagai contoh, beberapa memperlakukannya sama seperti di PWB echo(menyebabkan printf keluar), ksh menggunakannya untuk \cAkarakter kontrol (untuk argumen format printf dan untuk $'...'tetapi tidak untuk echoatau print!). Tidak yakin apa yang harus dilakukan dengan mencetak NUL byte, atau mungkin Anda mengacu pada ksh's printf '\c@'?
Stéphane Chazelas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.