쉘 변수를 명령 옵션으로 사용

쉘 변수를 명령 옵션으로 사용

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

일반적으로 명령줄 옵션 목록이든 경로 이름 목록이든 개별 항목 목록을 단일 문자열로 줄이는 것은 좋지 않습니다. 그 이유는 줄 아래 어딘가에서 문자열을 별도의 내용으로 다시 분할해야 하며 문자열에 의미론적 해석이 쉬울 수도 있고 중요하지 않을 수도 있는 따옴표와 공백이 포함되어 있으면 올바르게 수행할 수 없기 때문입니다. 문자열의 데이터입니다.

대신 배열을 사용하십시오.

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

효과가 있었겠지만... 별도의 참조 항목이 있는 배열이나 위치 매개변수를 사용하는 것이 더 안전할 것입니다. 이렇게 하면 필요할 때 공백이 포함된 콘텐츠를 사용할 수 있으며, 쉘이 옵션에 대해 파일 이름 생성(globbing)을 수행하는 것을 방지할 수 있습니다.


¹ 이름이 다음으로 시작하는 파일이 $IFS수정되지 않았고 현재 디렉터리에 존재하지 않으며 또는 쉘 옵션이 설정되어 있지 않은 경우.--exclude=.nullglobfailglob

답변2

@쿠살라난다이미 설명함기본적인 문제와 그에 대한 해결책,배쉬 FAQ 항목@glenn jackmann 링크도 유용한 정보를 많이 제공합니다. 다음은 이러한 리소스를 기반으로 내 문제에 어떤 일이 일어나고 있는지에 대한 자세한 설명입니다.

각 인수를 별도의 줄( 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=.*

답변3

함수와 쉘 스크립트를 작성할 때 처리를 위해 매개변수를 전달할 때 매개변수는 $1, $2, $3와 같이 int 숫자로 명명된 변수로 전달됩니다.

예를 들어:

bash my_script.sh Hello 42 World

내부적으로 이 명령은 Hello, to 및 for 를 참조하는 my_script.sh데 사용됩니다.$1$242$3World

Variable reference 는 $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"

관련 정보