perintah eval di Bash dan kegunaan khasnya

165

Setelah membaca halaman bash man dan sehubungan dengan posting ini .

Saya masih kesulitan memahami apa yang sebenarnya dilakukan evalperintah dan yang akan menjadi kegunaan khasnya. Sebagai contoh jika kita lakukan:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one

Apa sebenarnya yang terjadi di sini dan bagaimana tanda dolar dan garis miring terbalik menjadi masalah?

kstratis
sumber
1
Sebagai catatan, upaya kedua berhasil. $($n)berjalan $ndalam subkulit. Mencoba menjalankan perintah 1yang tidak ada.
Martin Wickman
1
@ MartinWickman Tapi persyaratannya adalah untuk menjalankannya pada echo $1akhirnya, bukan 1. Saya tidak berpikir itu bisa dilakukan menggunakan subkulit.
Hari Menon
5
Anda harus menyadari implikasi keamanan penggunaaneval .
Dijeda sampai pemberitahuan lebih lanjut.
1
@ Raze2dust: Saya tidak percaya dia menyarankan itu bisa dijalankan dengan subkulit, tetapi lebih menjelaskan mengapa perintah ke 5 OP terdaftar tidak berfungsi.
jedwards

Jawaban:

196

evalmengambil string sebagai argumennya, dan mengevaluasinya seolah-olah Anda mengetik string itu pada baris perintah. (Jika Anda melewati beberapa argumen, mereka pertama kali bergabung dengan spasi di antara mereka.)

${$n}adalah kesalahan sintaks dalam bash. Di dalam kurung kurawal, Anda hanya dapat memiliki nama variabel, dengan beberapa kemungkinan awalan dan sufiks, tetapi Anda tidak dapat memiliki sintaks bash sembarang dan khususnya Anda tidak dapat menggunakan ekspansi variabel. Ada cara untuk mengatakan "nilai variabel yang namanya ada dalam variabel ini", meskipun:

echo ${!n}
one

$(…)menjalankan perintah yang ditentukan di dalam tanda kurung dalam sebuah subkulit (yaitu dalam proses terpisah yang mewarisi semua pengaturan seperti nilai variabel dari shell saat ini), dan mengumpulkan hasilnya. Jadi echo $($n)berjalan $nsebagai perintah shell, dan menampilkan hasilnya. Sejak $ndievaluasi untuk 1, $($n)upaya untuk menjalankan perintah 1, yang tidak ada.

eval echo \${$n}menjalankan parameter yang diteruskan ke eval. Setelah ekspansi, parameternya adalah echodan ${1}. Jadi eval echo \${$n}jalankan perintah echo ${1}.

Perhatikan bahwa sebagian besar waktu, Anda harus menggunakan tanda kutip ganda sekitar substitusi variabel dan substitusi perintah (yaitu kapan ada $): "$foo", "$(foo)". Selalu letakkan tanda kutip ganda di sekitar penggantian variabel dan perintah , kecuali Anda tahu Anda harus membiarkannya. Tanpa tanda kutip ganda, shell melakukan pemisahan bidang (yaitu membagi nilai variabel atau output dari perintah menjadi kata-kata yang terpisah) dan kemudian memperlakukan setiap kata sebagai pola wildcard. Sebagai contoh:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

evaltidak sering digunakan. Dalam beberapa shell, penggunaan yang paling umum adalah untuk mendapatkan nilai dari variabel yang namanya tidak diketahui sampai runtime. Dalam bash, ini tidak perlu berkat ${!VAR}sintaksnya. evalmasih berguna ketika Anda perlu membangun perintah yang lebih panjang yang berisi operator, kata-kata yang dicadangkan, dll.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Berkenaan dengan komentar saya di atas, berapa banyak "melewati" yang dilakukan eval?
kstratis
@ Konos5 Komentar apa? evalmenerima string (yang mungkin merupakan hasil parsing dan evaluasi), dan menafsirkannya sebagai potongan kode.
Gilles 'SO- stop being evil'
Di bawah jawaban Raze2dust, saya telah meninggalkan komentar. Saya sekarang cenderung percaya bahwa eval sebagian besar digunakan untuk tujuan dereferencing. Jika saya mengetik eval echo \ $ {$ n} saya mendapatkannya. Namun jika saya mengetik echo \ $ {$ n} saya mendapatkan \ $ {1}. Saya percaya ini terjadi karena parsing "dua-pass" eval. Sekarang saya bertanya-tanya apa yang akan terjadi jika saya perlu melipatgandakan dereferensi menggunakan deklarasi i = n tambahan. Dalam hal ini menurut Raze2dust saya hanya perlu memberikan eval tambahan. Namun saya percaya bahwa harus ada cara yang lebih baik ... (itu bisa dengan mudah berantakan)
kstratis
@ Konos5 Saya tidak akan menggunakan eval eval. Saya tidak ingat pernah merasakan kebutuhan itu. Jika Anda benar-benar membutuhkan dua evalberlalu, menggunakan variabel sementara, itu akan lebih mudah untuk debug: eval tmp="\${$i}"; eval x="\${$tmp}".
Gilles 'SO- stop being evil'
1
@ Konos5 "Parsed 2x" sedikit menyesatkan. Beberapa orang mungkin dituntun untuk mempercayai hal ini karena sulitnya menentukan argumen string literal di Bash yang dilindungi dari berbagai ekspansi. evalhanya mengambil kode dalam sebuah string dan mengevaluasinya sesuai dengan aturan yang biasa. Secara teknis, itu bahkan tidak benar, karena ada beberapa kasus sudut di mana Bash memodifikasi parsing untuk bahkan tidak melakukan ekspansi ke argumen eval - tapi itu adalah berita gembira yang sangat tidak jelas yang saya ragu ada yang tahu.
ormaaj
39

Cukup anggap eval sebagai "mengevaluasi ekspresi Anda satu kali tambahan sebelum eksekusi"

eval echo \${$n}menjadi echo $1setelah putaran pertama evaluasi. Tiga perubahan untuk diperhatikan:

  • Yang \$menjadi $(garis miring terbalik diperlukan, jika tidak ia mencoba untuk mengevaluasi ${$n}, yang berarti variabel bernama {$n}, yang tidak diperbolehkan)
  • $n dievaluasi untuk 1
  • Yang evalhilang

Di babak kedua, pada dasarnya echo $1yang bisa langsung dieksekusi.

Jadi eval <some command>pertama-tama akan mengevaluasi <some command>(dengan mengevaluasi di sini saya maksudkan variabel pengganti, ganti karakter yang keluar dengan yang benar, dll.), Dan kemudian jalankan ekspresi yang dihasilkan sekali lagi.

evaldigunakan ketika Anda ingin secara dinamis membuat variabel, atau membaca keluaran dari program yang dirancang khusus untuk dibaca seperti ini. Lihat http://mywiki.wooledge.org/BashFAQ/048 untuk contohnya. Tautan ini juga berisi beberapa cara khas yang evaldigunakan, dan risiko yang terkait dengannya.

Hari Menon
sumber
3
Sebagai catatan untuk peluru pertama, ${VAR}sintaks yang diperbolehkan, dan lebih suka ketika ada ambiguitas (tidak $VAR == $V, diikuti oleh ARatau $VAR == $VAdiikuti oleh R). ${VAR}setara dengan $VAR. Bahkan, itu nama variabel $nyang tidak diizinkan.
jedwards
2
eval eval echo \\\${\${$i}}akan melakukan dereferensi tiga. Saya tidak yakin apakah ada cara yang lebih sederhana untuk melakukan itu. Juga, \${$n}bekerja dengan baik (cetakan one) pada mesin saya ..
Hari Menon
2
@ Konos5 echo \\\${\${$i}}mencetak \${${n}}. eval echo \\\${\${$i}}sama dengan echo \${${n}}`` and prints $ {1} . eval eval echo \\ $ {\ $ {$ i}} `setara dengan eval echo ${1}dan dicetak one.
Gilles 'SANGAT berhenti menjadi jahat'
2
@ Konos5 Berpikir di sepanjang garis yang sama - Yang pertama ` escapes the second one, and the third `lolos $setelah itu. Jadi itu menjadi \${${n}}setelah satu putaran evaluasi
Hari Menon
2
@ Konos5 Kiri-ke-kanan adalah cara yang benar untuk memikirkan kutipan dan backslash parsing. Pertama \\ menghasilkan satu backslash. Kemudian \$menghasilkan satu dolar. Dan seterusnya.
Gilles 'SO- berhenti menjadi jahat'
25

Dalam pengalaman saya, penggunaan eval "tipikal" adalah untuk menjalankan perintah yang menghasilkan perintah shell untuk mengatur variabel lingkungan.

Mungkin Anda memiliki sistem yang menggunakan kumpulan variabel lingkungan, dan Anda memiliki skrip atau program yang menentukan mana yang harus ditetapkan dan nilainya. Setiap kali Anda menjalankan skrip atau program, ia berjalan dalam proses bercabang, jadi apa pun yang dilakukannya secara langsung ke variabel lingkungan akan hilang saat keluar. Tetapi skrip atau program itu dapat mengirim perintah ekspor ke stdout.

Tanpa eval, Anda perlu mengarahkan stdout ke file temp, sumber file temp, dan kemudian menghapusnya. Dengan eval, Anda bisa:

eval "$(script-or-program)"

Perhatikan kutipan itu penting. Ambil contoh ini (buat-buat):

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!
sootsnoot
sumber
punya contoh alat umum yang melakukan ini? Alat itu sendiri memiliki sarana untuk menghasilkan seperangkat perintah shell yang dapat diteruskan ke eval?
Joakim Erdfelt
@ Joakim Saya tidak tahu ada alat opensource yang melakukannya, tapi itu digunakan di beberapa skrip pribadi di perusahaan tempat saya bekerja. Saya baru mulai menggunakan teknik ini lagi dengan xampp. File Apache .conf memperluas variabel lingkungan yang ditulis ${varname}. Saya merasa nyaman untuk menggunakan file .conf yang identik pada beberapa server yang berbeda dengan hanya beberapa hal yang diparameterisasi oleh variabel lingkungan. Saya mengedit / opt / lampp / xampp (yang memulai apache) untuk melakukan eval semacam ini dengan skrip yang menyembul di sekitar sistem dan menampilkan exportpernyataan bash untuk mendefinisikan variabel untuk file .conf.
sootsnoot
@ Joakim Alternatifnya adalah memiliki skrip untuk menghasilkan masing-masing file .conf yang terpengaruh dari sebuah template, berdasarkan pada pencarian yang sama. Satu hal yang saya sukai dari cara saya adalah memulai apache tanpa melalui / opt / lampp / xampp tidak menggunakan skrip keluaran basi, tetapi gagal memulai karena variabel lingkungan berkembang menjadi kosong dan membuat arahan yang tidak valid.
sootsnoot
@Anthony Sottile Saya melihat Anda mengedit jawaban untuk menambahkan tanda kutip sekitar $ (script-atau-program), mengatakan bahwa mereka penting ketika menjalankan beberapa perintah. Bisakah Anda memberikan contoh - berikut ini berfungsi dengan baik dengan perintah yang dipisahkan semi-colon di stdout dari foo.sh: echo '#! / Bin / bash'> foo.sh; echo 'echo "echo -na; echo -nb; echo -n c"' >> foo.sh; chmod 755 foo.sh; eval $ (./ foo.sh). Ini menghasilkan abc pada stdout. Menjalankan ./foo.sh menghasilkan: echo -na; echo -nb; echo
-nc
1
Untuk contoh alat umum yang menggunakan eval, lihat pyenv . pyenv memungkinkan Anda dengan mudah beralih di antara beberapa versi Python. Anda menempatkan eval "$(pyenv init -)"ke dalam .bash_profile(atau serupa) shell file konfigurasi. Itu membangun skrip shell kecil kemudian mengevaluasinya di shell saat ini.
Jerry101
10

Pernyataan eval memberitahu shell untuk mengambil argumen eval sebagai perintah dan menjalankannya melalui command-line. Ini berguna dalam situasi seperti di bawah ini:

Dalam skrip Anda jika Anda mendefinisikan perintah menjadi variabel dan kemudian Anda ingin menggunakan perintah itu maka Anda harus menggunakan eval:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
Nikhil Gupta
sumber
4

Pembaruan: Beberapa orang mengatakan seseorang harus-tidak pernah menggunakan eval. Saya tidak setuju. Saya pikir risikonya muncul ketika input yang korup dapat diteruskan eval. Namun ada banyak situasi umum di mana itu bukan risiko, dan karena itu perlu diketahui bagaimana menggunakan eval dalam hal apa pun. Jawaban stackoverflow ini menjelaskan risiko eval dan alternatif eval. Pada akhirnya tergantung pada pengguna untuk menentukan apakah eval aman dan efisien untuk digunakan.


evalPernyataan bash memungkinkan Anda untuk mengeksekusi baris kode yang dihitung atau diperoleh, dengan skrip bash Anda.

Mungkin contoh yang paling mudah adalah program bash yang membuka skrip bash lain sebagai file teks, membaca setiap baris teks, dan menggunakan evaluntuk mengeksekusinya secara berurutan. Itu pada dasarnya perilaku yang sama dengan sourcepernyataan bash , yang akan digunakan orang, kecuali jika diperlukan untuk melakukan semacam transformasi (misalnya penyaringan atau substitusi) pada konten skrip yang diimpor.

Saya jarang membutuhkannya eval, tetapi saya merasa berguna untuk membaca atau menulis variabel yang namanya terkandung dalam string yang ditugaskan ke variabel lain. Misalnya, untuk melakukan tindakan pada set variabel, sambil menjaga kode jejak kecil dan menghindari redundansi.

evalsecara konseptual sederhana. Namun, sintaks yang ketat dari bahasa bash, dan urutan parsing penerjemah bash dapat diberi nuansa dan membuatnya evaltampak samar dan sulit untuk digunakan atau dipahami. Berikut ini yang penting:

  1. Argumen yang diteruskan ke evaladalah ekspresi string yang dihitung pada saat runtime. evalakan mengeksekusi hasil parsing terakhir dari argumennya sebagai baris kode aktual dalam skrip Anda.

  2. Sintaks dan urutan parsing ketat. Jika hasilnya bukan baris kode bash yang dapat dieksekusi, dalam lingkup skrip Anda, program akan macet pada evalpernyataan saat mencoba mengeksekusi sampah.

  3. Saat menguji Anda dapat mengganti evalpernyataan dengan echodan melihat apa yang ditampilkan. Jika itu kode yang sah dalam konteks saat ini, menjalankannya evalakan berhasil.


Contoh-contoh berikut dapat membantu menjelaskan bagaimana eval bekerja ...

Contoh 1:

eval pernyataan di depan kode 'normal' adalah NOP

$ eval a=b
$ eval echo $a
b

Dalam contoh di atas, evalpernyataan pertama tidak memiliki tujuan dan dapat dihilangkan. evaltidak ada gunanya di baris pertama karena tidak ada aspek dinamis untuk kode, yaitu sudah diuraikan ke dalam baris akhir kode bash, sehingga akan sama dengan pernyataan kode normal dalam skrip bash. -2 evalada gunanya juga, karena, meskipun ada langkah parsing mengkonversi $ake setara string literal nya, tidak ada tipuan (misalnya tidak ada referensi melalui nilai string dari yang sebenarnya benda bash atau variabel bash script-diadakan), sehingga akan berperilaku identik sebagai baris kode tanpa evalawalan.



Contoh 2:

Melakukan penugasan var menggunakan nama var yang diteruskan sebagai nilai string.

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

Jika Anda melakukannya echo $key=$val, hasilnya adalah:

mykey=myval

Bahwa , sebagai hasil akhir dari penguraian string, adalah apa yang akan dieksekusi oleh eval, maka hasil dari pernyataan echo di akhir ...



Contoh 3:

Menambahkan lebih banyak tipuan ke Contoh 2

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

Di atas sedikit lebih rumit dari contoh sebelumnya, lebih bergantung pada urutan parsing dan kekhasan bash. The evalgaris akan kira-kira bisa diurai secara internal dalam urutan sebagai berikut (perhatikan pernyataan berikut pseudocode, bukan kode nyata, hanya untuk mencoba untuk menunjukkan bagaimana pernyataan akan mendapatkan dipecah menjadi langkah-langkah internal untuk sampai pada hasil akhir) .

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval

Jika asumsi penguraian tidak menjelaskan apa yang dilakukan eval cukup, contoh ketiga dapat menjelaskan penguraian lebih detail untuk membantu memperjelas apa yang sedang terjadi.



Contoh 4:

Temukan apakah vars, yang namanya terkandung dalam string, sendiri mengandung nilai string.

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done

Dalam iterasi pertama:

varname="myvarname_a"

Bash mem-parsing argumen ke eval, dan evalmelihat ini secara harfiah pada saat runtime:

eval varval=\$$myvarname_a

Pseudocode berikut ini mencoba menggambarkan bagaimana bash menginterpretasikan baris kode real di atas , untuk sampai pada nilai akhir yang dieksekusi oleh eval. (baris berikut deskriptif, bukan kode bash yang tepat):

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)

Setelah semua penguraian selesai, hasilnya adalah apa yang dieksekusi, dan efeknya jelas, menunjukkan tidak ada yang misterius tentang evaldirinya sendiri, dan kompleksitasnya adalah penguraian argumennya.

varval="User-provided"

Kode yang tersisa dalam contoh di atas hanya menguji untuk melihat apakah nilai yang ditetapkan untuk $ varval adalah nol, dan, jika demikian, meminta pengguna untuk memberikan nilai.

cahaya terang
sumber
3

Awalnya saya sengaja tidak pernah belajar cara menggunakan eval, karena kebanyakan orang akan merekomendasikan untuk menjauh darinya seperti wabah. Namun saya baru-baru ini menemukan kasus penggunaan yang membuat saya facepalm untuk tidak mengenalinya lebih cepat.

Jika Anda memiliki pekerjaan cron yang ingin Anda jalankan secara interaktif untuk diuji, Anda mungkin melihat konten file dengan cat, dan menyalin dan menempelkan pekerjaan cron untuk menjalankannya. Sayangnya, ini melibatkan menyentuh mouse, yang merupakan dosa dalam buku saya.

Katakanlah Anda memiliki pekerjaan cron di /etc/cron.d/repeatme dengan konten:

*/10 * * * * root program arg1 arg2

Anda tidak dapat menjalankan ini sebagai skrip dengan semua sampah di depannya, tetapi kita dapat menggunakan cut untuk menyingkirkan semua sampah, membungkusnya dalam sebuah subkulit, dan mengeksekusi string dengan eval

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

Perintah cut hanya mencetak bidang ke-6 file, dibatasi oleh spasi. Eval kemudian menjalankan perintah itu.

Saya menggunakan cron job di sini sebagai contoh, tetapi konsepnya adalah memformat teks dari stdout, dan kemudian mengevaluasi teks itu.

Penggunaan eval dalam hal ini tidak aman, karena kita tahu persis apa yang akan kita evaluasi sebelumnya.

Luke Pafford
sumber
2

Saya baru-baru ini harus menggunakan evaluntuk memaksa beberapa ekspansi brace dievaluasi dalam urutan yang saya butuhkan. Bash melakukan beberapa ekspansi brace dari kiri ke kanan, jadi

xargs -I_ cat _/{11..15}/{8..5}.jpg

meluas ke

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

tapi saya perlu ekspansi brace kedua dilakukan pertama, menghasilkan

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

Yang terbaik yang bisa saya lakukan untuk melakukan itu adalah

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

Ini bekerja karena tanda kutip tunggal melindungi set kawat gigi pertama dari ekspansi selama penguraian evalbaris perintah, meninggalkannya untuk diperluas oleh subkulit yang dipanggil oleh eval.

Mungkin ada beberapa skema licik yang melibatkan ekspansi penjepit bersarang yang memungkinkan ini terjadi dalam satu langkah, tetapi jika ada saya terlalu tua dan bodoh untuk melihatnya.

flabdablet
sumber
1

Anda bertanya tentang kegunaan umum.

Satu keluhan umum tentang skrip shell adalah bahwa Anda (diduga) tidak dapat melewati referensi untuk mendapatkan nilai kembali dari fungsi.

Tetapi sebenarnya, melalui "eval", Anda dapat melewati referensi. Callee dapat mengirimkan kembali daftar tugas variabel untuk dievaluasi oleh penelepon. Ini lewat referensi karena penelepon dapat diizinkan untuk menentukan nama variabel hasil - lihat contoh di bawah ini. Hasil kesalahan dapat diberikan kembali nama standar seperti errno dan errstr.

Berikut ini adalah contoh lewat referensi di bash:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

Outputnya terlihat seperti ini:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

Ada lebar pita hampir tak terbatas dalam output teks itu! Dan ada lebih banyak kemungkinan jika garis keluaran berganda digunakan: misalnya, baris pertama dapat digunakan untuk penugasan variabel, yang kedua untuk 'aliran pemikiran' terus menerus, tapi itu di luar cakupan tulisan ini.

Craig Hicks
sumber
mengatakan bahwa ada "lebih banyak kemungkinan" adalah sepele, sepele dan berlebihan untuk sedikitnya.
dotbit
0

Saya suka jawaban "mengevaluasi ekspresi Anda satu kali sebelum eksekusi", dan ingin menjelaskan dengan contoh lain.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

Hasil Penasaran di Opsi 2 adalah bahwa kami telah melewati 2 parameter sebagai berikut:

  • Parameter pertama: "value
  • Parameter kedua: content"

Bagaimana itu untuk kontra intuitif? Tambahan evalakan memperbaikinya.

Diadaptasi dari https://stackoverflow.com/a/40646371/744133

YoYo
sumber
0

Dalam pertanyaan:

who | grep $(tty | sed s:/dev/::)

kesalahan output mengklaim bahwa file a dan tty tidak ada. Saya mengerti ini berarti bahwa tty tidak ditafsirkan sebelum eksekusi grep, tetapi sebaliknya bash meneruskan tty sebagai parameter untuk grep, yang menafsirkannya sebagai nama file.

Ada juga situasi pengalihan bersarang, yang harus ditangani oleh tanda kurung yang cocok yang harus menentukan proses anak, tetapi bash pada dasarnya adalah pemisah kata, membuat parameter untuk dikirim ke suatu program, oleh karena itu tanda kurung tidak cocok terlebih dahulu, tetapi ditafsirkan sebagai terlihat.

Saya mendapatkan spesifik dengan grep, dan menetapkan file sebagai parameter alih-alih menggunakan pipa. Saya juga menyederhanakan perintah dasar, meneruskan keluaran dari perintah sebagai file, sehingga i / o piping tidak akan disarangkan:

grep $(tty | sed s:/dev/::) <(who)

bekerja dengan baik.

who | grep $(echo pts/3)

tidak benar-benar diinginkan, tetapi menghilangkan pipa bersarang dan juga berfungsi dengan baik.

Kesimpulannya, bash tampaknya tidak suka pipping bersarang. Penting untuk dipahami bahwa bash bukanlah program gelombang baru yang ditulis secara rekursif. Sebaliknya, bash adalah program lama 1,2,3, yang telah ditambahkan dengan fitur. Untuk tujuan memastikan kompatibilitas ke belakang, cara penafsiran awal tidak pernah dimodifikasi. Jika bash ditulis ulang untuk mencocokkan tanda kurung pertama, berapa banyak bug yang akan diperkenalkan ke dalam berapa banyak program bash? Banyak programmer suka menjadi samar.

Paul
sumber