Làm thế nào để vang một tiếng nổ!


59

Tôi đã cố gắng tạo một tập lệnh bằng cách echo'nhập nội dung vào một tệp, thay vì mở nó bằng trình chỉnh sửa

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

Đầu ra :

bash :! / bin / bash: không tìm thấy sự kiện

Tôi đã cô lập hành vi kỳ lạ này vào tiếng nổ .

$ echo !
!  

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

$ echo \#!
#!

$ echo \#!/bin/bash
bash: !/bin/bash: event not found
  • Tại sao bang gây ra điều này?
  • Những "sự kiện" mà bash đề cập đến là gì?
  • Làm cách nào để vượt qua vấn đề này và in "#! / Bin / bash" lên màn hình hoặc tệp của tôi?

Câu trả lời:


59

Hãy thử sử dụng dấu ngoặc đơn.

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

echo '#!'

echo '#!/bin/bash'

Vấn đề đang xảy ra vì bash đang tìm kiếm lịch sử của nó cho! / Bin / bash. Sử dụng dấu ngoặc đơn thoát khỏi hành vi này.


3
Nó cũng vô hiệu hóa phép nội suy chuỗi :(
Hubro 23/1/2015

Đó là điểm! Bạn có thể thêm các đối số khác vào cùng một lệnh echo bằng cách sử dụng dấu ngoặc kép và nhận nội suy trên các phần đó.
Richm

3
bạn cũng có thể sử dụng chuỗi kiểu C trong bash với $''. Ví dụ echo $'1!\n2!\n3!\n'in mỗi số theo sau là tiếng nổ và dòng mới.
gameufo 19/2/2015

1
Đặt tiếng nổ trong dấu ngoặc đơn không giúp tôi khi nhập chuỗi nhiều dòng :! bash
Đoạn

1
@kbolino Bạn nói đúng. Vì vậy, chuỗi ví dụ đầy đủ sẽ là : echo '0123'"$var"'456', lưu ý hai cặp dấu ngoặc đơn và một cặp dấu ngoặc kép.
phyatt

16

Như Richm đã nói , bash đang cố gắng làm một trận đấu lịch sử . Một cách khác để tránh nó là chỉ cần thoát khỏi tiếng nổ với \:

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

Mặc dù hãy cẩn thận với dấu ngoặc kép, phần \không bị xóa:

$ echo "\!"
\!

8
Trong bash, "\!"thực sự mở rộng đến \!, không !, được cho là tuân thủ POSIX.
Mikel

@Mikel Thở dài. Quá nhiều sự khác biệt giữa zsh và bash
Michael Mrozek

Điều này hoạt động ngay cả khi nhập một chuỗi nhiều dòng. Ghi nhớ 1. nằm ngoài chuỗi khi vào bang và 2. sử dụng phương thức này (dấu gạch chéo ngược) dường như hoạt động với ít bất ngờ nhất trong hầu hết các kịch bản.
binki

1
Sẽ hữu ích khi lưu ý rằng bash không thực hiện tìm kiếm lịch sử khi thực thi tập lệnh, vì vậy không cần phải làm gì đặc biệt cho nó ở đó.
binki

Không có cú pháp nào để thoát ra !bên trong dấu ngoặc kép mà không có hành vi kỳ diệu? Đây là hạt dẻ ...
Lassi

13

!bắt đầu một sự thay thế lịch sử (một sự kiện của người Viking là một dòng trong lịch sử lệnh); ví dụ !lsmở rộng đến dòng lệnh cuối cùng chứa ls!?foomở rộng đến dòng lệnh cuối cùng chứa foo. Bạn cũng có thể trích xuất các từ cụ thể (ví dụ: !!:1đề cập đến từ đầu tiên của lệnh trước đó) và hơn thế nữa; xem hướng dẫn để biết chi tiết.

Tính năng này được phát minh để nhanh chóng nhớ lại các lệnh trước đó trong những ngày mà phiên bản dòng lệnh còn nguyên thủy. Với các shell hiện đại (ít nhất là bash và zsh) và sao chép và dán, mở rộng lịch sử không hữu ích như trước đây - nó vẫn hữu ích, nhưng bạn có thể có được mà không cần nó.

Bạn có thể thay đổi ký tự nào kích hoạt thay thế lịch sử bằng cách đặt histcharsbiến; nếu bạn hiếm khi sử dụng thay thế lịch sử, bạn có thể đặt ví dụ histchars='¡^'để ¡kích hoạt mở rộng lịch sử thay vì !. Bạn thậm chí có thể tắt hoàn toàn tính năng này set +o histexpand.


3

Để có thể tắt mở rộng lịch sử trên một dòng lệnh cụ thể, bạn có thể sử dụng spacelàm ký tự thứ 3 của $histchars:

histchars='!^ '

Sau đó, nếu bạn nhập lệnh của mình với khoảng trắng ở đầu, mở rộng lịch sử sẽ không được thực hiện.

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

Tuy nhiên, lưu ý rằng các khoảng trắng hàng đầu cũng được sử dụng khi $HISTCONTROLchứa ignorespacenhư một cách để nói bashkhông ghi lại một dòng lệnh trong lịch sử.

Nếu bạn muốn cả hai tính năng không liên tục, bạn sẽ cần chọn một nhân vật khác làm nhân vật thứ 3 $histchars. Bạn muốn một cái mà không ảnh hưởng đến cách diễn giải lệnh của bạn. Một vài lựa chọn:

  • sử dụng dấu gạch chéo ngược ( \): \echo foohoạt động nhưng có tác dụng phụ là vô hiệu hóa bí danh hoặc từ khóa.
  • TAB: để chèn nó vào vị trí đầu tiên, bạn cần nhấn Ctrl+VTabmặc dù.
  • nếu bạn không nhớ gõ hai phím, bạn có thể chọn bất kỳ nhân vật mà bình thường không xuất hiện ở vị trí đầu tiên ( %, @, ?, lấy của riêng bạn) và tạo một bí danh có sản phẩm nào cho nó:

    histchars='!^%'
    alias %=
    

    Sau đó nhập ký tự đó, dấu cách và lệnh của bạn:

    bash-4.3$ % echo !!
    !!
    

(bạn sẽ không thể không ghi lại một lệnh mà thay lịch sử đã bị vô hiệu mặc dù. Cũng lưu ý rằng mặc định nhân vật thứ 3 của $histchars#để mở rộng lịch sử không được thực hiện trong ý kiến. Nếu bạn thay đổi nó, và bạn nhập ý kiến tại nhắc nhở, bạn nên biết thực tế là! trình tự có thể được mở rộng ở đó).


2

Các giải pháp đề xuất không hoạt động, ví dụ, ví dụ sau:

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

Trong trường hợp này, tiếng nổ có thể được in bằng mã ASCII bát phân của nó:

$ bash -c "echo -e 'hello World\0041'"
hello World!
$

1
Trong lệnh đầu tiên của bạn, !nằm giữa hai dấu ngoặc kép, trong đó các câu trả lời khác cho thấy nó vẫn giữ ý nghĩa mở rộng lịch sử của nó. Bạn có thể sử dụng bash -c 'echo '\''hello World!'\'hoặc bash -c "echo 'hello World"\!"'".
Gilles 'SO- ngừng trở nên xấu xa'

Nếu không có tham số -i, môi trường có thể bị thay đổi (như ~ / .profile của bạn không chạy). Tuy nhiên, làm -i có nghĩa là bất kỳ đầu ra nào từ .profile của bạn sẽ được ghi lại trong đầu ra của lệnh bạn thực sự muốn chạy.
Brent

-1

Kể từ Bash 4.3, bây giờ bạn có thể sử dụng dấu ngoặc kép để trích dẫn ký tự mở rộng lịch sử:

$ bash --version
GNU bash, version 4.3...
[...]
$ echo "Hello World!"
Hello World!

4
Không, điều đó chỉ !theo sau "đó không còn đặc biệt. echo "foo!"không sao, echo "foo!bar"không phải
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.