Tại sao {1,2} được in bởi một lệnh bằng $ () không được nội suy?


8

Tôi đang ở trong một thư mục chứa hai tệp văn bản:

$ touch test1.txt
$ touch test2.txt

Khi tôi cố gắng liệt kê các tệp (với Bash) bằng một số mẫu, nó hoạt động:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

Tuy nhiên, khi một mẫu được tạo bởi một lệnh kèm theo $(), chỉ một mẫu trong số đó hoạt động:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

Những gì đang xảy ra ở đây? Tại sao mô hình {1,2}không hoạt động?


4
Mở rộng cú đúp không được thực hiện trong dấu ngoặc đơn hoặc kép
Sergiy Kolodyazhnyy

3
@SergiyKolodyazhnyy Điểm của câu hỏi là ?được cũng trích dẫn, và được mở rộng sau khi $(...)thay thế nó, nhưng việc mở rộng cú đúp thì không.
Michael Homer

1
@muru Không, đó không phải là vấn đề tương tự. Ở đây thứ tự mở rộng không thành vấn đề, điều quan trọng là việc mở rộng diễn ra trong bối cảnh nào. Tôi sẽ không ngạc nhiên nếu câu hỏi này là một bản sao, nhưng tôi không thể tìm thấy nó.
Gilles 'SO- ngừng trở nên xấu xa'

1
@mosvy Ksh và bash thực hiện mở rộng theo cùng một thứ tự, nhưng ksh thực hiện mở rộng cú đúp trong trường hợp bash hoàn toàn không làm điều đó. Zsh-with-globsubst thực hiện các mở rộng tương tự như bash, nhưng theo một thứ tự khác.
Gilles 'SO- ngừng trở nên xấu xa'

1
@Gilles không họ không. Theo tài liệu và dễ dàng chứng minh, ksh (và zsh) sẽ thực hiện việc mở rộng niềng răng ngay trước khi toàn cầu hóa. zsh-with-globsubst sẽ không thực hiện bất kỳ sự mở rộng niềng răng nào trên kết quả của việc mở rộng $: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
mosvy

Câu trả lời:


17

Đó là sự kết hợp của hai thứ. Đầu tiên, mở rộng dấu ngoặc không phải là một mẫu khớp với tên tệp: đó là sự thay thế hoàn toàn bằng văn bản - xem Sự khác biệt giữa `a [bc] d` (ngoặc) và` a {b, c} d` (dấu ngoặc) là gì? . Thứ hai, khi bạn sử dụng kết quả của một sự thay thế lệnh bên ngoài dấu ngoặc kép ( ls $(…)), những gì xảy ra chỉ là khớp mẫu (và tách từ: toán tử chia tách + toàn cầu), không phải là phân tích lại hoàn toàn.

Với ls $(echo 'test?.txt'), lệnh echo 'test?.txt'xuất ra chuỗi test?.txt(với một dòng mới cuối cùng). Việc thay thế lệnh dẫn đến chuỗi test?.txt(không có dòng mới cuối cùng, vì lệnh thay thế dải theo dõi dòng mới). Sự thay thế không được trích dẫn này trải qua quá trình phân tách từ, thu được một danh sách bao gồm một chuỗi test?.txtvì không có ký tự khoảng trắng (chính xác hơn là không có ký tự nào trong đó $IFS). Mỗi phần tử của danh sách một phần tử này sau đó trải qua quá trình mở rộng ký tự đại diện có điều kiện và do có một ký tự đại diện ?trong chuỗi nên việc mở rộng ký tự đại diện xảy ra. Vì mẫu test?.txtkhớp với ít nhất một tên tệp, nên thành phần danh sách test?.txtđược thay thế bằng danh sách tên tệp khớp với các mẫu, thu được danh sách hai thành phần có chứatest1.txttest2.txt. Cuối cùng lsđược gọi với hai đối số test1test2.

Với ls $(echo 'test{1,2}'), lệnh echo 'test{1,2}'xuất ra chuỗi test{1,2}(với một dòng mới cuối cùng). Lệnh thay thế kết quả trong chuỗi test{1,2}. Sự thay thế không được trích dẫn này trải qua quá trình phân tách từ, thu được một danh sách bao gồm chuỗi đơn test{1,2}. Mỗi phần tử của danh sách một phần tử này sau đó trải qua quá trình mở rộng ký tự đại diện có điều kiện, không có gì (phần tử được giữ nguyên) vì không có ký tự đại diện trong chuỗi. Do đó lsđược gọi với các đối số duy nhất test{1,2}.

Để so sánh, đây là những gì xảy ra với ls $(echo test{1,2}). Lệnh echo test{1,2}xuất chuỗi test1 test2(với một dòng mới cuối cùng). Lệnh thay thế dẫn đến chuỗi test1 test2(không có dòng mới). Sự thay thế không được trích dẫn này trải qua quá trình phân tách từ, thu được hai chuỗi test1test2. Sau đó, vì cả hai chuỗi không chứa ký tự đại diện, chúng được để yên, do đó lsđược gọi với hai đối số test1test2.


3
Lưu ý rằng pdksh và ksh93 thực hiện mở rộng niềng răng khi mở rộng (trước khi toàn cầu hóa, không phải với noglob, nhưng trong trường hợp của ksh93, vẫn còn khi braceexpand bị tắt!)
Stéphane Chazelas

Bạn dường như quên .txttrong lời giải thích thứ hai.
val nói Phục hồi Monica

10

Thứ tự mở rộng là: mở rộng cú đúp; mở rộng dấu ngã, mở rộng tham số và biến, mở rộng số học và thay thế lệnh (được thực hiện theo kiểu từ trái sang phải); tách từ; và mở rộng tên tệp.

Mở rộng cú đúp sẽ không xảy ra sau khi thay thế lệnh. Bạn có thể sử dụng eval để buộc một vòng mở rộng khác:

eval echo $(echo '{1,2}lala')

Đó là kết quả:

1lala 2lala

6

Vấn đề đó rất cụ thể bash, và đó là vì họ đã quyết định bashtách phần mở rộng dấu ngoặc ra khỏi phần mở rộng tên tệp (globalbing) và để thực hiện trước, trước tất cả các bản mở rộng khác.

Từ bashtrang hướng dẫn:

Thứ tự mở rộng là: mở rộng cú đúp; mở rộng dấu ngã, mở rộng tham số và biến, mở rộng số học và thay thế lệnh (được thực hiện theo kiểu từ trái sang phải); tách từ; và mở rộng tên đường dẫn.

Trong ví dụ của bạn, bashsẽ chỉ thấy niềng răng của bạn sau khi nó đã thực hiện thay thế lệnh (the $(echo ...)), khi quá muộn.

Điều này khác với tất cả các shell khác, thực hiện việc mở rộng dấu ngoặc ngay trước đó (và một số thậm chí là một phần của) mở rộng tên đường dẫn (globalbing). Điều đó bao gồm nhưng không giới hạn cshở nơi mở rộng cú đúp được phát minh lần đầu tiên.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

Ví dụ thứ hai là trong cùng csh, zsh, ksh93, mkshhoặc fish.

Ngoài ra, lưu ý rằng mở rộng dấu ngoặc như là một phần của Globing cũng có sẵn thông qua glob(3)chức năng thư viện (ít nhất là trên Linux và tất cả các BSD), và trong các triển khai độc lập khác (ví dụ: trong perl perl -le 'print join " ", <test{1,2}.txt>':).

Tại sao điều đó được thực hiện khác đi bashcó lẽ là một câu chuyện đằng sau nó, nhưng FWIW tôi không thể tìm thấy bất kỳ lời giải thích hợp lý nào, và tôi thấy tất cả các hợp lý hóa hậu-hoc không thuyết phục.


3
Lưu ý rằng perlđược sử dụng để gọi cshđể mở rộng các khối, vì vậy không có gì đáng ngạc nhiên khi nó vẫn nhận ra các toán tử toàn cầu giống nhưcsh
Stéphane Chazelas

1

Vui lòng thử:::

ls $ (kiểm tra tiếng vang {1,2} \. txt)

Với một BackSlash. Nó hoạt dộng bây giờ. Cũng loại bỏ như các poster trước đó nói, các trích dẫn. Dấu chấm không dành cho mẫu phù hợp, nhưng được hiểu theo nghĩa đen là Thời kỳ ở đây.


(1) Câu hỏi đặt ra cho vấn đề gì xảy ra ở đây? Tại sao [không] mẫu hình {1,2}hành xử theo cách của nó? Câu hỏi đặt ra không hỏi “Làm thế nào tôi có thể nhận được một lệnh sử dụng {1,2}để hành xử như các lệnh với ?công trình?” Bạn đang trả lời sai câu hỏi. (2) Dấu gạch chéo ngược không có gì để làm với nó. Lệnh của bạn hoạt động theo cách của nó bởi vì bạn đã loại bỏ các trích dẫn trong lệnh trong câu hỏi.
G-Man nói 'Phục hồi Monica'

0

Nó hoạt động nếu bạn loại bỏ dấu ngoặc kép

$ ls $(echo test{1,2})
test1  test2

9
Việc mở rộng hiện đang diễn ra trước khi thay thế lệnh, điều mà tôi không nghĩ là câu hỏi đang hỏi là gì (so sánh những gì đang xảy ra với ?).
Michael Homer
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.