Mengapa saya perlu mengutip variabel untuk if, tetapi tidak untuk echo?

26

Saya telah membaca bahwa Anda memerlukan tanda kutip ganda untuk memperluas variabel, misalnya

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

akan bekerja seperti yang diharapkan, sementara

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

akan selalu mengatakan $test okbahkan jika $testitu nol.

tapi mengapa kita tidak perlu mengutip echo $test?

CharlesB
sumber
2
Jika Anda tidak mengutip variabel untuk digunakan sebagai argumen echo, spasi tambahan dan baris baru akan dihapus.
jordanm

Jawaban:

36

Anda selalu memerlukan tanda kutip di sekitar variabel dalam semua konteks daftar , di mana-mana variabel tersebut dapat diperluas ke beberapa nilai kecuali Anda memang menginginkan 3 efek samping dari meninggalkan variabel yang tidak dikutip.

Daftar konteks termasuk argumen untuk perintah sederhana seperti [atau echo, yang for i in <here>, tugas untuk array ... Ada konteks lain di mana variabel juga perlu dikutip. Yang terbaik adalah selalu mengutip variabel kecuali Anda punya alasan yang sangat bagus untuk tidak melakukannya.

Pikirkan tentang tidak adanya tanda kutip (dalam konteks daftar) sebagai operator split + glob .

Seolah-olah echo $testitu echo glob(split("$test")).

Perilaku shell membingungkan bagi kebanyakan orang karena di sebagian besar bahasa lain, Anda menempatkan tanda kutip di sekitar string tetap, seperti puts("foo"), dan tidak di sekitar variabel (seperti puts(var)) sementara di shell itu sebaliknya: semuanya adalah string dalam shell, jadi menempatkan tanda kutip di sekitar segalanya akan merepotkan, Anda echo test, Anda tidak perlu "echo" "test". Dalam shell, kutipan digunakan untuk hal lain: mencegah beberapa makna khusus dari beberapa karakter dan / atau mempengaruhi perilaku beberapa ekspansi.

Di [ -n $test ]atauecho $test , shell akan membagi $test(kosong pada secara default), dan kemudian melakukan pembuatan nama file (perluas semua *, '?' ... pola ke daftar file yang cocok), dan kemudian meneruskan daftar argumen itu ke [atau echoperintah .

Sekali lagi, anggap itu sebagai "[" "-n" glob(split("$test")) "]" . Jika $testkosong atau hanya berisi kosong (spc, tab, nl), maka operator split + glob akan mengembalikan daftar kosong, sehingga [ -n $test ]akan menjadi "[" "-n" "]", yang merupakan tes untuk memeriksa apakah "-n" adalah string kosong atau tidak. Tetapi bayangkan apa yang akan terjadi jika $test"*" atau "= foo" ...

Dalam [ -n "$test" ], [disahkan empat argumen"[" , "-n", ""dan "]"(tanpa tanda kutip), yang adalah apa yang kita inginkan.

Apakah itu echo atau [tidak ada bedanya, hanya saja echomenghasilkan hal yang sama apakah itu melewati argumen kosong atau tidak sama sekali.

Lihat juga jawaban ini untuk pertanyaan serupa untuk perincian lebih lanjut tentang [perintah dan [[...]]konstruk.

Stéphane Chazelas
sumber
7

@ h3rrmiller jawaban baik untuk menjelaskan mengapa Anda memerlukan tanda kutip untuk if(atau lebih tepatnya,[ / test), tetapi saya benar-benar berpendapat bahwa pertanyaan Anda tidak benar.

Coba perintah berikut, dan Anda akan melihat apa yang saya maksud.

export testvar="123    456"
echo $testvar
echo "$testvar"

Tanpa tanda kutip, substitusi variabel menyebabkan perintah kedua meluas ke:

echo 123    456

dan beberapa ruang diciutkan menjadi satu:

echo 123 456

Dengan tanda kutip, spasi dipertahankan.

Ini terjadi karena ketika Anda mengutip suatu parameter (apakah parameter itu diteruskan ke echo, testatau perintah lain), nilai parameter itu dikirim sebagai satu nilai ke perintah. Jika Anda tidak mengutipnya, shell melakukan keajaiban normal untuk mencari spasi putih untuk menentukan di mana setiap parameter dimulai dan berakhir.

Ini juga dapat diilustrasikan oleh program C (sangat sangat sederhana) berikut. Coba yang berikut ini pada baris perintah (Anda mungkin ingin melakukannya di direktori kosong agar tidak berisiko menimpa sesuatu).

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

lalu...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

Setelah berjalan paramtest, $?akan menampung jumlah parameter yang dilewati (dan nomor itu akan dicetak).

sebuah CVn
sumber
2

Ini semua tentang bagaimana shell menginterpretasikan baris sebelum suatu program dieksekusi.

Jika baris berbunyi echo I am $USER, shell mengekspansi ke echo I am blrfldan echotidak memiliki petunjuk apakah asal teks adalah ekspansi literal atau variabel. Demikian pula, jika sebuah baris berbunyi echo I am $UNDEFINED, shell tidak akan berkembang $UNDEFINEDmenjadi apa-apa dan argumen echo akan menjadi I am, dan itulah akhirnya. Karena echoberfungsi dengan baik tanpa argumen, echo $UNDEFINEDitu sepenuhnya valid.

Masalah Anda dengan iftidak benar-benar dengan if, karena ifhanya menjalankan program dan argumen apa pun mengikutinya dan menjalankan thenbagian jika program keluar 0(atau elsebagian jika ada satu dan program keluar non- 0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

Ketika Anda menggunakan if [ ... ]untuk melakukan perbandingan, Anda tidak menggunakan primitif yang dibangun ke dalam shell. Anda benar-benar menginstruksikan shell untuk menjalankan program yang disebut [superset sangat sedikit test(1)yang membutuhkan argumen terakhirnya ]. Kedua program keluar 0jika kondisi pengujian keluar benar dan 1jika tidak.

Alasan beberapa tes rusak ketika variabel tidak terdefinisi adalah karena testtidak melihat bahwa Anda menggunakan variabel. Ergo, [ $UNDEFINED -eq 2 ]rusak karena pada saat shell selesai dengan itu, semua testmelihat argumen -eq 2 ], yang bukan tes yang valid. Jika Anda melakukannya dengan sesuatu yang didefinisikan, seperti [ $DEFINED -ne 0 ], itu akan berhasil karena shell akan mengembangkannya menjadi tes yang valid (misalnya, 0 -ne 0).

Ada perbedaan semantik antara foo $UNDEFINED bar, yang mengembang menjadi dua argumen ( foodan bar) karena $UNDEFINEDsesuai dengan namanya. Bandingkan ini dengan foo "$UNDEFINED" bar, yang mengembang menjadi tiga argumen ( foo, string kosong dan `bar). Kutipan memaksa shell untuk menafsirkannya sebagai argumen apakah ada sesuatu di antara mereka atau tidak.

Blrfl
sumber
0

Tanpa tanda kutip $testdapat berkembang menjadi lebih dari satu kata sehingga perlu dikutip agar tidak melanggar sintaks karena setiap saklar di dalam [perintah mengharapkan satu argumen yang merupakan apa yang dilakukan tanda kutip (membuat apa pun$test diperluas menjadi satu argumen)

Alasan Anda tidak perlu mengutip untuk memperluas variabel echoadalah karena tidak mengharapkan satu argumen. Ini hanya akan mencetak apa yang Anda katakan. Jadi meskipun jika $testdiperluas hingga 100 kata, echo masih akan mencetaknya.

Lihatlah Bash Pitfalls

h3rrmiller
sumber
ya tapi mengapa kita tidak membutuhkannya echo?
CharlesB
@CharlesB Anda perlu tanda kutip untuk echo. Apa yang membuat Anda berpikir sebaliknya?
Gilles 'SO- stop being evil'
Saya tidak membutuhkannya, saya bisa echo $testdan berfungsi (ini mengeluarkan nilai $ test)
CharlesB
1
@CharlesB Hanya mengeluarkan nilai $ test jika itu tidak mengandung banyak spasi di mana saja. Coba program dalam jawaban saya untuk ilustrasi alasannya.
CVn
0

Parameter kosong dihapus jika tidak dikutip:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

Perintah yang dipanggil tidak melihat bahwa ada parameter kosong di baris perintah shell. Tampaknya [didefinisikan untuk mengembalikan 0 untuk -n tanpa diikuti. Kenapa

Mengutip juga membuat perbedaan untuk gema, dalam beberapa kasus:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"
Hauke ​​Laging
sumber
2
Bukan echo, itu shellnya. Anda akan melihat perilaku yang sama dengannya ls. Cobalah touch '*'beberapa saat jika Anda ingin berpetualang. :)
CVn
Itu hanya kata-kata karena tidak ada perbedaan pada kasus 'jika [...] `. [bukan perintah shell khusus. Itu berbeda dari [[(dalam bash) di mana mengutip tidak diperlukan.
Hauke ​​Laging