및 와 ${parameter%word}
함께 확장 기능을 사용하는 방법을 알아내려고 합니다 . Ghostscript를 사용하여 pdf를 결합하는 스크립트를 만들려고 시도했지만 매개 변수 확장과 함께 이상한 동작이 발생했고 이제는 왜 이런 동작이 발생하는지 궁금합니다.$@
$*
기본적으로 각 매개변수의 끝에서 ".pdf"를 제거한 다음 임의의 문자열로 연결하고(테스트에는 "-"를 사용함) 끝에 ".pdf"를 추가하려고 합니다. 결과. 예를 들어 예상되는 동작은 test.sh a.pdf b.pdf c.pdf
-> 입니다 a-b-c.pdf
. 내가 실행 중인 테스트 스크립트는 다음과 같습니다.
IFS='-'
echo ${*%.pdf}.pdf
echo "${*%.pdf}.pdf"
a=${*%.pdf}.pdf
b="${*%.pdf}.pdf"
echo $a
echo $b
내가 하면 bash test.sh a.pdf b.pdf c.pdf
다음을 얻습니다.
a b c.pdf
a-b-c.pdf
a b c.pdf
a b c.pdf
내가 하면 zsh test.sh a.pdf b.pdf c.pdf
다음을 얻습니다.
a b c.pdf
a.pdf-b.pdf-c.pdf
a-b-c.pdf
a.pdf-b.pdf-c.pdf
나는 zsh와 bash가 다르다는 것을 알고 있으므로 왜 다른 결과가 나오는지 걱정하지 않습니다. 그러나 각 경우에 문자열을 작성하는 4가지 방법 중 하나만 예상대로 작동합니다(두 번째는 bash, 세 번째는 zsh).
문자열을 구성하려는 유사해 보이는 시도가 왜 그렇게 다른 결과를 낳는 걸까요? 어떤 통찰력이라도 감사하겠습니다. 감사해요!
답변1
작동 방식 ${*%word}
등은 쉘에 따라 다릅니다.POSIX지정된 결과가 없습니다. 두 가지 주요 가능한 동작이 있습니다. 변환(접두사 또는 접미사 제거)은 각 단어에 적용되거나 단어 연결 결과에 적용될 수 있습니다. 배열을 지원하는 셸에서는 각 단어에 변환을 적용하는 것이 당연합니다. 이것이 bash와 ksh93이 수행하는 작업입니다. 배열을 지원하지 않는 쉘에서는 단어를 먼저 연결하는 것이 당연합니다(ash/dash가 하는 일입니다). 예를 들어:
# No arrays: $* joined = 'abc abc'; strip off b* → 'a'
$ dash -c 'echo ${*%%b*}' _ abc abc
a
# Arrays: $* = ('abc' 'abc'); strip off b* from each element → ('a' 'a'); then join
$ bash -c 'echo ${*%%b*}' _ abc abc
a a
첫 번째 문자 IFS
는 형성된 단어를 연결하는 데 사용됩니다 $*
. 패턴이 해당 문자와 일치하는 경우에만 삭제된 콘텐츠에 영향을 미칩니다. 예를 들어:
# No arrays: $* joined = 'abc-def-ghi'; strip off -* → 'abc'
$ dash -c 'IFS=-; echo "${*%%-*}"' _ abc-def ghi
abc
# Arrays: $* = ('abc-def' 'ghi'); strip off -* from each element → ('abc' 'ghi'); then join
$ bash -c 'IFS=-; echo "${*%%-*}"' _ abc-def ghi
abc-ghi
대체가 단어 문맥에 있을 때 이 끝까지 확장됩니다. 단어 컨텍스트에는 큰따옴표와 할당이 포함됩니다.언제 큰따옴표가 필요합니까?그리고쉘 변수의 확장과 glob 및 분할의 영향자세한 내용은. 이는 설명합니다 echo "${*%.pdf}.pdf"
: 의 첫 번째 문자는 IFS
연결에 사용되며 후속 분할이 없으므로 bash의 출력은 입니다 a-b-c.pdf
. 두 값 모두 a
마찬가지 b
입니다 a-b-c.pdf
.
첫 번째 예에서와 같이 대체 항목이 목록 컨텍스트에 있는 경우(즉, 인용되지 않은 경우) 결과는 토큰화됩니다(글로빙). 이는 , 를 기준으로 IFS
하므로 , , a-b-c.pdf
로 구분 됩니다 . 이 명령은 사이에 공백이 있는 세 단어를 인쇄합니다. 귀하의 예에서는 and에서도 똑같은 일이 발생합니다. or의 값은 문자로 분할됩니다.a
b
c.pdf
echo
echo $a
echo $b
a
b
IFS
Zsh는 취급 @
하고 *
다릅니다. 매개변수 이름 으로 *
큰따옴표 안에 문자열 스타일 동작(연결 후 변환)을 적용하고, 그렇지 않으면 배열 스타일 동작(각 요소 변환)을 적용합니다. 반면 매개변수는 @
항상 배열로 처리됩니다. 그러므로:
$ zsh -c 'echo "${*%.pdf}"' _ a.pdf b.pdf c.pdf
a.pdf b.pdf c
$ zsh -c 'echo ${*%.pdf}' _ a.pdf b.pdf c.pdf
a b c
$ zsh -c 'echo "${@%.pdf}"' _ a.pdf b.pdf c.pdf
a b c
$ zsh -c 'echo ${@%.pdf}' _ a.pdf b.pdf c.pdf
a b c
다른 쉘에서 발생하는 것과 달리 문자열 할당으로 인해 문자열이 처리되지 않습니다 $*
. 큰따옴표가 중요합니다. 이는 a=${*%.pdf}; echo $a
like가 처럼 작동 echo ${*%.pdf}
하지만 처럼 작동하지 않는 이유를 설명합니다 a="${*%.pdf}"; echo $a
.
의 경우 큰따옴표나 문자열 할당으로 인해 IFS=-
연결 시 대시가 사용되며 *
이는 단어 컨텍스트에 있는 한 발생합니다.
# ('a.pdf' 'b.pdf' 'c.pdf); strip each element → ('a' 'b' 'c'); print list
$ zsh -c 'IFS=-; echo ${*%.pdf}' _ a.pdf b.pdf c.pdf
a b c
# join → 'a.pdf-b.pdf-c.pdf'; strip the single word and print it
$ zsh -c 'IFS=-; echo "${*%.pdf}"' _ a.pdf b.pdf c.pdf
a.pdf-b.pdf-c
# ('a.pdf' 'b.pdf' 'c.pdf); strip each element → ('a' 'b' 'c'); `$*` in word context so join → 'a-b-c'; print word
$ zsh -c 'IFS=-; a=${*%.pdf}; echo "$a"' _ a.pdf b.pdf c.pdf
a-b-c
# join → 'a.pdf-b.pdf-c.pdf'; strip the single word; print the word
$ zsh -c 'IFS=-; a="${*%.pdf}"; echo "$a"' _ a.pdf b.pdf c.pdf
a.pdf-b.pdf-c
를 거의 사용해서는 안 됩니다 $*
. 위치 인수를 연결하는 데만 유용하며 연결에 의해 생성된 문자와 인수에 이미 있는 문자 를 IFS
구별하는 것이 불가능합니다 . 거의 항상 올바른 형식입니다. 단어 확장을 방지하려면 큰따옴표가 필요합니다(zsh에서도 생략하면 zsh에 미치는 영향이 훨씬 적습니다).IFS
IFS
"$@"
스크립트를 이해하기 쉽게 만들려면 한 번에 한 단계씩 수행하십시오. 즉, 각 부분에서 접미사를 제거한 다음 부분을 연결하십시오. 배열 변수를 사용하여 중간 결과를 저장합니다.
parts=("${@%.pdf}") # using @ because we want to have array behavior
IFS=-
joined="${parts[*]}" # using * and not @ for joining
echo "$joined.pdf"
이 코드 조각은 bash와 zsh에서 동일하게 작동합니다.
답변2
이것은 무슨 일이 일어나는지 설명합니다:
#!/bin/bash
IFS='-'
var='a-b-c.pdf'
echo $var
echo "$var"
echo ${*%.pdf}.pdf
원하는 문자열을 생성했지만 따옴표 누락으로 인해 단어 분할 이 -
.
아니면 이거:
[[ ${*%.pdf}.pdf =~ ' ' ]]
+ [[ a-b-c.pdf =~ ]]
echo $?
+ echo 1
1
여기에는 [[ ]]
분사가 없으며 set -vx
/는 bash -vx
확장에 공백이 포함되지 않음을 나타냅니다.