Bagaimana cara saya keluar dari karakter wildcard / asterisk di bash?

140

Sebagai contoh:

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

dan menggunakan \karakter escape:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

Saya jelas melakukan sesuatu yang bodoh.

Bagaimana cara mendapatkan hasilnya BAR * BAR?

andyuk
sumber

Jawaban:

146

Mengutip saat pengaturan $FOOtidak cukup. Anda juga perlu mengutip referensi variabel:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR
finnw
sumber
9
ini misterius, mengapa demikian? apa yang sedang terjadi?
tofutim
Karena variabel berkembang
Daniel
109

JAWABAN SINGKAT

Seperti yang dikatakan orang lain - Anda harus selalu mengutip variabel untuk mencegah perilaku aneh. Jadi gunakan echo "$ foo" sebagai ganti hanya echo $ foo .

JAWABAN PANJANG

Saya pikir contoh ini memerlukan penjelasan lebih lanjut karena ada lebih banyak hal yang terjadi daripada yang terlihat di awal.

Saya dapat melihat di mana kebingungan Anda datang karena setelah Anda menjalankan contoh pertama Anda, Anda mungkin berpikir sendiri bahwa shell jelas melakukan:

  1. Perluasan parameter
  2. Perluasan nama file

Jadi dari contoh pertama Anda:

me$ FOO="BAR * BAR"
me$ echo $FOO

Setelah ekspansi parameter setara dengan:

me$ echo BAR * BAR

Dan setelah perluasan nama file sama dengan:

me$ echo BAR file1 file2 file3 file4 BAR

Dan jika Anda mengetik echo BAR * BARdi baris perintah, Anda akan melihat bahwa keduanya setara.

Jadi Anda mungkin berpikir "jika saya lolos dari *, saya dapat mencegah perluasan nama file"

Jadi dari contoh kedua Anda:

me$ FOO="BAR \* BAR"
me$ echo $FOO

Setelah perluasan parameter harus setara dengan:

me$ echo BAR \* BAR

Dan setelah perluasan nama file harus sama dengan:

me$ echo BAR \* BAR

Dan jika Anda mencoba mengetik "echo BAR \ * BAR" secara langsung ke baris perintah, ia memang akan mencetak "BAR * BAR" karena perluasan nama file dicegah oleh escape.

Jadi mengapa menggunakan $ foo tidak berhasil?

Itu karena ada ekspansi ketiga yang terjadi - Penghapusan Kutipan. Dari penghapusan kutipan manual bash adalah:

Setelah perluasan sebelumnya, semua kemunculan karakter '\', '' ', dan' "'yang bukan hasil dari salah satu perluasan di atas akan dihapus.

Jadi yang terjadi adalah saat Anda mengetik perintah langsung ke baris perintah, karakter escape bukan hasil ekspansi sebelumnya sehingga BASH menghapusnya sebelum mengirimnya ke perintah echo, tetapi pada contoh kedua, "\ *" adalah hasil dari perluasan Parameter sebelumnya, jadi TIDAK dihapus. Akibatnya, echo menerima "\ *" dan itulah yang dicetaknya.

Perhatikan perbedaan antara contoh pertama - "*" tidak disertakan dalam karakter yang akan dihapus oleh Penghapusan Kutipan.

Saya harap ini masuk akal. Pada akhirnya kesimpulannya sama - gunakan saja tanda kutip. Saya hanya berpikir saya akan menjelaskan mengapa melarikan diri, yang secara logis seharusnya berfungsi jika hanya ekspansi Parameter dan Nama file yang berperan, tidak berfungsi.

Untuk penjelasan lengkap tentang ekspansi BASH, lihat:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions

mithu
sumber
1
Jawaban yang bagus! Sekarang saya tidak merasa saya mengajukan pertanyaan bodoh seperti itu. :-)
andyuk
1
Ada beberapa kegunaan untuk melarikan diri dari karakter khusus?
Jigar Joshi
"Anda harus selalu mengutip variabel untuk mencegah perilaku aneh" - saat Anda ingin menggunakannya sebagai string
Angelo
Meskipun jawaban dari @finnw di atas secara langsung menjawab pertanyaan, jawaban ini jauh lebih baik dalam menjelaskan alasannya , yang jauh lebih membantu dalam memperkirakan bagaimana penggunaan dalam skenario kita sendiri akan bekerja. Ini adalah jenis jawaban yang perlu kita lihat lebih banyak.
Nicolas Coombs
56

Saya akan menambahkan sedikit ke utas lama ini.

Biasanya Anda akan menggunakan

$ echo "$FOO"

Namun, saya mengalami masalah bahkan dengan sintaks ini. Simak script berikut ini.

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

The *kebutuhan untuk diteruskan verbatim untuk curl, tetapi masalah yang sama akan timbul. Contoh di atas tidak akan berfungsi (ini akan diperluas ke nama file di direktori saat ini) dan begitu pula sebaliknya \*. Anda juga tidak dapat mengutip $curl_optskarena akan dikenali sebagai satu opsi (tidak valid) untuk curl.

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Oleh karena itu saya akan merekomendasikan penggunaan bashvariabel $GLOBIGNOREuntuk mencegah perluasan nama file sama sekali jika diterapkan ke pola global, atau gunakan set -fbendera bawaan.

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

Menerapkan contoh asli Anda:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR
AlexandreH
sumber
4
Penjelasan bagus, terima kasih! Kasus penggunaan saya adalah SELECT * FROM etc., ini adalah satu-satunya cara yang berhasil.
knutole
1
Terima kasih telah mempersembahkan solusi set -f!
hachre
5
FOO='BAR * BAR'
echo "$FOO"
tzot.dll
sumber
Ini berfungsi, tetapi tidak perlu mengubah tanda kutip tunggal di baris pertama menjadi tanda kutip ganda.
finnw
1
Tidak, tetapi kebiasaan menggunakan tanda kutip tunggal lebih disukai saat Anda akan memasukkan karakter shell khusus dan Anda tidak ingin ada substitusi.
tzot
3
echo "$FOO"
Rafał Dowgird
sumber
3

Mungkin ada baiknya membiasakan diri menggunakan printfdaripada menggunakan echobaris perintah.

Dalam contoh ini tidak memberikan banyak manfaat tetapi bisa lebih bermanfaat dengan keluaran yang lebih kompleks.

FOO="BAR * BAR"
printf %s "$FOO"
Dave Webb
sumber
Kenapa di neraka? printf adalah proses yang terpisah (setidaknya, bukan bash built-in) dan penggunaan printf yang Anda tunjukkan tidak memiliki keuntungan melebihi echo.
ddaa
2
printf adalah bash built-in dan saya mengatakan dalam jawaban saya "Dalam contoh ini tidak memberikan banyak manfaat tetapi bisa lebih berguna dengan keluaran yang lebih kompleks.
Dave Webb