명령 옵션에 쉘 변수 사용


19

Bash 스크립트에서 사용중인 옵션을 rsync별도의 변수 에 저장하려고 합니다. 간단한 옵션 (예 :)에는 잘 작동 --recursive하지만 다음 과 같은 문제가 발생합니다 --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)

당신이 볼 수 있듯이, 통과 --exclude='.*'rsync"수동으로"잘 (작동 .bar복사되지 않음) 옵션이 첫번째 변수에 저장 될 때,이 작업을 수행하지.

나는 이것이 따옴표 또는 와일드 카드 (또는 둘 다)와 관련이 있다고 생각하지만 정확하게 무엇이 잘못되었는지 알 수 없었습니다.



1
또는 자체 사이트 에서이 답변 을 참조하십시오 .
Scott

답변:


38

일반적으로 명령 줄 옵션 목록이든 경로 이름 목록이든 관계없이 개별 항목 목록을 단일 문자열로 강등시키는 것은 좋지 않습니다.

대신 배열 사용

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

또는

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

나중에 ...

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

이런 식으로 개별 옵션의 인용이 유지됩니다 (의 확장을 큰 따옴표로 묶기 만하면 ${rsync_options[@]}). 또한 호출하기 전에 배열의 개별 항목을 쉽게 조작 할 수 있습니다 rsync.

POSIX 셸에서 다음에 대한 위치 매개 변수 목록을 사용할 수 있습니다.

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

rsync "$@" source/ target

다시 말하지만 확장의 큰 따옴표는 $@여기서 중요합니다.

접선 적으로 관련됨 :


문제는 두 옵션 세트를 문자열에 넣을 때 --exclude옵션 값 의 작은 따옴표 가 해당 값의 일부가된다는 것입니다. 그 후,

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

¹ 일했을 것입니다 ...하지만 개별적으로 인용 된 항목과 함께 배열 또는 위치 매개 변수를 사용하는 것이 더 안전합니다. 그렇게하면 필요한 경우 공백이있는 것을 사용할 수 있으며 쉘이 옵션에서 파일 이름 생성 (글 로빙)을 수행하지 않아도됩니다.


¹ $IFS수정되지 않았 --exclude=.으며 현재 디렉토리에서 이름이 시작되는 파일이없고 nullglob또는 failglob셸 옵션이 설정되어 있지 않은 경우.


배열을 사용하면 잘 작동합니다. 자세한 답변에 감사드립니다!
Florian Brucker

3

@Kusalananda 는 이미 기본 문제와 그 해결 방법에 대해 설명 했으며 @glenn jackmann 링크 한 Bash FAQ 항목 에도 유용한 정보가 많이 있습니다. 다음은 이러한 리소스를 기반으로 내 문제가 어떻게 발생하는지에 대한 자세한 설명입니다.

작은 인수를 사용하여 각 인수를 별도의 줄에 인쇄하여 사물을 설명합니다 ( argtest.bash).

#!/bin/bash

for var in "$@"
do
    echo "$var"
done

"수동으로"옵션 전달 :

$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*

예상대로, 부분 -rnv--exclude='.*'따옴표가없는 공백으로 구분되므로 두 개의 인수로 나뉩니다 (이를 단어 분리 라고 함 ).

또한 따옴표 주위에주의 .*제거 된 : 작은 따옴표는 콘텐츠를 전달하는 쉘에게 특별한 해석없이 , 하지만 따옴표는 자신이 명령에 전달되지 않습니다 .

옵션을 변수에 문자열로 저장하면 (배열 사용과 반대) 따옴표 는 제거되지 않습니다 .

$ OPTS="--exclude='.*'"

$ ./argtest.bash $OPTS
--exclude='.*'

이것은 두 가지 이유 때문입니다. 정의 할 때 사용되는 큰 따옴표는 작은 따옴표의 $OPTS특수 처리를 방지하므로 후자는 값의 일부입니다.

$ echo $OPTS
--exclude='.*'

이제 $OPTS명령의 인수로 사용하면 매개 변수 확장 전에 따옴표가 처리 되므로 따옴표가 $OPTS너무 늦게 발생합니다.

이것은 (원래의 문제에서) rsync패턴 '.*'대신 제외 패턴 (따옴표 포함! )을 사용 한다는 것을 의미 합니다. .*이름이 작은 따옴표로 시작하고 점으로 끝나고 작은 따옴표로 끝나는 파일은 제외합니다. 분명히 그것은 의도 된 것이 아닙니다.

해결 방법은 다음을 정의 할 때 큰 따옴표를 생략하는 것입니다 $OPTS.

$ OPTS2=--exclude='.*'

$ ./argtest.bash $OPTS2
--exclude=.*

그러나 보다 복잡한 경우에는 미묘한 차이로 인해 변수 할당항상 인용 하는 것이 좋습니다 .

@ Kusalananda가 지적했듯이 인용하지 않는 .*것도 효과가 있었을 것입니다. 패턴 확장 을 막기 위해 따옴표를 추가 했지만 이 특별한 경우에는 꼭 필요하지 않았습니다 .

$ ./argtest.bash --exclude=.*
--exclude=.*

Bash 패턴 확장을 수행하지만 패턴 --exclude=.*이 파일과 일치하지 않으므로 패턴이 명령으로 전달됩니다. 비교:

$ touch some_file

$ ./argtest.bash some_*
some_file

$ ./argtest.bash does_not_exit_*
does_not_exit_*

그러나 어떤 이유로 든 파일이 일치 --exclude=.*하면 패턴이 확장 되기 때문에 패턴을 인용하지 않는 것은 위험합니다 .

$ touch -- --exclude=.special-filenames-happen

$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen

마지막으로, 배열을 사용하면 내 인용 문제가 발생하지 않는 이유를 살펴 보겠습니다 (배열을 사용하여 명령 인수를 저장하는 다른 이점 외에도).

배열을 정의 할 때 단어 분할 및 따옴표 처리가 예상대로 수행됩니다.

$ 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=.*

옵션을 명령에 전달할 때 우리는 구문을 사용 "${ARRAY[@]}"하여 배열의 각 요소를 별도의 단어로 확장합니다.

$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*

이 물건은 오랫동안 혼란 스러웠으므로 이와 같은 자세한 설명이 도움이됩니다.
Joe

0

인수가 처리되도록 전달되는 함수 및 쉘 스크립트를 작성할 때 인수는 숫자로 명명 된 변수 (예 : $ 1, $ 2, $ 3)로 전달됩니다.

예를 들면 다음과 같습니다.

bash my_script.sh Hello 42 World

내부는 my_script.sh, 명령을 사용합니다 $1, 안녕하세요을 참조 $242, 그리고 $3에 대한World

변수 참조 $0는 현재 스크립트 이름으로 확장됩니다. 예 :my_script.sh

명령을 변수로 사용하여 전체 코드를 재생하지 마십시오.

명심하십시오 :

1 스크립트에서 모든 대문자 변수 이름을 사용하지 마십시오.

2 역 따옴표를 사용하지 말고 대신 $ (...)를 사용하십시오.

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"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.