Tại sao dấu chấm than `!` Đôi khi làm bash?


13

Tôi nhận ra rằng nó !có ý nghĩa đặc biệt đối với dòng lệnh trong ngữ cảnh của lịch sử dòng lệnh, nhưng ngoài ra, trong một kịch bản chạy, dấu chấm than đôi khi có thể gây ra lỗi phân tích cú pháp.
Tôi nghĩ rằng nó có một cái gì đó để làm với một event, nhưng tôi không biết một sự kiện là gì hoặc nó làm gì. Mặc dù vậy, cùng một lệnh có thể hành xử khác nhau trong các tình huống khác nhau.
Ví dụ cuối cùng, dưới đây, gây ra lỗi; Nhưng tại sao, khi cùng một mã làm việc bên ngoài thay thế lệnh? .. sử dụng GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found


@Warren .. Cảm ơn. Tôi đã thấy QA đó, nhưng nó thực sự chỉ nói về cách thoát khỏi dấu gạch chéo ngược ... Vấn đề của tôi liên quan nhiều hơn đến lý do tại sao mã dường như đã thoát được hoạt động trong một tình huống chứ không phải ...
Peter.O

@fred: "dường như đã thoát"? Tôi không thấy bất kỳ lối thoát nào cả và bạn đang sử dụng dấu ngoặc kép. Xem câu trả lời (sửa đổi) của tôi. Phần nào bạn nghĩ là thoát?
Caleb

@Caleb. Có, tôi đã sử dụng thuật ngữ sai .. protectedsẽ thích hợp hơn. (được bảo vệ bởi 'dấu ngoặc đơn')
Peter.O

Nếu bạn chỉ quan tâm đến các bài tập đơn giản, thì bạn có thể sử dụng var=$(…)(không có dấu ngoặc kép), và nó sẽ hoạt động như (tôi nghĩ) bạn mong đợi. Đây là vẫn “an toàn” vì phần giá trị của một nhiệm vụ đơn giản không phải là đối tượng để tách từ hoặc globbing (mặc dù điều này có thể không đúng với nhiệm vụ thực hiện thông qua lệnh nội trú (ví dụ export, localvv) theo tất cả vỏ). Thật không may, điều này không vượt quá các bài tập đơn giản vì các trích dẫn kép là cách để bảo vệ chống lại sự phân tách và tạo khối trong khi vẫn có các kiểu mở rộng khác trong các ngữ cảnh khác.
Chris Johnsen

Câu trả lời:


11

Các !nhân vật gọi thay thế lịch sử của bash. Khi được theo sau bởi một chuỗi (như trong ví dụ thất bại của bạn), nó cố gắng mở rộng đến sự kiện lịch sử cuối cùng bắt đầu bằng chuỗi đó. Giống như $varđược mở rộng thành giá trị của chuỗi đó, !echosẽ mở rộng đến lệnh echo cuối cùng trong lịch sử của bạn.

Không gian là một nhân vật phá vỡ trong những mở rộng như vậy. Đầu tiên lưu ý làm thế nào điều này sẽ làm việc với các biến:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

Điều tương tự sẽ xảy ra cho việc mở rộng lịch sử. Ký tự bang ( !) bắt đầu một chuỗi thay thế lịch sử, nhưng chỉ khi được theo sau bởi một chuỗi. Theo sau nó với một khoảng trắng làm cho nó theo nghĩa đen thay vì một phần của chuỗi thay thế.

Bạn có thể tránh loại thay thế này cho cả hai biến và lịch sử bằng cách sử dụng dấu ngoặc đơn. Ví dụ đầu tiên của bạn sử dụng dấu ngoặc đơn và do đó chạy tốt. Các ví dụ cuối cùng của bạn nằm trong dấu ngoặc kép và do đó bash quét chúng cho các chuỗi mở rộng trước khi nó làm bất cứ điều gì khác. Lý do duy nhất khiến người đầu tiên không vấp ngã là không gian là một nhân vật phá vỡ như hình trên.


Cảm ơn Caleb .. Một trong những quan niệm trước đây của tôi đã bị bác bỏ ... Tôi nghĩ rằng phân tích cú pháp bash được thực hiện từ khung trong cùng hoặc dấu ngoặc, và sau đó hoạt động ra bên ngoài ... Có vẻ như bash phân tích khác với giả định của tôi.
Peter.O

1
Trích dẫn là đủ khó hiểu trong bash mà không thay đổi trong các chuỗi lồng nhau. Vì nó là, sự thay thế xảy ra rất sớm trong quá trình. Xem xét ví dụ này:var=word; echo "test '$var'"; echo 'test "$var"'
Caleb

.. Có bất thiện. Tôi đã nhận thức được các dấu ngoặc kép trong dấu ngoặc kép ... Sự hiểu lầm của tôi là tôi nghĩ rằng mã trong dấu ngoặc thay thế lệnh sẽ được phân tách riêng biệt, với những gì bao quanh các dấu ngoặc đó; nhưng dường như không .. cảm ơn.
Peter.O

6

Như Caleb đã nói , !được sử dụng để gọi thay thế lịch sử của bash.

Nếu giống như tôi, bạn cảm thấy bạn không cần một tính năng như vậy, bạn có thể vô hiệu hóa nó chèn dòng sau vào ~/.bashrc:

set +H

Tôi không cần nó bởi vì lịch sử có thể được phục hồi bằng mũi tên lên và Ctrl- rtìm kiếm ngược tăng dần. Xem trang hướng dẫn của bash, phần Lệnh để Thao tác Lịch sử để biết danh sách chi tiết các phím tắt.


2
Làm thế nào để bạn sống mà không có !!?
Caleb

Cảm ơn., Tôi sẽ nghĩ rằng đó có thể là một vấn đề, tính di động, nhưng sử dụng set +Htrong kịch bản cũng hoạt động tốt :) +1
Peter.O

2
@fred: lạ, nói chung, mở rộng lịch sử chỉ "bật" cho các vỏ tương tác.
enzotib

@enzo .. Cảm ơn một lần nữa .. Tôi đã thử nó từ dòng lệnh .. Ah! Nếu việc học không thú vị lắm, thì thật tẻ nhạt ... tôi có nhắc đến cà phê không? nó cũng giúp :)
Peter.O

Ya, đó là một gotcha. Tôi đã sao chép mã đã dán từ các tập lệnh thất bại trên dòng lệnh chỉ vì lý do này. Mở rộng lịch sử không phải là một mối quan tâm trong kịch bản nhưng nó nằm trên một vỏ tương tác.
Caleb

2

ví dụ đầu tiên của bạn:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

có thể được giảm xuống

echo '! p' 
echo '!p'

Trong dấu ngoặc đơn, tất cả các ký tự bảo tồn các giá trị theo nghĩa đen của chúng. Vì vậy, !đã mất ý nghĩa đặc biệt của nó và mở rộng lịch sử không được tạo thành trước.

ví dụ thứ hai và thứ ba của bạn:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

có thể được giảm xuống

echo "'! p'"

echo "'!p'"

'! p''!p'về cơ bản là một phần của chuỗi trích dẫn kép.

Trong dấu ngoặc kép, tất cả các nhân vật bảo tồn các giá trị văn chương của họ ngoại trừ $ , `, \!.

Điều đó ngụ ý các trích dẫn duy nhất '! p''!p'đã mất ý nghĩa đặc biệt của chúng (nghĩa là: không thể thoát ra !) nhưng !vẫn giữ ý nghĩa đặc biệt của nó do đó mở rộng lịch sử được thực hiện.

Tuy nhiên, khi !được theo sau bởi một ký tự khoảng trắng, việc mở rộng lịch sử không được thực hiện.

Trích dẫn từ man bash:

NHANH CHÓNG

[...]

Các ký tự kèm theo trong dấu ngoặc đơn duy trì giá trị theo nghĩa đen của từng ký tự trong dấu ngoặc kép. [...]

Các ký tự được đặt trong dấu ngoặc kép sẽ giữ giá trị bằng chữ của tất cả các ký tự trong dấu ngoặc kép, ngoại trừ $, `, \, và, khi mở rộng lịch sử được bật,!. [...] Nếu được bật, mở rộng lịch sử sẽ được thực hiện trừ khi! xuất hiện trong dấu ngoặc kép được thoát bằng dấu gạch chéo ngược. Dấu gạch chéo ngược trước! không được gỡ bỏ.

MỞ RỘNG LỊCH SỬ

[...]

Mở rộng lịch sử được giới thiệu bởi sự xuất hiện của nhân vật mở rộng lịch sử, đó là! theo mặc định Chỉ dấu gạch chéo ngược (\) và dấu ngoặc đơn mới có thể trích dẫn ký tự mở rộng lịch sử.

Một số ký tự ngăn cản việc mở rộng lịch sử nếu được tìm thấy ngay sau ký tự mở rộng lịch sử, ngay cả khi nó không được trích dẫn: dấu cách, tab, dòng mới, trả về vận chuyển và =. Nếu tùy chọn shell extglob được bật, (cũng sẽ ức chế mở rộng.

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.