Tại sao các tùy chọn trong một biến được trích dẫn không thành công, nhưng hoạt động khi không được trích dẫn?


18

Tôi đọc về điều đó tôi nên trích dẫn các biến trong bash, ví dụ "$ foo" thay vì $ foo. Tuy nhiên, trong khi viết một kịch bản, tôi đã tìm thấy một trường hợp nó hoạt động mà không có dấu ngoặc kép nhưng không phải với chúng:

wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line 
relative_path="$3" # /XXX received from command line

Cái này hoạt động:

wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

Điều này không có (lưu ý các trích dẫn kép aroung $ wget_options):

wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
  • Lý do cho điều này là gì?

  • Là dòng đầu tiên là phiên bản tốt; hoặc tôi nên nghi ngờ rằng có một lỗi ẩn ở đâu đó gây ra hành vi này?

  • Nói chung, tôi tìm tài liệu tốt ở đâu để hiểu cách bash và trích dẫn của nó hoạt động? Trong khi viết kịch bản này, tôi cảm thấy rằng tôi đã bắt đầu làm việc trên cơ sở thử và sai thay vì hiểu các quy tắc.


3
Câu hỏi của bạn được trả lời ở đây: mywiki.wooledge.org/BashFAQ/050
glenn Jackman

3
Đi đến nguồn cho các quy tắc: hướng dẫn bash . Hãy chú ý đến phần 3.5 "Mở rộng Shell", đặc biệt là chia tách từ và mở rộng tên tệp - 2 yếu tố này là những gì bạn sử dụng dấu ngoặc kép để kiểm soát.
glenn jackman


4
Tôi nghĩ nó giúp hiểu cách các đối số dòng lệnh hoạt động ở mức thấp. Khi một chương trình được thực thi, nó sẽ nhận được các đối số dưới dạng một danh sách các danh sách các ký tự (đủ gần). Mỗi danh sách bên trong là những gì chúng ta gọi là một "đối số." Hầu hết các chương trình phụ thuộc vào sự phân tách hợp lý giữa các đối số. Ở đây, bạn thấy rằng wgetkhông biết --mirror --no-host-directoriesnghĩa là gì (như một đối số), nhưng nó xử lý nó khi nó được chia thành hai đối số. Rất ít chương trình xử lý không gian và trích dẫn đặc biệt một khi chúng nằm trong vectơ đối số. Vấn đề là ở chỗ bash, và các vỏ khác, có nghĩa là>
HTNW

2
> được con người sử dụng. Sẽ rất khó chịu khi xác định thủ công các ranh giới giữa các đối số, do đó các shell tách ra trên khoảng trắng để biến một dòng (danh sách các ký tự) thành một vectơ đối số (danh sách các danh sách các ký tự). Mở rộng biến là một trong những mở rộng đầu tiên bash, vì vậy bạn có thể tưởng tượng nó $ahoàn toàn tương đương với việc viết trực tiếp nội dung của nó. Bây giờ vấn đề đã rõ ràng: a="-a -b"; cmd "$a"mở rộng ra cmd "-a -b", nhưng cmdcó lẽ không biết điều đó có nghĩa là gì. cmd $amở rộng đến cmd -a -b, mà có lẽ không hoạt động.
HTNW

Câu trả lời:


28

Về cơ bản, bạn nên nhân đôi trích dẫn mở rộng biến để bảo vệ chúng khỏi việc tách từ (và tạo tên tệp). Tuy nhiên, trong ví dụ của bạn,

wget_options='--mirror --no-host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

chia từ là chính xác những gì bạn muốn .

Với "$wget_options"(trích dẫn), wgetkhông biết phải làm gì với đối số duy nhất --mirror --no-host-directoriesvà phàn nàn

wget: unknown option -- mirror --no-host-directories

Để wgetxem hai tùy chọn --mirror--no-host-directoriesnhư tách biệt, việc chia tách từ phải xảy ra.

Có nhiều cách mạnh mẽ hơn để làm điều này. Nếu bạn đang sử dụng bashhoặc bất kỳ shell nào khác sử dụng mảng như bashlàm, hãy xem câu trả lời của glenn jackman . Câu trả lời của Gilles cũng mô tả một giải pháp thay thế cho các vỏ đơn giản hơn như tiêu chuẩn /bin/sh. Cả hai về cơ bản lưu trữ mỗi tùy chọn như là một yếu tố riêng biệt trong một mảng.

Câu hỏi liên quan với câu trả lời hay: Tại sao tập lệnh shell của tôi bị nghẹt trên khoảng trắng hoặc các ký tự đặc biệt khác?


Mở rộng đôi trích dẫn biến là một quy tắc tốt. Làm điều đó . Sau đó, hãy lưu ý đến rất ít trường hợp bạn không nên làm điều đó. Chúng sẽ hiển thị cho bạn thông qua các thông báo chẩn đoán, chẳng hạn như thông báo lỗi ở trên.

Cũng có một vài trường hợp bạn không cần trích dẫn mở rộng biến. Nhưng dù sao thì việc tiếp tục sử dụng dấu ngoặc kép sẽ dễ dàng hơn vì nó không tạo ra nhiều khác biệt. Một trường hợp như vậy là

variable=$other_variable

Một số khác là

case $variable in
    ...) ... ;;
esac

2
Trước khi sử dụng toán tử split + global đó, người ta có thể cần đảm bảo rằng $IFScó chứa giá trị đúng. Ở đây bạn cần phân chia không gian và văn bản xảy ra không chứa bất kỳ tab hoặc dòng mới nào, vì vậy giá trị mặc định $IFSsẽ làm, nhưng nếu mã đó được sử dụng trong một chức năng có thể được gọi trong ngữ cảnh $IFScó thể được sửa đổi , bạn muốn đặt $IFStrước (và có thể khôi phục nó sau đó hoặc sử dụng phạm vi cục bộ cho nó nếu phần còn lại của mã giả định không được sửa đổi $IFS)
Stéphane Chazelas

32

Cách mã hóa mạnh mẽ nhất là sử dụng một mảng:

wget_options=(
    --mirror 
    --no-host-directories
    --directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"

Đây là câu trả lời đúng. Tham khảo
l0b0

2
Đó là một câu trả lời hay, vì vậy tôi đã ủng hộ nó nhưng Kusalanda đã giúp tôi hiểu thêm tại sao mã của tôi sai và tôi chỉ có thể chấp nhận một.
z32a7ul

Tôi đã chạy vào một thế giới rắc rối cho đến khi ai đó trong danh sách rsync chỉ cho tôi cấu trúc này. Nó đặc biệt hữu ích nếu một số phần tử có thể là chuỗi rỗng. Điều này làm cho chuỗi rỗng biến mất. Một số lệnh thích cprsyncsẽ làm những điều không mong muốn nếu lệnh của bạn mở rộng thành thứ gì đó như rsync '' rest of parameters. Điều này là tuyệt vời để xây dựng một lệnh một cách có điều kiện và sau đó chỉ chạy nó một lần ở một nơi.
Joe

17

Bạn đang cố lưu trữ một danh sách các chuỗi trong một biến chuỗi. Nó không phù hợp. Không có vấn đề làm thế nào bạn truy cập vào biến, một cái gì đó bị hỏng.

wget_options='--mirror --no-host-directories'đặt biến wget_optionsthành một chuỗi chứa một khoảng trắng. Tại thời điểm này, không có cách nào để biết liệu không gian được cho là một phần của tùy chọn hay phân cách giữa các tùy chọn.

Khi bạn truy cập vào biến với một thay thế được trích dẫn wget "$wget_options", giá trị của biến được sử dụng như một chuỗi. Điều này có nghĩa là nó được truyền dưới dạng một tham số duy nhất wget, vì vậy đây là một tùy chọn duy nhất. Điều này phá vỡ trong trường hợp của bạn bởi vì bạn dự định nó có nghĩa là nhiều tùy chọn.

Khi bạn sử dụng một sự thay thế không được trích dẫn wget $wget_options, giá trị của biến chuỗi sẽ trải qua quá trình mở rộng có biệt danh là chia tách + thế giới:

  1. Lấy giá trị của biến và chia nó thành các phần được phân tách bằng khoảng trắng (giả sử bạn chưa sửa đổi $IFSbiến). Điều này dẫn đến một danh sách trung gian của chuỗi.
  2. Đối với mỗi thành phần của danh sách trung gian, nếu đó là mẫu ký tự đại diện khớp với một hoặc nhiều tệp, hãy thay thế thành phần đó bằng danh sách các tệp phù hợp.

Điều này xảy ra để làm việc trong ví dụ của bạn, vì quá trình phân tách biến không gian thành dấu phân cách, nhưng nói chung không hoạt động vì một tùy chọn có thể chứa khoảng trắng và ký tự đại diện.

Trong ksh, bash, yash và zsh, bạn có thể sử dụng một biến mảng. Một mảng trong thuật ngữ shell là một danh sách các chuỗi, vì vậy không mất thông tin. Để tạo một biến mảng, đặt dấu ngoặc quanh các phần tử mảng khi gán giá trị cho biến. Để truy cập tất cả các phần tử của mảng, sử dụng - đây là một khái quát của , tạo thành một danh sách từ các phần tử của mảng. Lưu ý rằng bạn cũng cần dấu ngoặc kép ở đây, nếu không, mỗi phần tử trải qua quá trình phân tách + global."${VARIABLE[@]}""$@"

wget_options=(--mirror --no-host-directories --user-agent="I can haz spaces")
wget "${wget_options[@]}" 

Trong đồng bằng sh, không có biến mảng. Nếu bạn không ngại mất các đối số vị trí, bạn có thể sử dụng chúng để lưu trữ một danh sách các chuỗi.

set -- --mirror --no-host-directories --user-agent="I can haz spaces"
wget "$@" 

Để biết thêm thông tin, hãy xem Tại sao tập lệnh shell của tôi bị nghẹt trên khoảng trắng hoặc các ký tự đặc biệt khác?


Đối với sh đơn giản, một lớp con sẽ bảo toàn các đối số vị trí : (set -- ...; exec wget "$@" ...).
John Kugelman hỗ trợ Monica
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.