Sử dụng biến shell cho các tùy chọn lệnh


18

Trong tập lệnh Bash, tôi đang cố lưu trữ các tùy chọn tôi đang sử dụng cho rsyncmột biến riêng biệt. Điều này hoạt động tốt cho các tùy chọn đơn giản (như --recursive), nhưng tôi đang gặp vấn đề với --exclude='.*':

$ find source
source
source/.bar
source/foo

$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo

sent 57 bytes  received 19 bytes  152.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)

$ RSYNC_OPTIONS="-rnv --exclude='.*'"

$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo

sent 78 bytes  received 22 bytes  200.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)

Như bạn có thể thấy, chuyển --exclude='.*'đến rsync"thủ công" hoạt động tốt ( .barkhông được sao chép), nó không hoạt động khi các tùy chọn được lưu trữ trong một biến trước tiên.

Tôi đoán rằng điều này có liên quan đến dấu ngoặc kép hoặc ký tự đại diện (hoặc cả hai), nhưng tôi không thể biết chính xác điều gì là sai.



1
Hoặc xem câu trả lời này trên trang web của chúng tôi.
Scott

Câu trả lời:


37

Nói chung, sẽ là một ý tưởng tồi khi hạ cấp một danh sách các mục riêng biệt thành một chuỗi, bất kể đó là danh sách các tùy chọn dòng lệnh hay danh sách tên đường dẫn.

Sử dụng một mảng thay thế:

rsync_options=( -rnv --exclude='.*' )

hoặc là

rsync_options=( -r -n -v --exclude='.*' )

và sau đó...

rsync "${rsync_options[@]}" source/ target

Bằng cách này, trích dẫn của các tùy chọn riêng lẻ được duy trì (miễn là bạn trích dẫn hai lần mở rộng ${rsync_options[@]}). Nó cũng cho phép bạn dễ dàng thao tác các mục riêng lẻ của mảng, bạn có cần phải làm như vậy trước khi gọi rsync.

Trong bất kỳ shell POSIX nào, người ta có thể sử dụng danh sách các tham số vị trí cho điều này:

set -- -rnv --exclude='.*'

rsync "$@" source/ target

Một lần nữa, trích dẫn gấp đôi việc mở rộng $@là rất quan trọng ở đây.

Liên quan liên tục:


Vấn đề là khi bạn đặt hai bộ tùy chọn thành một chuỗi, các trích dẫn đơn --excludecủa giá trị của tùy chọn sẽ trở thành một phần của giá trị đó. Vì thế,

RSYNC_OPTIONS='-rnv --exclude=.*'

sẽ có hiệu quả¹ ... nhưng sẽ tốt hơn (như an toàn hơn) khi sử dụng một mảng hoặc các tham số vị trí với các mục được trích dẫn riêng lẻ. Làm như vậy cũng sẽ cho phép bạn sử dụng những thứ có khoảng trắng trong đó, nếu bạn cần và tránh việc shell thực hiện việc tạo tên tệp (globalbing) trên các tùy chọn.


Miễn $IFSlà không được sửa đổi và không có tệp nào có tên bắt đầu --exclude=.trong thư mục hiện tại và các tùy chọn nullglobhoặc failglobshell không được đặt.


Sử dụng một mảng hoạt động tốt, cảm ơn bạn đã trả lời chi tiết của bạn!
Florian Brucker

3

@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--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 $OPTSngă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 $OPTSlà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 $OPTSxảy ra "quá muộn".

Điều này có nghĩa là (trong vấn đề ban đầu của tôi) rsyncsử 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=.*

Công cụ này đã làm tôi bối rối trong một thời gian dài, vì vậy một lời giải thích chi tiết như thế này là hữu ích.
Joe

0

Khi chúng ta viết các hàm và các kịch bản shell, trong đó các đối số được truyền vào để được xử lý, các đối số sẽ được truyền các biến có tên int, ví dụ $ 1, $ 2, $ 3

Ví dụ :

bash my_script.sh Hello 42 World

Bên trong my_script.sh, các lệnh sẽ sử dụng $1để tham khảo Hello, $2to 42$3forWorld

Tham chiếu biến $0, sẽ mở rộng thành tên của tập lệnh hiện tại, vdmy_script.sh

Đừng chơi toàn bộ mã với các lệnh dưới dạng các biến.

Hãy ghi nhớ :

1 Tránh sử dụng tên biến tất cả chữ hoa trong tập lệnh.

2 Đừng sử dụng backquote, thay vào đó hãy sử dụng $ (...), nó sẽ tốt hơn.

if [ $# -ne 2 ]
then
    echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file"
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.