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=.
nullglob
failglob
답변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
$2
42
$3
World
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"