Bí ẩn mở rộng cú đúp trong Bash


19

Điều này:

$ echo {{a..c},{1..3}}

sản xuất này:

a b c 1 2 3

Điều này là tốt, nhưng khó để giải thích cho rằng

$ echo {a..c},{1..3}

cho

a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

Đây có phải là tài liệu ở đâu đó? Các Bash Reference không đề cập đến nó (mặc dù nó có một ví dụ sử dụng nó).

Câu trả lời:


18

Vâng, nó được làm sáng tỏ một lớp tại một thời điểm:

X{{a..c},{1..3}}Y

được ghi chép lại như được mở rộng để X{a..c}Y X{1..3}Y(đó là X{A,B}Ymở rộng ra XA XBvới Abị {a..c}Bđược {1..3}), mình ghi nhận như được mở rộng đểXaY XbY XcY X1Y X2Y X3Y .

Những gì có thể có giá trị tài liệu là chúng có thể được lồng nhau (rằng cái đầu tiên }không đóng cái đầu tiên{ ví dụ trong đó).

Tôi cho rằng đạn pháo có thể đã chọn giải quyết niềng răng bên trong trước, giống như bằng cách }lần lượt thực hiện lần lượt:

  1. X{{a..c},{1..3}}
  2. X{a,{1..3}}Y X{b,{1..3}}Y X{c,{1..3}}Y

    (có nghĩa là A{a..c}Bmở rộng ra AaB AbB AcB, nơi AX{B,{1..3}Y)

  3. X{a,1}Y X{a,2}Y X{a,3}Y X{b,1}Y X{b,2}Y X{b,3}Y X{c,1}Y X{c,2}Y X{c,3}Y

  4. XaY X1Y XaY Xa2...

Nhưng tôi không thấy rằng đặc biệt trực quan hơn và hữu ích hơn (ví dụ như ví dụ của Kevin trong các bình luận), vẫn sẽ có một sự mơ hồ về thứ tự mở rộng sẽ được thực hiện, và đó không phải là cách csh(vỏ được giới thiệu niềng răng mở rộng vào cuối những năm 70, trong khi {1..3}hình thức đến sau (1995) từ zsh{a..c}sau đó (2004) từ bash) đã làm điều đó.

Lưu ý rằng csh(ngay từ đầu, hãy xem trang man 2BSD (1979) ) đã ghi lại thực tế rằng việc mở rộng niềng răng có thể được lồng vào nhau, mặc dù không nói rõ ràng việc mở rộng niềng răng lồng nhau sẽ được mở rộng như thế nào. Nhưng bạn có thể nhìn vào cshmã từ năm 1979 để xem nó đã được thực hiện như thế nào sau đó. Xem cách nó thực sự xử lý việc làm tổ thực sự và cách nó được giải quyết bắt đầu từ các dấu ngoặc ngoài.

Trong mọi trường hợp, tôi không thực sự thấy việc mở rộng {a..c},{1..3}có thể có bất kỳ ảnh hưởng nào. Trong đó, ,không phải là người vận hành mở rộng niềng răng (vì nó không nằm trong niềng răng), nên được đối xử như bất kỳ nhân vật bình thường nào.


Có vẻ lạ đối với tôi rằng niềng răng bên ngoài được cho là sẽ được giải quyết trước những cái bên trong.
Hauke ​​Laging

@ stéphane-chazelas Có hai cách rõ ràng mà biểu thức này có thể được phân tích cú pháp. Tại sao nó được phân tích cú pháp một cách mà không phải là cách khác? Nhận xét của bạn dường như không đưa ra một lời giải thích.
igal

Vì vậy, lời giải thích đó có ý nghĩa, nhưng nếu điều này "được ghi nhận là được mở rộng thành ..." thì có URL không?
xenoid

@xenoid Xem giải pháp cập nhật của tôi.
igal

1
@ (mọi người): Hãy xem xét việc mở rộng /dev/{h,s}d{a..d}{1..4,}. Bây giờ giả sử bạn muốn mở rộng nó để bao gồm /dev/null/dev/zero. Nếu mở rộng niềng răng hoạt động từ trong ra ngoài, việc mở rộng đó sẽ thực sự gây khó chịu khi thi công. Nhưng bởi vì nó hoạt động từ bên ngoài vào, nó khá tầm thường:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Kevin

7

Đây là câu trả lời ngắn. Trong biểu thức đầu tiên, dấu phẩy được sử dụng như một dấu phân cách, vì vậy việc mở rộng dấu ngoặc chỉ là sự kết hợp của hai biểu thức con lồng nhau. Trong biểu thức thứ hai, dấu phẩy được coi là biểu thức con một ký tự, do đó các biểu thức sản phẩm được hình thành.

Những gì bạn đã thiếu là định nghĩa về cách thực hiện mở rộng cú đúp. Dưới đây là ba tài liệu tham khảo:

Một lời giải thích chi tiết hơn sau đây.


Bạn đã so sánh kết quả của biểu thức này:

$ echo {{a..c},{1..3}}
a b c 1 2 3

kết quả của biểu thức này:

$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

Bạn nói rằng điều này thật khó để giải thích, tức là điều này là phản trực giác. Điều còn thiếu là một định nghĩa chính thức về cách mở rộng niềng răng được xử lý. Bạn lưu ý rằng Hướng dẫn Bash không đưa ra định nghĩa đầy đủ.

Tôi đã tìm kiếm một chút nhưng tôi cũng không thể tìm thấy định nghĩa còn thiếu (hoàn chỉnh, chính thức). Vì vậy, tôi đã đi đến mã nguồn:

Nguồn chứa một vài ý kiến ​​hữu ích. Đầu tiên là tổng quan cấp cao về thuật toán mở rộng niềng răng:

Basic idea:

Segregate the text into 3 sections: preamble (stuff before an open brace),
postamble (stuff after the matching close brace) and amble (stuff after
preamble, and before postamble).  Expand amble, and then tack on the
expansions to preamble.  Expand postamble, and tack on the expansions to
the result so far.

Vì vậy, định dạng của mã thông báo mở rộng cú đúp là như sau:

<PREAMBLE><AMBLE><POSTAMBLE>

Điểm vào chính để mở rộng là một hàm được gọi brace_expandlà mô tả như sau:

Return an array of strings; the brace expansion of TEXT.

Vì vậy, brace_expandhàm lấy một chuỗi biểu thị một biểu thức mở rộng dấu ngoặc và trả về mảng các chuỗi được mở rộng.

Kết hợp hai quan sát này, chúng ta thấy rằng amble được mở rộng thành một danh sách các chuỗi, mỗi chuỗi được nối vào phần mở đầu. Sau đó, postamble được mở rộng thành một danh sách các chuỗi và mỗi chuỗi trong danh sách postamble được nối vào từng chuỗi trong danh sách mở đầu / amble (nghĩa là sản phẩm của hai danh sách được tạo thành). Nhưng điều này không mô tả cách xử lý amble và postamble. May mắn thay có một bình luận mô tả đó là tốt. Amble được xử lý bởi một hàm gọi là expand_ambleđịnh nghĩa được đi trước bởi nhận xét sau:

Expand the text found inside of braces.  We simply try to split the
text at BRACE_ARG_SEPARATORs into separate strings.  We then brace
expand each slot which needs it, until there are no more slots which
need it.

Ở những nơi khác trong mã chúng ta thấy rằng BRACE_ARG_SEPARATOR được xác định là dấu phẩy. Điều này cho thấy rõ rằng amble là một danh sách các chuỗi được phân tách bằng dấu phẩy, một số trong đó cũng có thể là biểu thức mở rộng dấu ngoặc. Các chuỗi này sau đó tạo thành một mảng duy nhất. Cuối cùng, chúng ta cũng có thể thấy rằng sau khi expand_ambleđược gọi là brace_expandhàm sau đó được gọi đệ quy trên postamble. Điều này cung cấp cho chúng tôi một mô tả đầy đủ của thuật toán.

Có một số tài liệu tham khảo (không chính thức) khác chứng thực phát hiện này.

Để tham khảo, hãy xem Bash Hackers Wiki . Phần kết hợp và lồng nhau không giải quyết được vấn đề của bạn, nhưng trang này đưa ra cú pháp / ngữ pháp của việc mở rộng dấu ngoặc, mà tôi nghĩ là đã trả lời câu hỏi của bạn. Cú pháp được đưa ra bởi các mẫu sau:

{string1,string2,...,stringN}

{<START>..<END>}

<PREAMBLE>{........}

{........}<POSTSCRIPT>

<PREAMBLE>{........}<POSTSCRIPT>

Và phân tích cú pháp được mô tả như sau:

Mở rộng cú đúp được sử dụng để tạo các chuỗi tùy ý. Các chuỗi được chỉ định được sử dụng để tạo ra tất cả các kết hợp có thể với các phần mở đầu và phần mô tả xung quanh tùy chọn.

Để tham khảo, hãy xem Hướng dẫn dành cho người mới bắt đầu của Bash , có đoạn sau:

Brace expansion is a mechanism by which arbitrary strings may be generated. Patterns to be brace-expanded take the form of an optional PREAMBLE, followed by a series of comma-separated strings between a pair of braces, followed by an optional POSTSCRIPT. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.

Vì vậy, để phân tích các biểu thức mở rộng dấu ngoặc, chúng ta đi từ trái sang phải, mở rộng từng biểu thức và tạo thành các sản phẩm kế tiếp nhau (liên quan đến hoạt động nối chuỗi).

Bây giờ hãy xem xét biểu hiện đầu tiên của bạn:

{{a..c},{1..3}}

Trong ngôn ngữ của Bash Hack Bash Wiki, mẫu này khớp với mẫu đầu tiên:

{string1,string2,...,stringN}

Trường hợp N=2, string1={a..c}string2={1..3}- mở rộng niềng răng bên trong được thực hiện đầu tiên và mỗi trong số chúng đều có dạng {<START>..<END>}. Ngoài ra, chúng ta có thể nói rằng đây là một biểu thức mở rộng cú đúp chỉ bao gồm một amble (không có phần mở đầu hoặc postamble). Amble là một danh sách được phân tách bằng dấu phẩy, vì vậy chúng tôi đi qua danh sách một vị trí tại một thời điểm và thực hiện các mở rộng bổ sung khi cần thiết. Không có sản phẩm nào được hình thành vì không có biểu thức liền kề (dấu phẩy được sử dụng làm dấu phân cách).

Tiếp theo hãy nhìn vào biểu thức thứ hai của bạn:

{a..c},{1..3}

Trong ngôn ngữ của Bash Hacker Wiki, biểu thức này khớp với biểu mẫu:

{........}<POSTSCRIPT>

trong đó phần tái bút là biểu thức con ,{1..3}. Ngoài ra, chúng ta có thể nói rằng biểu thức này có amble ( {a..c}) và postamble ( ,{1..3}). Amble được mở rộng vào danh sách a b cvà sau đó mỗi chuỗi này được nối với từng chuỗi trong quá trình mở rộng của postamble. Postamble được xử lý đệ quy: nó có phần mở đầu ,và khả năng {1..3}. Điều này được mở rộng vào danh sách ,1 ,2 ,3. Hai danh sách a b c,1 ,2 ,3sau đó được kết hợp để tạo thành danh sách sản phẩm a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3.

Nó có thể giúp đưa ra một mô tả đại số psuedo về cách các biểu thức này được phân tích cú pháp, trong đó dấu ngoặc "[]" biểu thị mảng, "+" biểu thị phép nối mảng và "*" biểu thị sản phẩm của Cartesian (đối với phép nối).

Đây là cách biểu thức đầu tiên được mở rộng (một bước trên mỗi dòng):

{{a..c},{1..3}}
{a..c} + {1..3}
[a b c] + [1 2 3]
a b c 1 2 3

Và đây là cách biểu thức thứ hai được mở rộng:

{a..c},{1..3}
{a..c} * ,{1..3}
[a b c] * [,1 ,2 ,3]
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

2

Hiểu biết của tôi là thế này:

Niềng răng bên trong được giải quyết trước tiên (như mọi khi)

{{a..c},{1..3}}

vào

{a,b,c,1,2,3}

Bởi vì ,trong vòng niềng răng, nó chỉ phân tách các yếu tố niềng răng.

Nhưng trong trường hợp

{a..c},{1..3}

các ,không nằm trong dấu ngoặc tức là nó là một nhân vật bình thường gây ra hoán vị nẹp ở hai bên.


Vì vậy, {a..c}hoặc giải quyết a,b,choặc a b cphụ thuộc vào độ ẩm và Dow Jones? Khéo léo.
kubanchot

Điều này có vẻ hơi khó hiểu. Nếu {{a..c},{1..3}}giống như vậy {a,b,c,1,2,3}, thì không nên {{a..c}.{1..3}}giống như {a,b,c.1,2,3}? Tất nhiên đây không phải là trường hợp.
ilkkachu

@ilkkachu Tại sao phải như vậy? ,là nhân vật phân tách mở rộng cú đúp, .là không. Tại sao một nhân vật bình thường nên dẫn đến kết quả giống như một nhân vật đặc biệt? c.1là một yếu tố cú đúp. Nhưng trong {a..c}.{1..3}đó .là mỏ neo cho việc mở rộng nẹp ở bên trái và bên phải. Với ,các dấu ngoặc ngoài được sử dụng để mở rộng dấu ngoặc vì nội dung của chúng có định dạng mở rộng dấu ngoặc, với .chúng không phải vì nội dung của chúng không có định dạng đó.
Hauke ​​Laging

@HaukeLaging, tốt, nếu {{a..c},{1..3}}lần lượt vào {a,b,c,1,2,3}sau đó một số dấu phẩy chỉ xuất hiện giữa a, bc. Tại sao chúng không xuất hiện theo cùng một cách với {a..c}.{1..3}? Nhận xét của @kubanchot là về điều tương tự, nếu dấu phẩy xuất hiện ở đó như thế, làm sao chúng ta biết khi nào bản mở rộng tạo ra dấu phẩy và khi nào thì không? Câu trả lời tất nhiên là, nó không bao giờ tự tạo bất kỳ dấu phẩy nào, nó tạo ra một danh sách các từ. Vì vậy, không có gì được biến thành {a,b,c,1,2,3}hoặc {a,b,c.1,2,3}.
ilkkachu

@kubanchot Bạn không nên tạo ra những câu trả lời thú vị mà bạn không hiểu.
Hauke ​​Laging
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.