Bash 스크립트에서 인수를 반복하는 방법

셸/배시 스크립트를 만들고 싶은 복잡한 명령이 있습니다. '$1'로 쉽게 작성할 수 있습니다:

foo $1 args -o $1.ext

스크립트에 여러 입력 이름을 전달할 수 있기를 원합니다. 올바른 방법은 무엇인가요?

물론 공백이 있는 파일 이름도 처리하고 싶습니다.

질문에 대한 의견 (1)
해결책

모든 인수를 나타내려면 "$@"를 사용합니다:

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

이렇게 하면 각 인수를 반복하여 별도의 줄에 출력합니다. 인용할 때 공백이 있는 경우 인수가 제대로 분리된다는 점을 제외하면 $@는 $*처럼 동작합니다:

sh test.sh 1 2 '3 4'
1
2
3 4
해설 (7)

지금은 삭제된 답변VonC가 다시 작성했습니다; 로버트 갬블의 간결한 답변은 질문에 직접적으로 대응하고 있습니다. 이 답변은 공백이 포함된 파일 이름과 관련된 몇 가지 문제에 대해 자세히 설명합니다. 참조하세요: ${1:+"$@"} in /bin/sh 기본 정설: "$@"는 맞고, $*(따옴표로 묶지 않음)는 거의 항상 틀립니다. 이는 인자에 공백이 포함될 때 "$@"가 정상적으로 작동하고 는 공백이 없을 때 $*와 동일하게 작동하기 때문입니다. 어떤 상황에서는 &$*&$$도 괜찮지만 &$@&$$는 보통 (항상 그런 것은 아니지만 항상) 같은 위치에서 작동합니다. 따옴표로 묶지 않은 $@$*는 동일합니다(그리고 거의 항상 틀립니다). 그렇다면 $*, $@, &$*&$, &$@&$의 차이점은 무엇일까요? 이들은 모두 셸에 대한 모든 인수와 관련이 있지만 서로 다른 일을 합니다. 따옴표로 묶지 않았을 때 $*$@는 같은 일을 합니다. 이들은 각 '단어'(공백이 아닌 시퀀스)를 별도의 인수로 취급합니다. 그러나 따옴표로 묶은 형식은 상당히 다릅니다. &$*"는 인수 목록을 공백으로 구분된 단일 문자열로 취급하는 반면 &$@"는 인수를 명령줄에 지정했을 때와 거의 동일하게 취급합니다. 위치 인수가 없는 경우 "$@"는 전혀 확장되지 않고 "$*"는 빈 문자열 — 로 확장되며, 인식하기 어려울 수 있지만 차이가 있습니다. 자세한 내용은 아래에서 (비표준) 명령 al을 소개한 후 참조하세요. 보조 논문: 공백이 있는 인수를 처리한 다음 다른 명령에 공백으로 인수를 처리해야 하는 경우, 때때로 비표준 도구가 필요할 때가 있습니다. (또는 배열을 신중하게 사용해야 합니다: '"${array[@]}"는 '"$@"와 유사하게 작동합니다.) 예시:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

왜 작동하지 않나요? 셸이 확장하기 전에 따옴표를 처리하기 때문에 작동하지 않습니다. 변수를 처리하기 때문입니다. 따라서 쉘이 $var에 포함된 따옴표에 주의를 기울이도록 하려면, eval`을 사용해야 합니다:

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

다음과 같은 파일 이름이 있을 때는 정말 까다로워집니다, '와 같이 (따옴표와 큰따옴표, 공백으로 묶인) 파일 이름이 있을 때는 까다롭습니다.

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

셸은 (모든 셸이) 그런 것들을 처리하기가 특별히 쉽지 않습니다. 처리하기가 쉽지 않기 때문에 (재미있게도) 많은 유닉스 프로그램에서 이러한 셸을 잘 처리하지 못합니다. 유닉스에서 파일 이름(단일 구성 요소)에는 다음을 제외한 모든 문자를 포함할 수 있습니다. 슬래시와 NUL '\0'을 제외한 모든 문자를 포함할 수 있습니다. 그러나 셸에서는 경로 이름에 공백이나 줄바꿈, 탭을 을 사용하지 않는 것이 좋습니다. 표준 Unix 파일 이름에 공백 등이 포함되지 않는 이유이기도 합니다. 공백 및 기타 문제가 될 수 있는 문자를 포함할 수 있는 파일 이름을 다룰 때는 파일 이름을 다룰 때는 매우 조심해야 하며, 저는 오래 전에 오래 전에 유닉스에서 표준이 아닌 프로그램이 필요하다는 것을 알게 되었습니다. 저는 이 프로그램을 escape라고 부릅니다(버전 1.1은 1989-08-23T16:01:45Z 날짜). 다음은 SCCS 제어 시스템에서 사용 중인 escape의 예입니다. 이것은 델타(체크인이라고 생각하세요)와 (체크인이라고 생각하세요)을 모두 수행하는 커버 스크립트입니다. get(*체크아웃*이라고 생각하세요)을 모두 수행하는 커버 스크립트입니다. 다양한 인수, 특히-y(변경한 이유) 에는 공백과 개행이 포함될 수 있습니다. 이 스크립트는 1992년에 작성되었으므로 백 틱 대신에 (cmd ...) 표기법을 사용하며 첫 줄에 #!/bin/sh를 사용하지 않습니다.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "$@"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(요즘은 이스케이프를 그렇게 철저하게 사용하지 않을 것입니다. 예를 들어 -e 인수와 함께 필요하지 않습니다 - 그러나 전반적으로 이것은 이스케이프를 사용하는 간단한 스크립트 중 하나입니다). escape프로그램은echo처럼 단순히 인수를 출력합니다. 처럼 단순히 인자를 출력하지만, 인자가 함께 사용하도록 보호됩니다. 평가 (eval의 한 수준; 원격 셸을 실행하는 프로그램이 있는데 실행하고 escape의 출력을 이스케이프해야 하는 프로그램이 있습니다).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

인수를 한 줄에 하나씩 나열하는 al이라는 또 다른 프로그램이 있습니다. (그리고 훨씬 더 오래된 버전입니다: 1987-01-27T14:35:49의 버전 1.1). 이 프로그램은 스크립트를 디버깅할 때 가장 유용합니다. 명령줄에 연결하여 명령에 실제로 어떤 인수가 전달되는지 확인할 수 있습니다.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[추가: 이제 다양한 "$@" 표기법의 차이를 보여드리기 위해 한 가지 예제를 더 살펴보겠습니다:

$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

명령줄에서 **/* 사이의 원래 공백은 보존되지 않는다는 점에 유의하세요. 또한 다음을 사용하여 셸에서 '명령줄 인수'를 변경할 수 있다는 점에 유의하세요:

set -- -new -opt and "arg with space"

이것은 '-new', '-opt', 'and', 'arg with space'의 4가지 옵션을 설정합니다.
] 흠, 꽤 긴 답변이네요 - 아마도 해석이 더 나은 용어일 것입니다. 요청 시 이스케이프의 소스 코드 제공(이름 닷컴으로 이메일 보내주세요. 성은 gmail 닷컴으로 이메일). al`의 소스 코드는 매우 간단합니다:

#include 
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

그게 다입니다. 이것은 로버트 갬블이 보여준 test.sh 스크립트와 동일하며 셸 함수로 작성할 수 있습니다(하지만 제가 al을 처음 작성할 당시에는 셸 함수가 로컬 버전의 본 셸에 존재하지 않았습니다). 또한 al을 간단한 셸 스크립트로 작성할 수도 있습니다:

[ $# != 0 ] && printf "%s\n" "$@"

인수가 전달되지 않았을 때 출력을 생성하지 않도록 조건문이 필요합니다. printf` 명령은 형식 문자열 인수만 있는 빈 줄을 생성하지만 C 프로그램에서는 아무 것도 생성하지 않습니다.

해설 (1)

Robert의 대답이 정답이며 sh에서도 작동한다는 점에 유의하세요. (휴대용으로) 더 단순화할 수 있습니다:

for i in "$@"

와 같습니다:

for i

즉, 아무것도 필요하지 않습니다!

테스트 ($는 명령 프롬프트):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d

저는 커니건과 파이크의 유닉스 프로그래밍 환경에서 이에 대해 처음 읽었습니다.

bash에서help for`는 이를 문서화합니다:

for NAME [in WORDS ... ;] do COMMANDS; done

'in WORDS ...;'가 없으면 'in $@'&#39가 가정됩니다.

해설 (10)

간단한 경우에 대한 'shift' 을 사용할 수도 있습니다. It 는 인수 목록 같은 대기열은. '각' shift+ctrl 아웃해야 던지는 첫 번째 인수 및 각각의 나머지 색인입니다 인수만 는 감소합니다.

#this prints all arguments
while test $# -gt 0
do
    echo "$1"
    shift
done
해설 (7)

예를 들어 don& 요소이면 어레이에서는 솔리드로 액세스할 수 있습니다 # 39 모두야 반복할 경우 싶지 않다.


argc=$#
argv=("$@")

for (( j=0; j
해설 (3)
aparse() {
while [[ $# > 0 ]] ; do
  case "$1" in
    --arg1)
      varg1=${2}
      shift
      ;;
    --arg2)
      varg2=true
      ;;
  esac
  shift
done
}

aparse "$@"
해설 (1)