Làm thế nào để bash phân biệt giữa mở rộng cú đúp và nhóm lệnh?


48

Tôi nhận thấy rằng {có thể được sử dụng trong mở rộng niềng răng :

echo {1..8}

hoặc trong nhóm lệnh:

{ls;echo hi}

Làm thế nào để bash biết sự khác biệt?


1
Câu hỏi xuất sắc, +1. Có vẻ như nó có thể được {hiểu là một danh sách lệnh nếu nó xuất hiện ở đầu lệnh và dưới dạng mở rộng dấu ngoặc, nhưng tôi không chắc chắn.
Celada

16
{ls;echo hi}không hợp pháp bash. Bạn cần một khoảng trống sau dấu ngoặc mở và dấu chấm phẩy trước khi đóng.
PSkocik

Câu trả lời:


39

Một lý do đơn giản là sự tồn tại của một ký tự : space.

Mở rộng cú đúp không xử lý không gian (không trích dẫn).

Một {...}danh sách cần không gian (không trích dẫn).

Câu trả lời chi tiết hơn là cách shell phân tích một dòng lệnh .


Bước đầu tiên để phân tích (hiểu) một dòng lệnh là chia nó thành nhiều phần.
Các phần này (thường được gọi là từ hoặc mã thông báo) là kết quả của việc phân chia một dòng lệnh tại mỗi ký tự meta từ liên kết :

  1. Tách lệnh thành các mã thông báo được phân tách bằng tập hợp các ký tự meta cố định: SPACE, TAB, NEWLINE,;, (,), <,>, |, và &. Các loại mã thông báo bao gồm từ, từ khóa, chuyển hướng I / O và dấu chấm phẩy.

Siêu ký tự: spacetabenter;,<>|&.

Sau khi tách, các từ có thể thuộc loại (theo cách hiểu của trình bao):

  • Lệnh trước lệnh: LC=ALL ...
  • Chỉ huy LC=ALL echo
  • Tranh luận LC=ALL echo "hello"
  • Chuyển hướng LC=ALL echo "hello" >&2

Niềng răng mở rộng

Chỉ khi "chuỗi cú đúp" (không có dấu cách hoặc ký tự meta) là một từ đơn (như được mô tả ở trên) và không được trích dẫn , thì đó là một ứng cử viên cho "Mở rộng cú đúp ". Nhiều kiểm tra được thực hiện trên cấu trúc nội bộ sau này.

Như vậy, điều này: {ls,-l}đủ tiêu chuẩn là "Brace mở rộng" để trở thành ls -l, hoặc là first wordhoặc argument(trong bash, zsh là khác nhau).

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

Nhưng điều này sẽ không : {ls ,-l}. Bash sẽ phân tách spacevà phân tích dòng thành hai từ: {ls,-l}sẽ kích hoạt một command not found(đối số ,-l}bị mất):

 $ {ls ,-l}
 bash: {ls: command not found

Dòng của bạn: {ls;echo hi}sẽ không trở thành "Mở rộng cú đúp" vì hai ký tự meta ;space.

Nó sẽ được chia thành ba phần này: {lslệnh mới : echo hi}. Hiểu rằng ;kích hoạt bắt đầu một lệnh mới. Lệnh {lssẽ không được tìm thấy và lệnh tiếp theo sẽ in hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

Nếu nó được đặt sau một số lệnh khác, thì dù sao nó cũng sẽ bắt đầu một lệnh mới sau ;:

$ echo {ls;echo hi}
{ls
hi}

Danh sách

Một trong những "lệnh ghép" là "Danh sách cú đúp" (từ của tôi) : { list; }.
Như bạn có thể thấy, nó được xác định bằng dấu cách và đóng ;.
Các khoảng trắng và ;là cần thiết bởi vì cả hai {}là " Từ dành riêng ".

Và do đó, để được công nhận là từ, phải được bao quanh bởi các ký tự meta (hầu như luôn luôn space:).

Như được mô tả trong điểm 2 của trang được liên kết

  1. Kiểm tra mã thông báo đầu tiên của mỗi lệnh để xem nó có phải là ...., {hoặc (, sau đó lệnh thực sự là một lệnh ghép.

Ví dụ của bạn: {ls;echo hi}không phải là một danh sách.

Nó cần một đóng cửa ;và một không gian (ít nhất) sau {. Cuối cùng }được xác định bởi việc đóng cửa ;.

Đây là một danh sách { ls;echo hi; }. Và đây { ls;echo hi;}cũng là (ít được sử dụng, nhưng hợp lệ) (Cảm ơn @choroba đã giúp đỡ).

$ { ls;echo hi; }
A-list-of-files
hi

Nhưng là đối số (shell biết sự khác biệt) với một lệnh, nó gây ra lỗi:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

Nhưng hãy cẩn thận với những gì bạn tin rằng shell đang phân tích cú pháp:

$ echo { ls;echo hi;
{ ls
hi

2
Đây thực sự là câu trả lời tốt nhất, bởi vì bạn cung cấp cho chúng tôi thực sự cách trình phân tích cú pháp bash hoạt động! và với một lời giải thích chi tiết!
lovespring

2
Bạn không cần khoảng trống giữa ;}. { ls;}hoạt động như dấu chấm phẩy đã là một siêu ký tự.
choroba

1
@lovespring Cảm ơn, vâng tôi đã đầu tư một chút thời gian để viết nó. Tôi rất vui khi biết rằng nó hữu ích. Một lần nữa cám ơn.

bài viết tuyệt vời, cảm ơn rất nhiều vì đã tham khảo
Edward Torvalds

16

Khối {là một từ khóa shell, vì vậy nó phải tách biệt với từ tiếp theo theo khoảng trắng, trong khi mở rộng dấu ngoặc, không nên có khoảng trắng (nếu bạn cần niềng mở rộng một khoảng trắng, bạn phải thoát nó echo {\ ,a}{b,c}:).

Bạn có thể sử dụng mở rộng dấu ngoặc khi bắt đầu lệnh:

{ls,.}  # expands to "ls ."

Tuy nhiên, bạn không thể sử dụng nó để mở rộng thành một khối, vì việc phân tích các lệnh nhóm xảy ra trước khi mở rộng:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory

5

Nó biết bằng cách kiểm tra cú pháp của dòng lệnh. Theo cùng một cách nó biết rằng trong biểu thức echo echo, tiếng vang đầu tiên phải được coi là một lệnh và tiếng vang thứ hai là một tham số của tiếng vang đầu tiên.

Trong bash nó rất đơn giản, vì { cmd; }nên có dấu cách và dấu chấm phẩy. Tuy nhiên, ví dụ trong zsh, chúng không cần thiết, nhưng vẫn bằng cách phân tích bối cảnh của {}shell có thể cho biết những gì nên được thực hiện với nội dung của nó.

Hãy xem xét những điều sau đây:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

Cả hai trả về ngày hiện tại, nhưng

echo {1..3}

trả về 1 2 3vì shell biết {}trong một đối số cho lệnh echo, vì vậy nên được mở rộng.


{theo sau là không gian không trích dẫn không bắt đầu mở rộng cú đúp trong bash.
choroba

@choroba Có, và không chỉ ngay sau đó {. Không gian không trích dẫn không thể ở bất cứ đâu vì shell chia toàn bộ dòng lệnh trên khoảng trắng.
jimmij

0

Đầu tiên, một dấu ngoặc kép phải là một từ của chính nó và từ đầu tiên của dòng lệnh:

echo { these braces are just words }

Thứ hai, niềng răng riêng lẻ không đặc biệt (như bạn có thể thấy ở trên). Niềng răng trống cũng không đặc biệt:

echo {} # just the token {}: familiar from the find command

Bất cứ điều gì mà không có dấu phẩy cũng chỉ là chính nó

echo {abc} # just {abc}

Đây là nơi hành động bắt đầu.

echo {a,b} # braces disappear, a b results.

Vì vậy, về cơ bản để mở rộng cú đúp để khởi động, chúng ta cần một từ duy nhất (không được tách thành các trường trên khoảng trắng), bên trong đó xảy ra ít nhất một trường hợp {...}bên trong có ít nhất một dấu phẩy.

Nhân tiện, đây có thể là từ đầu tiên trong dòng lệnh:

{ls,-l} .   # just "ls -l ."
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.