Apa yang salah dengan “echo $ (stuff)” atau “echo` stuff` ”?

31

Saya menggunakan salah satu dari yang berikut ini

echo $(stuff)
echo `stuff`

(Di mana stuffmisalnya pwdatau dateatau sesuatu yang lebih rumit).

Kemudian saya diberitahu bahwa sintaks ini salah, praktik buruk, tidak elegan, berlebihan, berlebihan, terlalu rumit, pemrograman pemujaan kargo, noobish, naif, dll.

Tetapi perintah itu berhasil, jadi apa sebenarnya yang salah dengannya?

Kamil Maciorowski
sumber
11
Ini mubazir dan karenanya tidak boleh digunakan. Bandingkan dengan Penggunaan Kucing yang Tidak Berguna
dr01
7
echo $(echo $(echo $(echo$(echo $(stuff)))))juga tidak berfungsi, dan Anda mungkin masih tidak akan menggunakannya, kan? ;-)
Peter - Reinstate Monica
1
Apa yang sebenarnya Anda coba lakukan dengan ekspansi itu?
curiousguy
@curiousguy Saya mencoba membantu pengguna lain, maka jawaban saya di bawah ini. Baru-baru ini, saya telah melihat setidaknya tiga pertanyaan yang menggunakan sintaksis ini. Penulis mereka berusaha melakukan ... berbagai stuff. : D
Kamil Maciorowski
2
Juga, bukankah kita semua sepakat bahwa tidak bijaksana untuk membatasi diri ke ruang gema ?? ;-)
Peter - Reinstate Monica

Jawaban:

63

tl; dr

Sebuah tunggal stuffakan paling mungkin bekerja untuk Anda.



Jawaban penuh

Apa yang terjadi

Ketika Anda berlari foo $(stuff), inilah yang terjadi:

  1. stuff berjalan;
  2. outputnya (stdout), bukannya dicetak, menggantikan $(stuff)dalam doa foo;
  3. kemudian fooberjalan, argumen baris perintahnya jelas tergantung pada apa yang stuffdikembalikan.

Ini $(…)mekanisme yang disebut "perintah substitusi". Dalam kasus Anda, perintah utama adalah echoyang pada dasarnya mencetak argumen baris perintahnya ke stdout. Jadi apapun yang stuffmencoba untuk mencetak ke stdout ditangkap, diteruskan ke echodan dicetak ke stdout oleh echo.

Jika Anda ingin keluaran stuffdicetak ke stdout, jalankan solnya stuff.

The `…`sintaks melayani tujuan yang sama sebagai $(…)(dengan nama yang sama: "perintah substitusi"), ada beberapa perbedaan, jadi Anda tidak bisa begitu saja interchange mereka. Lihat FAQ ini dan pertanyaan ini .


Haruskah saya menghindari echo $(stuff)apa pun?

Ada alasan yang mungkin ingin Anda gunakan echo $(stuff)jika Anda tahu apa yang Anda lakukan. Untuk alasan yang sama Anda harus menghindari echo $(stuff)jika Anda tidak benar-benar tahu apa yang Anda lakukan.

Intinya adalah stuffdan echo $(stuff)tidak persis sama. Yang terakhir berarti memanggil operator split + glob pada output stuffdengan nilai default $IFS. Pengutipan perintah berlapis ganda mencegah hal ini. Mengutip substitusi perintah tunggal membuatnya tidak lagi menjadi substitusi perintah.

Untuk mengamati ini ketika melakukan splitting, jalankan perintah berikut:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

Dan untuk globbing:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Seperti yang Anda lihat echo "$(stuff)"adalah setara (-ish *) untuk stuff. Anda bisa menggunakannya tetapi apa gunanya menyulitkan dengan cara ini?

Di sisi lain, jika Anda ingin output stuffmengalami splitting + globbing maka Anda mungkin menemukan echo $(stuff)berguna. Itu harus menjadi keputusan sadar Anda.

Ada perintah yang menghasilkan keluaran yang harus dievaluasi (yang meliputi pemisahan, globbing dan banyak lagi) dan dijalankan oleh shell, jadi eval "$(stuff)"ada kemungkinan (lihat jawaban ini ). Saya belum pernah melihat perintah yang membutuhkan outputnya untuk menjalani splitting + globbing tambahan sebelum dicetak . Penggunaan yang disengaja echo $(stuff)tampaknya sangat jarang.


Bagaimana dengan var=$(stuff); echo "$var"?

Poin bagus. Cuplikan ini:

var=$(stuff)
echo "$var"

harus setara dengan echo "$(stuff)"setara (-ish *) untuk stuff. Jika seluruh kode, jalankan stuffsaja.

Namun, jika Anda perlu menggunakan output stufflebih dari satu kali maka pendekatan ini

var=$(stuff)
foo "$var"
bar "$var"

biasanya lebih baik daripada

foo "$(stuff)"
bar "$(stuff)"

Bahkan jika fooada echodan Anda mendapatkan echo "$var"dalam kode Anda, mungkin lebih baik untuk tetap seperti ini. Hal yang perlu dipertimbangkan:

  1. Dengan var=$(stuff) stuffsekali berlari; bahkan jika perintahnya cepat, menghindari penghitungan output yang sama dua kali adalah hal yang benar. Atau mungkin stuffmemiliki efek selain menulis ke stdout (misalnya membuat file sementara, memulai layanan, memulai mesin virtual, memberi tahu server jauh), jadi Anda tidak ingin menjalankannya berkali-kali.
  2. Jika stuffmenghasilkan keluaran tergantung waktu atau agak acak, Anda mungkin mendapatkan hasil yang tidak konsisten dari foo "$(stuff)"dan bar "$(stuff)". Setelah var=$(stuff)nilai $vardiperbaiki dan Anda dapat yakin foo "$var"dan bar "$var"mendapatkan argumen baris perintah yang identik.

Dalam beberapa kasus, alih-alih foo "$var"Anda mungkin ingin (perlu) untuk menggunakan foo $var, terutama jika stuffmenghasilkan beberapa argumen untuk foo(variabel array mungkin lebih baik jika shell Anda mendukungnya). Sekali lagi, ketahuilah apa yang Anda lakukan. Ketika datang ke echoperbedaan antara echo $vardan echo "$var"sama dengan antara echo $(stuff)dan echo "$(stuff)".


* Setara ( -ish )?

Saya katakan echo "$(stuff)"setara (-ish) untuk stuff. Setidaknya ada dua masalah yang membuatnya tidak persis sama:

  1. $(stuff)berjalan stuffdalam subkulit, jadi lebih baik untuk mengatakan echo "$(stuff)"itu setara (-ish) untuk (stuff). Perintah yang memengaruhi shell yang dijalankan, jika dalam subkulit, tidak memengaruhi shell utama.

    Dalam contoh ini stuffadalah a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    Bandingkan dengan

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    dan dengan

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Contoh lain, mulailah dengan stuffmenjadi cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    dan tes stuffdan (stuff)versi.

  2. echobukan alat yang baik untuk menampilkan data yang tidak terkontrol . Ini yang echo "$var"kita bicarakan seharusnya printf '%s\n' "$var". Tetapi karena pertanyaan itu menyebutkan echodan karena solusi yang paling mungkin adalah tidak menggunakan echosejak awal, saya memutuskan untuk tidak memperkenalkannya printfsampai sekarang.

  3. stuffatau (stuff)akan interleave output stdout dan stderr, sementara echo $(stuff)akan mencetak semua output stderr dari stuff(yang berjalan pertama), dan hanya kemudian output stdout dicerna oleh echo(yang berjalan terakhir).

  4. $(…)menghapus semua baris baru yang tertinggal dan kemudian echomenambahkannya kembali. Jadi echo "$(printf %s 'a')" | xxdmemberikan output yang berbeda dari printf %s 'a' | xxd.

  5. Beberapa perintah ( lsmisalnya) bekerja secara berbeda tergantung apakah output standar adalah konsol atau tidak; begitu ls | catjuga tidak sama ls. Demikian pula echo $(ls)akan bekerja secara berbeda dari ls.

    Mengesampingkan ls, dalam kasus umum jika Anda harus memaksakan perilaku lain ini maka stuff | catlebih baik daripada echo $(ls)atau echo "$(ls)"karena itu tidak memicu semua masalah lain yang disebutkan di sini.

  6. Status keluar yang sangat berbeda (disebutkan untuk kelengkapan jawaban wiki ini; untuk perincian lihat jawaban lain yang layak mendapat pujian).

Kamil Maciorowski
sumber
5
Satu lagi perbedaan: stuffcaranya akan interleave stdoutdan stderroutput, sementara echo `stuff` akan mencetak semua stderroutput dari stuff, dan hanya kemudian stdoutoutput.
Ruslan
3
Dan ini adalah contoh lain untuk: Hampir tidak ada terlalu banyak kutipan dalam skrip bash. echo $(stuff)dapat menempatkan Anda ke masalah yang jauh lebih banyak seolah- echo "$(stuff)"olah Anda tidak ingin globbing dan ekspansi secara eksplisit.
rexkogitans
2
Satu lagi sedikit perbedaan: $(…)menghapus semua baris baru dan kemudian echomenambahkannya kembali. Jadi echo "$(printf %s 'a')" | xxdmemberikan output yang berbeda dari printf %s 'a' | xxd.
derobert
1
Anda tidak boleh lupa bahwa beberapa perintah ( lsmisalnya) bekerja secara berbeda tergantung apakah output standar adalah konsol atau tidak; begitu ls | catjuga tidak sama ls. Dan tentu saja echo $(ls)akan bekerja berbeda dari ls.
Martin Rosenau
@Ruslan Jawabannya sekarang adalah wiki komunitas. Saya telah menambahkan komentar Anda yang bermanfaat ke wiki. Terima kasih.
Kamil Maciorowski
5

Perbedaan lain: Kode keluar sub-shell hilang, jadi kode keluar dari echodiambil sebagai gantinya.

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
WaffleSouffle
sumber
3
Selamat Datang di Pengguna Super! Bisakah Anda menjelaskan perbedaan yang Anda tunjukkan? Terima kasih
bertieb
4
Saya percaya bahwa ini menunjukkan bahwa kode pengembalian $?akan menjadi kode pengembalian echo(untuk echo $(stuff)) alih-alih yang returndiedit oleh stuff. The stuffFungsi return 1(kode keluar), tapi echoset ke "0" terlepas dari kode kembali stuff. Masih harus dijawab.
Ismael Miguel
nilai kembali dari subkulit telah hilang
phuclv