@Kusalananda đã giải thích vấn đề cơ bản và cách giải quyết, và mục Bash FAQ được liên kết bởi @glenn jackmann cũng cung cấp nhiều thông tin hữu ích. Đây là một lời giải thích chi tiết về những gì xảy ra trong vấn đề của tôi dựa trên những tài nguyên này.
Chúng tôi sẽ sử dụng một tập lệnh nhỏ in từng đối số của nó trên một dòng riêng biệt để minh họa cho mọi thứ ( argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
Tùy chọn vượt qua "thủ công":
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
Như mong đợi, các phần -rnv
và --exclude='.*'
được chia thành hai đối số, vì chúng được phân tách bằng khoảng trắng không trích dẫn (điều này được gọi là tách từ ).
Cũng lưu ý rằng các trích dẫn xung quanh .*
đã bị xóa: các trích dẫn đơn báo cho trình bao vượt qua nội dung của chúng mà không có giải thích đặc biệt , nhưng bản thân các trích dẫn không được truyền cho lệnh .
Nếu bây giờ chúng ta lưu trữ các tùy chọn trong một biến dưới dạng một chuỗi (trái ngược với việc sử dụng một mảng), thì các trích dẫn sẽ không bị xóa :
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
Điều này là do hai lý do: dấu ngoặc kép được sử dụng khi xác định $OPTS
ngăn chặn xử lý đặc biệt đối với dấu ngoặc đơn, vì vậy dấu ngoặc kép là một phần của giá trị:
$ echo $OPTS
--exclude='.*'
Khi chúng ta sử dụng $OPTS
làm đối số cho một lệnh thì các trích dẫn được xử lý trước khi mở rộng tham số , vì vậy các trích dẫn $OPTS
xảy ra "quá muộn".
Điều này có nghĩa là (trong vấn đề ban đầu của tôi) rsync
sử dụng mẫu loại trừ '.*'
(có dấu ngoặc kép!) Thay vì mẫu .*
- nó loại trừ các tệp có tên bắt đầu bằng một trích dẫn theo sau là dấu chấm và kết thúc bằng một trích dẫn. Rõ ràng đó không phải là những gì đã được dự định.
Một cách giải quyết khác là đã bỏ qua dấu ngoặc kép khi xác định $OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
Tuy nhiên, đó là một cách thực hành tốt để luôn trích dẫn các bài tập thay đổi vì sự khác biệt tinh tế trong các trường hợp phức tạp hơn.
Như @Kusalananda lưu ý, không trích dẫn .*
cũng sẽ có tác dụng. Tôi đã thêm các trích dẫn để ngăn chặn việc mở rộng mẫu , nhưng điều đó không thực sự cần thiết trong trường hợp đặc biệt này :
$ ./argtest.bash --exclude=.*
--exclude=.*
Nó chỉ ra rằng Bash không thực hiện việc mở rộng mô hình, nhưng các mô hình --exclude=.*
không phù hợp với bất kỳ tập tin, vì vậy mô hình được truyền lại cho các lệnh. Đối chiếu:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
Tuy nhiên, không trích dẫn mẫu là nguy hiểm, bởi vì nếu (vì bất kỳ lý do gì) có một tệp phù hợp --exclude=.*
thì mẫu sẽ được mở rộng:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
Cuối cùng, hãy xem tại sao sử dụng một mảng ngăn chặn vấn đề trích dẫn của tôi (ngoài các ưu điểm khác của việc sử dụng mảng để lưu trữ các đối số lệnh).
Khi xác định mảng, phân tách từ và xử lý trích dẫn xảy ra như mong đợi:
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
Khi đi qua các tùy chọn để chỉ huy, chúng ta sử dụng cú pháp "${ARRAY[@]}"
này sẽ mở rộng mỗi phần tử của mảng vào một từ riêng biệt:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*