Trích dẫn trong $ (thay thế lệnh) trong Bash


126

Trong môi trường Bash của tôi, tôi sử dụng các biến chứa khoảng trắng và tôi sử dụng các biến này trong lệnh thay thế. Thật không may, tôi không thể tìm thấy câu trả lời trên SE.

Cách chính xác để trích dẫn các biến của tôi là gì? Và tôi nên làm thế nào nếu chúng được lồng?

DIRNAME=$(dirname "$FILE")

hoặc tôi trích dẫn bên ngoài thay thế?

DIRNAME="$(dirname $FILE)"

hoặc cả hai?

DIRNAME="$(dirname "$FILE")"

hoặc tôi sử dụng back-tick?

DIRNAME=`dirname "$FILE"`

Cách đúng đắn để làm điều này là gì? Và làm thế nào tôi có thể dễ dàng kiểm tra nếu các trích dẫn được đặt đúng?




1
Đây là một câu hỏi hay, nhưng đưa ra tất cả các vấn đề với các khoảng trống được nhúng, tại sao bạn lại tự làm khó cuộc sống bằng cách sử dụng chúng vào mục đích?
Joe

3
@Joe, với các khoảng trống được nhúng, bạn có nghĩa là không gian trong tên tệp? Cá nhân tôi không sử dụng chúng thường xuyên, nhưng tôi đang làm việc với các thư mục và tập tin người khác mà tôi không chắc chắn nếu chúng có chứa khoảng trắng. Hơn nữa, tôi nghĩ tốt hơn là làm cho đúng ngay lập tức để tôi không phải lo lắng trong tương lai.
CousinCocaine

4
Đúng. Chúng ta sẽ làm gì với những "người khác"? <G>
Joe

Câu trả lời:


188

Theo thứ tự từ xấu nhất đến tốt nhất:

  • DIRNAME="$(dirname $FILE)" sẽ không làm những gì bạn muốn nếu $FILEchứa các ký tự khoảng trắng hoặc toàn cầu \[?*.
  • DIRNAME=`dirname "$FILE"`là chính xác về mặt kỹ thuật, nhưng backticks không được khuyến khích để mở rộng lệnh vì sự phức tạp thêm khi lồng chúng.
  • DIRNAME=$(dirname "$FILE")là chính xác, nhưng chỉ bởi vì đây là một bài tập . Nếu bạn sử dụng thay thế lệnh trong bất kỳ bối cảnh khác, chẳng hạn như export DIRNAME=$(dirname "$FILE")hay du $(dirname "$FILE"), việc thiếu dấu ngoặc kép sẽ gây ra rắc rối nếu kết quả của việc mở rộng chứa các ký tự khoảng trắng hoặc globbing.
  • DIRNAME="$(dirname "$FILE")"là cách được đề nghị. Bạn có thể thay thế DIRNAME=bằng một lệnh và một khoảng trắng mà không thay đổi bất cứ điều gì khác và dirnamenhận được chuỗi chính xác.

Để cải thiện hơn nữa:

  • DIRNAME="$(dirname -- "$FILE")"hoạt động nếu $FILEbắt đầu với một dấu gạch ngang.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"hoạt động ngay cả khi $FILEkết thúc bằng một dòng mới, vì loại $()bỏ các dòng mới ở cuối đầu ra và dirnamexuất ra một dòng mới sau kết quả. Sheesh dirname, tại sao bạn phải khác biệt?

Bạn có thể lồng mở rộng lệnh bao nhiêu tùy thích. Với $()bạn luôn tạo một bối cảnh trích dẫn mới, vì vậy bạn có thể làm những việc như thế này:

foo "$(bar "$(baz "$(ban "bla")")")"

Bạn không muốn thử điều đó với backticks.


3
Câu trả lời rõ ràng cho câu hỏi của tôi. Khi tôi lồng các biến này, tôi có thể tiếp tục trích dẫn như bây giờ không?
CousinCocaine

3
Có một tài liệu tham khảo / tài nguyên chi tiết hành vi của dấu ngoặc kép trong một trạm biến áp trong dấu ngoặc kép không?
AmadeusDrZaius

12
@AADEusDrZaius "Với $()bạn luôn tạo ra một bối cảnh trích dẫn mới", vì vậy nó giống như bên ngoài các trích dẫn bên ngoài. Theo tôi biết thì không còn gì nữa.
l0b0

4
@ l0b0 Cảm ơn, vâng, tôi thấy lời giải thích của bạn rất rõ ràng. Tôi chỉ tự hỏi liệu nó có trong một hướng dẫn ở đâu đó không. Tôi đã tìm thấy nó (mặc dù không chính thức) tại lenedge . Tôi đoán nếu bạn đọc về thứ tự thay thế một cách cẩn thận , kết quả là bạn có thể rút ra thực tế này.
AmadeusDrZaius

1
Vì vậy, trích dẫn lồng nhau có thể được chấp nhận, nhưng chúng loại bỏ chúng tôi bởi vì hầu hết các lược đồ tô màu cú pháp không phát hiện ra tình huống đặc biệt. Khéo léo.
Luke Davis

19

Bạn luôn có thể hiển thị các hiệu ứng của trích dẫn biến với printf.

Word chia nhỏ được thực hiện trên var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 trích dẫn, vì vậy không có từ chia tách:

$ printf '[%s]\n' "$var1"
[hello     world]

Word chia nhỏ var1bên trong $(), tương đương với echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Không có từ tách var1, không có vấn đề với việc không trích dẫn $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Word chia tách var1một lần nữa:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Trích dẫn cả hai, cách dễ nhất để chắc chắn.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Vấn đề toàn cầu

Không trích dẫn một biến cũng có thể dẫn đến việc mở rộng toàn cầu nội dung của nó:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Lưu ý điều này xảy ra sau khi biến chỉ được mở rộng. Không cần thiết phải trích dẫn một quả địa cầu trong khi chuyển nhượng:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Sử dụng set -fđể vô hiệu hóa hành vi này:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

set +fđể kích hoạt lại nó:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]

8
Mọi người có xu hướng quên rằng việc chia tách từ không phải là vấn đề duy nhất, bạn có thể muốn thay đổi ví dụ của mình var1='hello * world'để minh họa cho vấn đề toàn cầu hóa.
Stéphane Chazelas

10

Ngoài ra câu trả lời được chấp nhận:

Mặc dù tôi thường đồng ý với câu trả lời của @ l0b0 ở đây, tôi nghi ngờ việc đặt các backticks trần trong danh sách "tệ nhất đến tốt nhất" ít nhất là một phần kết quả của giả định $(...)có sẵn ở mọi nơi. Tôi nhận ra rằng câu hỏi chỉ định Bash, nhưng có rất nhiều lần Bash trở nên có ý nghĩa /bin/sh, có thể không thực sự luôn là vỏ Bourne Again đầy đủ.

Cụ thể, trình bao Bourne đơn giản sẽ không biết phải làm gì $(...), do đó, các tập lệnh tuyên bố là tương thích với nó (ví dụ: thông qua một #!/bin/shdòng shebang) sẽ có khả năng hoạt động sai nếu chúng thực sự được chạy bởi "thực" /bin/sh- đây là đặc biệt quan tâm khi, giả sử, sản xuất các tập lệnh init, hoặc đóng gói các tập lệnh trước và sau tập lệnh, và có thể đặt một tập tin ở một nơi đáng ngạc nhiên trong quá trình cài đặt một hệ thống cơ sở.

Nếu bất kỳ điều nào trong số đó nghe giống như điều gì đó mà bạn dự định thực hiện với biến này, thì việc lồng có lẽ ít đáng quan tâm hơn là thực sự có kịch bản, chạy theo dự đoán. Khi đó là một trường hợp đủ đơn giản và tính di động là một mối quan tâm, ngay cả khi tôi mong đợi tập lệnh thường chạy trên các hệ thống có /bin/shBash, tôi thường có xu hướng sử dụng backticks vì lý do này, với nhiều bài tập thay vì lồng nhau.

Đã nói tất cả, tính phổ biến của các shell thực hiện $(...)(Bash, Dash, et al.), Để chúng ta ở một vị trí tốt để gắn bó với cú pháp POSIX đẹp hơn, dễ làm tổ hơn và gần đây được ưa thích hơn trong hầu hết các trường hợp, cho tất cả các lý do @ l0b0 đề cập.

Ngoài ra: điều này thỉnh thoảng cũng xuất hiện trên StackOverflow -


//, Câu trả lời tuyệt vời. Tôi cũng đã gặp phải các vấn đề tương thích ngược với / bin / sh trong quá khứ. Bạn có lời khuyên nào về cách giải quyết vấn đề của anh ấy bằng các phương pháp tương thích ngược không?
Nathan Basan

theo kinh nghiệm của tôi: đôi khi bạn có thể cần phải đổi backticks thành đô la paren, và chưa một lần nó được giúp đỡ (cần thiết, cụ thể) để thay đổi paren đô la được bao bọc thành backticks. Tôi chỉ viết backticks trong mã javascript của mình chứ không viết mã shell.
Steven Lu
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.