Apa manfaat menggunakan $ () daripada backtick di skrip shell?

175

Ada dua cara untuk menangkap output dari baris perintah di bash:

  1. Backticks shell Legacy Bourne ``:

    var=`command`
  2. $() sintaks (yang sejauh yang saya tahu adalah spesifik Bash, atau setidaknya tidak didukung oleh shell lama non-POSIX seperti Bourne asli)

    var=$(command)

Apakah ada manfaat menggunakan sintaks kedua dibandingkan dengan backticks? Atau apakah keduanya sepenuhnya 100% setara?

DVK
sumber
15
$()POSIX dan didukung oleh semua shell Bourne modern, misalnya ksh, bash, ash, dash, zsh, busybox, sebut saja. (Yang tidak begitu modern adalah Solaris /bin/sh, tetapi pada Solaris Anda akan memastikan untuk menggunakan yang modern /usr/xpg4/bin/shsebagai gantinya).
Jens
27
Juga, catatan tentang penggunaan $()dan backticks di alias. Jika sudah ada alias foo=$(command)di Anda .bashrcmaka commandakan dieksekusi ketika perintah alias itu sendiri dijalankan saat .bashrcinterpretasi. Dengan alias foo=`command`, commandakan dieksekusi setiap kali alias dijalankan. Tetapi jika Anda melarikan diri $dengan $()formulir (misalnya alias foo=\$(command)), itu juga akan mengeksekusi setiap kali alias dijalankan, alih-alih selama .bashrcinterpretasi. Sejauh yang saya tahu dari pengujian, bagaimanapun; Saya tidak dapat menemukan apa pun di bash docs yang menjelaskan perilaku ini.
dirtside
4
@dirtside Shell mana ini, saya telah menguji bash dan POSIX shell, backtick dieksekusi ketika saya sumber. Contoh sederhana: alias curDate = `date` Setelah saya sumber dan jalankan curDate, maka saya mendapatkan pesan tidak dapat menemukan perintah Mon (bersumber pada hari Senin), misalnya.
thecarpy
@dirtside Itu tidak benar. Bahkan dengan alias foo = `command` commanddijalankan hanya satu kali. Saya memeriksanya: function aaa () {printf date; echo aaa >> ~ / test.txt; } alias test1 = aaa. Fungsi aaa hanya mengeksekusi satu kali (setelah setiap login) tidak peduli berapa kali alias ( test1) dieksekusi. Saya menggunakan .bashrc (di Debian 10).
vitaliydev
Tidak tahu, itu adalah versi bash apa pun yang saya gunakan lima setengah tahun yang lalu, yang mungkin sudah cukup tua bahkan pada saat itu. Mungkin saja pengujian saya salah, tetapi sekarang tidak masalah karena saya ragu ada yang masih menggunakan versi apa pun itu.
dirtside

Jawaban:

156

Yang utama adalah kemampuan untuk bersarang mereka, perintah dalam perintah, tanpa kehilangan kewarasan Anda mencoba mencari tahu apakah beberapa bentuk pelarian akan bekerja pada backticks.

Contoh, meskipun agak dibuat-buat:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

yang akan memberi Anda daftar semua file di /dirpohon direktori yang memiliki nama yang sama dengan file teks tanggal paling awal dari Desember 2011 (a) .

Contoh lain adalah sesuatu seperti mendapatkan nama (bukan path lengkap) dari direktori induk:

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(a) Sekarang perintah spesifik itu mungkin tidak benar-benar berfungsi, saya belum menguji fungsi. Jadi, jika Anda memilih saya untuk itu, Anda telah kehilangan pandangan :-) Ini dimaksudkan hanya sebagai ilustrasi tentang bagaimana Anda bisa bersarang, bukan sebagai cuplikan siap produksi bebas bug.

paxdiablo
sumber
87
Saya berharap semua kode pada SO menjadi cuplikan siap produksi, yang dirancang untuk standar keandalan kode antar-jemput NASA. Yang kurang mendapat bendera dan penghapusan suara.
DVK
1
@DVK Jika Anda tidak bercanda, saya tidak setuju bahwa kontribusi kode harus ditandai karena gagal secara otomatis menjauhkan diri dari standar SO (lisensi CC-BY-SA atau MIT) untuk memungkinkan jaminan atau kesesuaian tujuan tersebut. Saya malah akan menggunakan kembali kode pada SO dengan risiko saya sendiri, dan memilih kontribusi sesuai dengan manfaat, manfaat teknis, dll.
chrstphrchvz
9
@chrstphrchvz, jika Anda melihat profil saya, Anda akan melihat cuplikan kecil ini: "Semua kode yang saya poskan di Stack Overflow dicakup oleh lisensi" Lakukan apa pun yang Anda inginkan dengannya ", teks lengkapnya adalah: Do whatever the heck you want with it." :-) Bagaimanapun, saya cukup yakin itu adalah humor dari DVK.
paxdiablo
1
tapi bagaimana kalau itu "cd / home / pax / xyzzy / plover"? Akankah Anda menemukan diri Anda dalam labirin lorong-lorong kecil yang berkelok-kelok, semua berbeda?
Duncan
@wchargin, tahukah Anda bahwa kejadian ini adalah motivator utama untuk penambahan literal definisi yang digunakan ke bahasa C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod
59

Misalkan Anda ingin menemukan direktori lib yang sesuai dengan tempat gccdiinstal. Anda punya pilihan:

libdir=$(dirname $(dirname $(which gcc)))/lib

libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

Yang pertama lebih mudah daripada yang kedua - gunakan yang pertama.

Jonathan Leffler
sumber
4
Akan lebih baik untuk melihat beberapa kutipan di sekitar penggantian perintah itu!
Tom Fenech
1
Setidaknya untuk bash, komentar @TomFenech tidak berlaku untuk tugas. x=$(f); x=`f` berperilaku sama dengan x="$(f)"; x="`f`". Sebaliknya, tugas array x=($(f)); x=(`f`) tidak melakukan pemisahan $IFSkarakter seperti yang diharapkan ketika menjalankan perintah. Ini nyaman ( x=1 2 3 4tidak masuk akal) tetapi tidak konsisten.
kdb
@ KdB Anda benar tentang x=$(f)bekerja tanpa tanda kutip. Saya seharusnya lebih spesifik; Saya mengusulkan untuk menggunakan libdir=$(dirname "$(dirname "$(which gcc)")")/lib(mengutip sekitar pergantian perintah dalam ). Jika tidak kutip, Anda masih tunduk pada pemisahan kata dan ekspansi gumpalan yang biasa.
Tom Fenech
38

Backticks ( `...`) adalah sintaksis lama yang hanya diperlukan oleh bourne-shells tertua yang tidak kompatibel dengan $(...)POSIX dan POSIX dan lebih disukai karena beberapa alasan:

  • Backslash ( \) di dalam backticks ditangani dengan cara yang tidak jelas:

    $ echo "`echo \\a`" "$(echo \\a)"
    a \a
    $ echo "`echo \\\\a`" "$(echo \\\\a)"
    \a \\a
    # Note that this is true for *single quotes* too!
    $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" 
    foo is \, bar is \\
  • Mengutip bersarang di dalam $()jauh lebih mudah:

    echo "x is $(sed ... <<<"$y")"

    dari pada:

    echo "x is `sed ... <<<\"$y\"`"

    atau menulis sesuatu seperti:

    IPs_inna_string=`awk "/\`cat /etc/myname\`/"'{print $1}' /etc/hosts`

    karena $()menggunakan konteks yang sama sekali baru untuk mengutip

    yang tidak portabel seperti cangkang Bourne dan Korn akan membutuhkan backslash ini, sedangkan Bash dan dash tidak.

  • Sintaks untuk pergantian perintah bersarang lebih mudah:

    x=$(grep "$(dirname "$path")" file)

    dari:

    x=`grep "\`dirname \"$path\"\`" file`

    karena $()memberlakukan konteks yang sama sekali baru untuk mengutip, sehingga setiap penggantian perintah dilindungi dan dapat diperlakukan sendiri tanpa perhatian khusus pada penawaran dan pelolosan. Saat menggunakan backticks, itu menjadi lebih buruk dan lebih buruk setelah dua tingkat di atas.

    Beberapa contoh lagi:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Ini memecahkan masalah perilaku yang tidak konsisten saat menggunakan backquotes:

    • echo '\$x' output \$x
    • echo `echo '\$x'` output $x
    • echo $(echo '\$x') output \$x
  • Sintaks Backticks memiliki batasan historis pada konten perintah yang disematkan dan tidak dapat menangani beberapa skrip yang valid yang menyertakan backquote, sementara $()formulir yang lebih baru dapat memproses segala jenis skrip tertanam yang valid.

    Misalnya, skrip tertanam yang valid ini tidak berfungsi di kolom kiri, tetapi berfungsi di IEEE kanan :

    echo `                         echo $(
    cat <<\eof                     cat <<\eof
    a here-doc with `              a here-doc with )
    eof                            eof
    `                              )
    
    
    echo `                         echo $(
    echo abc # a comment with `    echo abc # a comment with )
    `                              )
    
    
    echo `                         echo $(
    echo '`'                       echo ')'
    `                              )

Oleh karena itu sintaks untuk substitusi perintah$ -prefixed harus menjadi metode yang disukai, karena itu jelas secara visual dengan sintaks bersih (meningkatkan manusia dan keterbacaan mesin), itu nestable dan intuitif, penguraian bagian dalamnya terpisah, dan itu juga lebih konsisten (dengan semua ekspansi lain yang diuraikan dari dalam tanda kutip ganda) di mana backtick adalah satu-satunya pengecualian dan karakter mudah disamarkan ketika berdekatan dengan membuatnya lebih sulit untuk dibaca, terutama dengan font kecil atau tidak biasa.`"

Sumber: Mengapa $(...)lebih disukai daripada `...`(backticks)? di BashFAQ

Lihat juga:

kenorb
sumber
1
Kutipan bersarang dalam aksen substitusi gaya gravis sebenarnya tidak terdefinisi, Anda dapat menggunakan tanda kutip ganda baik di luar maupun di dalam tetapi tidak keduanya, mudah dibawa. Kerang menafsirkannya secara berbeda; beberapa membutuhkan backslash untuk melarikan diri dari mereka, beberapa mengharuskan mereka tidak melarikan diri backslash.
mirabilos
23

Dari man bash:

          $(command)
   or
          `command`

   Bash performs the expansion by executing command and replacing the com-
   mand  substitution  with  the  standard output of the command, with any
   trailing newlines deleted.  Embedded newlines are not deleted, but they
   may  be  removed during word splitting.  The command substitution $(cat
   file) can be replaced by the equivalent but faster $(< file).

   When the old-style backquote form of substitution  is  used,  backslash
   retains  its  literal  meaning except when followed by $, `, or \.  The
   first backquote not preceded by a backslash terminates the command sub-
   stitution.   When using the $(command) form, all characters between the
   parentheses make up the command; none are treated specially.
dgw
sumber
9

Selain jawaban lainnya,

$(...)

menonjol secara visual lebih baik daripada

`...`

Backticks terlihat terlalu mirip apostrof; ini bervariasi tergantung pada font yang Anda gunakan.

(Dan, seperti yang baru saja saya perhatikan, backticks jauh lebih sulit untuk dimasukkan dalam sampel kode inline.)

Keith Thompson
sumber
2
Anda harus memiliki keyboard yang aneh (atau saya lakukan?). Bagi saya, jauh lebih mudah untuk mengetikkan backticks - mereka adalah tombol sudut kiri atas, tidak perlu SHIFT atau ALT.
DVK
1
@DVK: Saya berbicara tentang penampilan mereka, tidak mudah mengetik. (Keyboard saya mungkin sama dengan milik Anda.) Namun, sekarang setelah Anda menyebutkannya, saya pikir saya memiliki memori otot yang lebih baik untuk $ (dan )daripada yang saya lakukan untuk backtick; YMMV.
Keith Thompson
tidak pernah diprogram dalam bash (dilewati dari ksh lama ke Perl) jadi pasti tidak ada memori untuk sintaks tertentu :)
DVK
1
@DVK, saya pikir Keith mengacu pada fakta bahwa kode non-blok di sini (kode blok berarti menggunakan empat spasi di awal baris) menggunakan backticks untuk mengindikasikannya, sehingga sulit untuk menempatkan backticks di dalamnya, ilustrasi lain dari kesulitan bersarang :-) FWIW, Anda mungkin menemukan kode dan / kode tag (cara lain untuk melakukan kode non-blok) dapat lebih mudah berisi backticks.
paxdiablo
@ Max - mengerti. Duh! Saya memang secara mental terjebak pada blok kode karena suatu alasan.
DVK
7

$() memungkinkan bersarang.

out=$(echo today is $(date))

Saya pikir backticks tidak mengizinkannya.

Shiplu Mokaddim
sumber
2
Anda dapat membuat sarang backticks; itu jauh lebih sulit: out=`echo today is \`date\`` .
Jonathan Leffler
4

Ini adalah standar POSIX yang mendefinisikan $(command)bentuk substitusi perintah. Kebanyakan cangkang yang digunakan saat ini adalah sesuai dengan POSIX dan mendukung formulir yang lebih disukai ini daripada notasi backtick kuno. Bagian substitusi perintah (2.6.3) dari dokumen Bahasa Shell menjelaskan hal ini:

Substitusi perintah memungkinkan output dari suatu perintah untuk diganti di tempat nama perintah itu sendiri. Substitusi perintah akan terjadi ketika perintah terlampir sebagai berikut:

$(command)

atau (versi kutipan balik):

`command`

Shell harus memperluas substitusi perintah dengan mengeksekusi perintah di lingkungan subkulit (lihat Lingkungan Eksekusi Shell ) dan mengganti subtitusi perintah (teks perintah ditambah "$ ()" atau tanda kutip mundur) dengan output standar dari perintah, menghapus urutan satu atau lebih <newline>karakter di akhir substitusi. <newline>Karakter yang disematkan sebelum akhir output tidak akan dihapus; Namun, mereka dapat diperlakukan sebagai pembatas lapangan dan dihilangkan selama pemisahan lapangan, tergantung pada nilai IFS dan kutipan yang berlaku. Jika output berisi byte nol, perilaku tidak ditentukan.

Dalam gaya substitusi komando yang dikutip kembali, <backslash>akan mempertahankan makna literalnya, kecuali bila diikuti oleh: ' $', ' `', atau <backslash>. Pencarian untuk backquote yang cocok harus dipenuhi oleh backquote tanpa tanda kutip pertama yang tidak diloloskan; selama pencarian ini, jika backquote yang tidak diloloskan ditemui dalam komentar shell, dokumen-di sini, substitusi perintah tertanam dari formulir $ ( perintah ), atau string yang dikutip, hasil yang tidak ditentukan terjadi. String yang dikutip satu atau dua yang dimulai, tetapi tidak berakhir, dalam urutan " `...`" menghasilkan hasil yang tidak ditentukan.

Dengan formulir $ ( perintah ), semua karakter yang mengikuti tanda kurung terbuka ke tanda kurung penutup yang cocok merupakan perintah . Setiap skrip shell yang valid dapat digunakan untuk perintah , kecuali skrip yang hanya terdiri dari pengalihan yang menghasilkan hasil yang tidak ditentukan.

Hasil substitusi perintah tidak akan diproses untuk ekspansi tilde lebih lanjut, ekspansi parameter, substitusi perintah, atau ekspansi aritmatika. Jika substitusi perintah terjadi di dalam tanda kutip ganda, pemisahan bidang dan perluasan nama jalur tidak akan dilakukan pada hasil substitusi.

Substitusi perintah dapat disarangkan. Untuk menentukan sarang dalam versi backquoted, aplikasi harus mendahului backquote batin dengan <backslash>karakter; sebagai contoh:

\`command\`

Sintaks bahasa perintah shell memiliki ambiguitas untuk ekspansi yang dimulai dengan "$((", yang dapat memperkenalkan ekspansi aritmatika atau substitusi perintah yang dimulai dengan subkulit. Ekspansi aritmatika telah diutamakan; yaitu, shell pertama-tama harus menentukan apakah ia dapat menguraikan ekspansi sebagai ekspansi aritmatika dan hanya akan menguraikan ekspansi sebagai perintah substitusi jika ia menentukan bahwa ia tidak dapat mem-parsing ekspansi sebagai ekspansi aritmatika. Shell tidak perlu mengevaluasi ekspansi bersarang ketika melakukan penentuan ini. Jika ia menemui akhir input tanpa telah menentukan bahwa ia tidak dapat menguraikan ekspansi sebagai ekspansi aritmatika, shell akan memperlakukan ekspansi sebagai ekspansi aritmatika yang tidak lengkap dan melaporkan kesalahan sintaksis. Aplikasi yang sesuai harus memastikan bahwa ia memisahkan " $(" dan '('menjadi dua token (yaitu, pisahkan dengan spasi putih) dalam substitusi perintah yang dimulai dengan subkulit. Misalnya, substitusi perintah yang berisi satu subkulit dapat ditulis sebagai:

$( (command) )

JRFerguson
sumber
4

Ini adalah pertanyaan warisan, tapi saya datang dengan contoh sempurna valid $(...)atas `...`.

Saya menggunakan desktop jarak jauh untuk windows yang menjalankan cygwin dan ingin mengulangi hasil dari perintah. Sayangnya, karakter backtick tidak dapat dimasuki, baik karena desktop remote atau cygwin itu sendiri.

Sangat masuk akal untuk berasumsi bahwa tanda dolar dan tanda kurung akan lebih mudah untuk diketik dalam pengaturan aneh seperti itu.

Piotr Zierhoffer
sumber