Cara menggemakan bang!

59

Saya mencoba membuat skrip dengan echomemasukkan konten ke dalam file, alih-alih membukanya dengan editor

echo -e "#!/bin/bash \n /usr/bin/command args"  > .scripts/command

The Output :

bash:! / bin / bash: acara tidak ditemukan

Saya telah mengisolasi perilaku aneh ini ke ledakan .

$ echo !
!  

$ echo "!"
bash: !: event not found

$ echo \#!
#!

$ echo \#!/bin/bash
bash: !/bin/bash: event not found
  • Kenapa bang menyebabkan ini?
  • Apa "peristiwa" yang dirujuk oleh bash ini?
  • Bagaimana cara saya mengatasi masalah ini dan mencetak "#! / Bin / bash" ke layar atau file saya?
Stefan
sumber

Jawaban:

59

Coba gunakan tanda kutip tunggal.

echo -e '#!/bin/bash \n /usr/bin/command args'  > .scripts/command

echo '#!'

echo '#!/bin/bash'

Masalahnya terjadi karena bash mencari riwayatnya! / Bin / bash. Menggunakan tanda kutip tunggal lolos dari perilaku ini.

Richm
sumber
3
Ini juga menonaktifkan interpolasi string :(
Hubro
Itulah intinya! Anda dapat menambahkan argumen lain ke perintah gema yang sama menggunakan tanda kutip ganda dan mendapatkan interpolasi pada bagian-bagian itu.
Richm
3
Anda juga dapat menggunakan string gaya C di bash with $''. Misalnya echo $'1!\n2!\n3!\n'mencetak setiap angka diikuti dengan bang dan baris baru.
spelufo
1
Menempatkan bang dalam tanda kutip tunggal tidak membantu saya ketika memasukkan string multi-line:! bash — tanda kutip tunggal tidak luput dari ledakan (nyata)
binki
1
@kbolino Anda benar. Jadi string contoh lengkapnya adalah:, echo '0123'"$var"'456'perhatikan dua pasang tanda kutip tunggal dan sepasang tanda kutip ganda.
phyatt
16

Seperti yang dikatakan Richm , bash sedang mencoba melakukan pertandingan sejarah . Cara lain untuk menghindarinya adalah dengan melarikan diri dari ledakan dengan \:

$ echo \#\!/bin/bash
#!/bin/bash

Meskipun berhati-hatilah bahwa di dalam tanda kutip ganda, \itu tidak dihapus:

$ echo "\!"
\!
Michael Mrozek
sumber
8
Di bash, "\!"sebenarnya diperluas menjadi \!, tidak !, seharusnya untuk kepatuhan POSIX.
Mikel
@Mikel Sigh. Begitu banyak perbedaan antara zsh dan bash
Michael Mrozek
Ini berfungsi bahkan saat memasukkan string multiline. Ingat untuk 1. berada di luar string ketika memasuki bang dan 2. menggunakan metode ini (backslash) tampaknya bekerja dengan kejutan paling tidak dalam kebanyakan skenario.
binki
1
Akan bermanfaat untuk dicatat bahwa bash tidak melakukan pencarian riwayat saat mengeksekusi skrip, jadi tidak perlu melakukan sesuatu yang istimewa untuk itu di sana.
binki
Apakah tidak ada sintaks untuk melarikan diri ke !dalam tanda kutip ganda tanpa perilaku sulap? Ini gila ...
Lassi
13

!memulai substitusi sejarah ("acara" adalah garis dalam sejarah perintah); misalnya !lsmemperluas ke baris perintah terakhir yang berisi ls, dan !?foomemperluas ke baris perintah terakhir yang berisi foo. Anda juga dapat mengekstrak kata-kata spesifik (mis. !!:1Merujuk pada kata pertama dari perintah sebelumnya) dan lebih banyak lagi; lihat manual untuk detailnya.

Fitur ini diciptakan untuk dengan cepat mengingat perintah sebelumnya di masa ketika edisi baris perintah adalah primitif. Dengan shell modern (setidaknya bash dan zsh) dan copy-and-paste, ekspansi sejarah tidak berguna seperti dulu - ini masih membantu, tetapi Anda bisa bertahan tanpa itu.

Anda dapat mengubah karakter mana yang memicu substitusi riwayat dengan menetapkan histcharsvariabel; jika Anda jarang menggunakan substitusi histori, Anda dapat mengatur mis histchars='¡^'sehingga ¡memicu ekspansi histori alih-alih !. Anda bahkan dapat mematikan fitur sama sekali set +o histexpand.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
3

Untuk dapat menonaktifkan ekspansi riwayat pada baris perintah tertentu, Anda dapat menggunakan spacesebagai karakter ke-3 dari $histchars:

histchars='!^ '

Kemudian, jika Anda memasukkan perintah Anda dengan spasi terkemuka, ekspansi sejarah tidak akan dilakukan.

bash-4.3$ echo "#!/bin/bash"
bash: !/bin/bash: event not found
bash-4.3$  echo "#!/bin/bash"
#!/bin/bash

Namun perlu dicatat bahwa spasi utama juga digunakan ketika $HISTCONTROLberisi ignorespacesebagai cara untuk memberitahu bashagar tidak merekam baris perintah dalam sejarah.

Jika Anda menginginkan kedua fitur tersebut secara independen, Anda harus memilih karakter lain sebagai karakter ketiga $histchars. Anda ingin satu yang tidak mempengaruhi cara perintah Anda ditafsirkan. Beberapa pilihan:

  • menggunakan backslash ( \): \echo fooberfungsi tetapi memiliki efek samping menonaktifkan alias atau kata kunci.
  • TAB: untuk memasukkannya di posisi pertama, Anda harus menekan Ctrl+VTab.
  • jika Anda tidak keberatan mengetik dua tombol, Anda dapat memilih karakter yang biasanya tidak muncul di posisi pertama ( %, @, ?, memilih sendiri) dan membuat sebuah alias kosong untuk itu:

    histchars='!^%'
    alias %=

    Lalu masukkan karakter itu, spasi dan perintah Anda:

    bash-4.3$ % echo !!
    !!

(Anda tidak akan dapat merekam perintah di mana substitusi riwayat telah dinonaktifkan. Juga catat bahwa karakter ke-3 default $histcharsadalah #agar ekspansi riwayat tidak dilakukan dalam komentar. Jika Anda mengubahnya, dan Anda memasukkan komentar di cepat, Anda harus menyadari fakta bahwa! Urutan dapat diperluas di sana).

Stéphane Chazelas
sumber
2

Solusi yang diusulkan tidak berfungsi, misalnya, contoh berikut:

$ bash -c "echo 'hello World!'"
-bash: !'": event not found
$

Dalam hal ini bang dapat dicetak menggunakan kode ASCII oktalnya:

$ bash -c "echo -e 'hello World\0041'"
hello World!
$
Atti
sumber
1
Di perintah pertama Anda, !adalah di antara tanda kutip ganda, di mana sebagai jawaban lain menunjukkan itu mempertahankan makna ekspansi sejarahnya. Anda bisa menggunakan bash -c 'echo '\''hello World!'\'atau bash -c "echo 'hello World"\!"'".
Gilles 'SO- stop being evil'
Tanpa parameter -i, lingkungan dapat diubah (seperti ~ /. Profil Anda tidak berjalan). Namun, melakukan -i berarti output apa pun dari .profile Anda akan ditangkap dalam output dari perintah yang sebenarnya ingin Anda jalankan.
Brent
-1

Sejak Bash 4.3, sekarang Anda dapat menggunakan tanda kutip ganda untuk mengutip karakter ekspansi sejarah:

$ bash --version
GNU bash, version 4.3...
[...]
$ echo "Hello World!"
Hello World!
Flimm
sumber
4
Tidak, itu hanya !diikuti oleh "yang tidak lagi istimewa. echo "foo!"tidak apa echo "foo!bar"-apa
Stéphane Chazelas