Development Tip

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

yourdevel 2020. 9. 28. 10:15
반응형

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


쉘 / bash 스크립트를 만들고 싶은 복잡한 명령이 있습니다. $1쉽게 쓸 수 있습니다.

foo $1 args -o $1.ext

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

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


"$@"모든 인수를 나타내는 데 사용 합니다.

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

이것은 각 인수를 반복하고 별도의 행에 인쇄합니다. $ @는 인용 될 때 공백이 있으면 인수가 올바르게 분할된다는 점을 제외하면 $ *처럼 작동합니다.

sh test.sh 1 2 '3 4'
1
2
3 4

VonC 가 지금 삭제 한 답변다시 작성합니다 .

Robert Gamble의 간결한 대답은 질문을 직접적으로 다룹니다. 이것은 공백을 포함하는 파일 이름과 관련된 몇 가지 문제를 증폭시킵니다.

참조 : / bin / sh의 $ {1 : + "$ @"}

기본 논문 : "$@" 정확하고, $*(인용 부호로 둘러싸는) 거의 항상 잘못된 것입니다. 이 때문입니다 "$@"작품의 벌금 인수는 공백을 포함 할 때와 동일하게 작동 $*그렇지 않은 경우입니다. 어떤 상황에서는 "$*"괜찮지 만 "$@"보통 (항상 그런 것은 아님) 같은 장소에서 작동합니다. 인용되지 $@않으며 $*동등합니다 (거의 항상 잘못됨).

그래서, 차이 무엇인가 $*, $@, "$*", 그리고 "$@"? 그것들은 모두 '쉘에 대한 모든 인수'와 관련이 있지만 다른 일을합니다. 인용 부호로 둘러싸이지 않은, 때 $*$@같은 일을한다. 각 '단어'(비 공백의 시퀀스)를 별도의 인수로 취급합니다. 따옴표로 묶인 형식은 매우 다릅니다 "$*". 인수 목록을 공백으로 구분 된 단일 문자열로 "$@"취급하는 반면 , 인수는 명령 줄에 지정되었을 때와 거의 동일하게 취급합니다. "$@"위치 인수가 없으면 전혀 확장되지 않습니다. "$*"빈 문자열로 확장됩니다. 인식하기 어려울 수 있지만 차이가 있습니다. (비표준) 명령 도입 후 아래의 자세한 정보를 참조하십시오 al.

2 차 논문 : 공백이있는 인수를 처리 한 다음 다른 명령에 전달해야하는 경우 지원할 비표준 도구가 필요할 때가 있습니다. (또는 배열을 신중하게 사용해야합니다. "${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
    $ 

" He said, "Don't do this!""(따옴표, 큰 따옴표 및 공백 포함)와 같은 파일 이름이있을 때 이것은 정말 까다로워집니다 .

    $ 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
    $ 

셸 (모두)은 이러한 작업을 특히 쉽게 처리하지 못하므로 (재미있게도) 많은 유닉스 프로그램이 이러한 작업을 잘 처리하지 못합니다. Unix에서 파일 이름 (단일 구성 요소)은 슬래시와 NUL을 제외한 모든 문자를 포함 할 수 있습니다 '\0'. 그러나 쉘은 경로 이름에 공백이나 줄 바꿈 또는 탭을 사용하지 않도록 강력히 권장합니다. 또한 표준 Unix 파일 이름에 공백 등이 포함되지 않는 이유입니다.

공백과 기타 성가신 문자를 포함 할 수있는 파일 이름을 다룰 때는 매우주의해야합니다. 오래 전에 Unix에서 표준이 아닌 프로그램이 필요하다는 것을 알게되었습니다. 나는 그것을 부른다 escape(버전 1.1은 1989-08-23T16 : 01 : 45Z 일자).

다음은 escapeSCCS 제어 시스템과 함께 사용 되는 예입니다 . delta( 체크인 생각 )과 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.)

The escape program simply outputs its arguments, rather like echo does, but it ensures that the arguments are protected for use with eval (one level of eval; I do have a program which did remote shell execution, and that needed to escape the output of escape).

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

I have another program called al that lists its arguments one per line (and it is even more ancient: version 1.1 dated 1987-01-27T14:35:49). It is most useful when debugging scripts, as it can be plugged into a command line to see what arguments are actually passed to the command.

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

[Added: And now to show the difference between the various "$@" notations, here is one more example:

$ 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
$

Notice that nothing preserves the original blanks between the * and */* on the command line. Also, note that you can change the 'command line arguments' in the shell by using:

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

This sets 4 options, '-new', '-opt', 'and', and 'arg with space'.
]

Hmm, that's quite a long answer - perhaps exegesis is the better term. Source code for escape available on request (email to firstname dot lastname at gmail dot com). The source code for al is incredibly simple:

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

That's all. It is equivalent to the test.sh script that Robert Gamble showed, and could be written as a shell function (but shell functions didn't exist in the local version of Bourne shell when I first wrote al).

Also note that you can write al as a simple shell script:

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

The conditional is needed so that it produces no output when passed no arguments. The printf command will produce a blank line with only the format string argument, but the C program produces nothing.


Note that Robert's answer is correct, and it works in sh as well. You can (portably) simplify it even further:

for i in "$@"

is equivalent to:

for i

I.e., you don't need anything!

Testing ($ is command prompt):

$ 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

I first read about this in Unix Programming Environment by Kernighan and Pike.

In bash, help for documents this:

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

If 'in WORDS ...;' is not present, then 'in "$@"' is assumed.


For simple cases you can also use shift. It treats the argument list like a queue. Each shift throws the first argument out and the index of each of the remaining arguments is decremented.

#this prints all arguments
while test $# -gt 0
do
    echo "$1"
    shift
done

You can also access them as an array elements, for example if you don't want to iterate through all of them

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

for (( j=0; j<argc; j++ )); do
    echo "${argv[j]}"
done

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

aparse "$@"

참고URL : https://stackoverflow.com/questions/255898/how-to-iterate-over-arguments-in-a-bash-script

반응형