실행될 때 변수를 인용해야합니까?


18

쉘 스크립팅의 일반적인 규칙은 강제적 인 이유가없는 한 변수를 항상 인용해야한다는 것입니다. 알고 싶은 것보다 자세한 내용 은 bash / POSIX 셸에서 변수를 인용하는 것을 잊어 버린 보안에 대한 훌륭한 Q & A를 살펴보십시오 .

그러나 다음과 같은 기능을 고려하십시오.

run_this(){
    $@
}

$@거기에 인용 해야합니까 ? 나는 그것을 조금 가지고 놀고 따옴표가 부족하여 문제를 일으킨 사례를 찾을 수 없었습니다. 반면에 따옴표를 사용하면 공백이 포함 된 명령을 따옴표로 묶은 변수로 전달할 때 중단됩니다.

#!/usr/bin/sh
set -x
run_this(){
    $@
}
run_that(){
    "$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"

위의 스크립트를 실행하면 다음이 반환됩니다.

$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users  0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found

run_that $comm대신에 사용하면 해결할 수 run_that "$comm"있지만 run_this인용되지 않은 기능은 두 가지 모두에서 작동하므로 더 안전한 내기처럼 보입니다.

따라서 $@작업을 $@명령 으로 실행하는 함수에서 사용하는 특정 경우를 $@인용 해야 합니까? 인용하지 말아야 할 이유 / 설명을 설명하고이를 손상시킬 수있는 데이터의 예를 제시하십시오.


6
run_that의 행동은 내가 기대하는 것입니다 (명령 경로에 공백이 있으면 어떻게됩니까?). 다른 행동을 원한다면 데이터가 무엇인지 아는 전화 사이트 에서 인용을 취소 해야합니다. 이 함수를로 호출하면 run_that ls -l어느 버전에서나 동일하게 작동합니다. 다르게 기대했던 사건이 있습니까?
마이클 호머

@MichaelHomer 여기에서 편집 한 내용이 다음과 같은
muru

@MichaelHomer 어떤 이유로 (아마도 여전히 두 번째 커피 잔을 마시지 않았기 때문에) 나는 명령의 인수 나 경로에서 공백을 고려하지 않았지만 명령 자체에서만 (옵션) 고려했습니다. 종종 그렇듯이, 이것은 회고에서 매우 분명해 보입니다.
terdon

쉘이 단순히 명령을 배열에 채우고로 실행하는 대신 여전히 함수를 지원하는 이유가 ${mycmd[@]}있습니다.
chepner

답변:


20

문제는 명령이 함수에 전달되는 방법에 있습니다.

$ run_this ls -l Untitled\ Document.pdf 
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_that ls -l Untitled\ Document.pdf 
-rw------- 1 muru muru 33879 Dec 20 11:09 Untitled Document.pdf

"$@"run_this함수가 일반적으로 작성된 명령 앞에 붙는 일반적인 경우에 사용해야합니다 . run_this지옥을 인용하다 :

$ run_this 'ls -l Untitled\ Document.pdf'
ls: cannot access Untitled\: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l "Untitled\ Document.pdf"'
ls: cannot access "Untitled\: No such file or directory
ls: cannot access Document.pdf": No such file or directory
$ run_this 'ls -l Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l' 'Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory

공백이있는 파일 이름을에 전달 해야하는 방법을 잘 모르겠습니다 run_this.


1
실제로이 메시지를 표시 한 편집입니다. 어떤 이유로 공백이있는 파일 이름으로 테스트하는 것은 발생하지 않았습니다. 왜 그런지 전혀 몰랐지만 당신은 간다. 당신은 물론 옳습니다, 나는 이것을 올바르게 수행하는 방법을 보지 못합니다 run_this.
terdon

@terdon 인용은 습관이 너무 많아서 $@실수로 인용하지 않은 것으로 가정했습니다 . 나는 예를 남겼어야했다. : D
muru December

2
아냐, 그것은 실제로 너무 많은 습관이었고 (잘못) 그것을 시험해 보았고 "어쩌면 이것도 따옴표가 필요하지 않다"고 결론 지었다. 뇌파로 알려진 절차.
terdon

1
공백이있는 파일 이름은에 전달할 수 없습니다 run_this. 이것은 기본적으로 Bash FAQ 050 에서 설명한 것처럼 복잡한 명령을 문자열로 채우는 것과 동일한 문제 입니다.
Etan Reisner

9

다음 중 하나입니다.

interpret_this_shell_code() {
  eval "$1"
}

또는:

interpret_the_shell_code_resulting_from_the_concatenation_of_those_strings_with_spaces() {
  eval "$@"
}

또는:

execute_this_simple_command_with_these_arguments() {
  "$@"
}

그러나:

execute_the_simple_command_with_the_arguments_resulting_from_split+glob_applied_to_these_strings() {
  $@
}

별로 이해가되지 않습니다.

당신이 실행하려는 경우 ls -l명령 (안 ls와 명령 ls-l인수 등)을, 당신이 할 것입니다 :

interpret_this_shell_code '"ls -l"'
execute_this_simple_command_with_these_arguments 'ls -l'

(가능성이), 그것은 그러나 만약 ls명령과 ls-l인수로, 당신은 실행할 것입니다 :

interpret_this_shell_code 'ls -l'
execute_this_simple_command_with_these_arguments ls -l

실행하려는 단순한 명령 이상인 경우 변수 할당, 방향 재 지정, 파이프 등을 수행하려는 경우 다음 작업 만 interpret_this_shell_code수행됩니다.

interpret_this_shell_code 'ls -l 2> /dev/null'

물론 당신은 항상 할 수 있습니다 :

execute_this_simple_command_with_these_arguments eval '
  ls -l 2> /dev/null'

5

bash / ksh / zsh 관점에서 보면 $*$@일반 배열 확장의 특별한 경우입니다. 배열 확장은 일반적인 변수 확장과 다릅니다.

$ a=("a b c" "d e" f)
$ printf ' -> %s\n' "${a[*]}"
 -> a b c d e f
$ printf ' -> %s\n' "${a[@]}"
-> a b c
-> d e
-> f
$ printf ' -> %s\n' ${a[*]}
 -> a
 -> b
 -> c
 -> d
 -> e
 -> f
$ printf ' -> %s\n' ${a[@]}
 -> a
 -> b
 -> c
 -> d
 -> e
 -> f

이랑 $* /의 ${a[*]}확장 당신의 첫 번째 값과 결합 배열 얻을 IFS공간은 기본적으로 하나의 거대한 문자열 - 어떤입니다. 인용하지 않으면 일반 문자열처럼 분할됩니다.

$@/ ${a[@]}확장, 동작이 있는지 여부에 따라 달라집니다$@ /의 ${a[@]}확장이 인용되지 않거나 :

  1. 인용 된 경우 ( "$@"또는"${a[@]}" ) 인 경우 "$1" "$2" "$3" #... 또는"${a[1]}" "${a[2]}" "${a[3]}" # ...
  2. 인용되지 않은 경우 ( $@또는 ${a[@]}) $1 $2 $3 #... 또는${a[1]} ${a[2]} ${a[3]} # ...

줄 바꿈 명령의 경우 가장 확실한 것은 인용 된 @ 확장 (1) .


bash (및 bash와 같은) 배열에 대한 더 좋은 정보 : https://lukeshu.com/blog/bash-arrays.html


1
Vader 마스크를 착용하면서 Luke로 시작하는 링크를 언급하고 있음을 깨달았습니다. 이 게시물로 힘이 강합니다.
PSkocik

4

큰 따옴표를 쓰지 않기 때문에 함수에 부여 한 링크$@ 에 모든 문제를 남겼습니다 .

라는 명령을 어떻게 실행할 수 *있습니까? 당신은 그것을 할 수 없습니다 run_this:

$ ls
1 2
$ run_this '*'
dash: 2: 1: not found
$ run_that '*'
dash: 3: *: not found

그리고 오류가 발생하더라도 run_that더 의미있는 메시지 를 보았습니다 .

$@개별 단어 로 확장 하는 유일한 방법 은 큰 따옴표입니다. 명령으로 명령을 실행하려면 명령과 명령 매개 변수를 분리 된 단어로 전달해야합니다. 함수 내부가 아닌 호출자 측에서 한 일.

$ cmd=ls
$ param1=-l
$ run_that "$cmd" "$param1"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

더 나은 선택입니다. 또는 쉘이 배열을 지원하는 경우 :

$ cmd=(ls -l)
$ run_that "${cmd[@]}"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

쉘이 배열을 전혀 지원하지 않더라도을 사용하여 배열을 재생할"$@" 수 있습니다 .


3

변수 실행 bash은 실패하기 쉬운 기술입니다. 쓰기는 불가능합니다run_this 과 같이 모든 경우를 올바르게 처리 함수 .

  • 파이프 라인 (예 : ls | grep filename )
  • 입 / 출력 리디렉션 (예 : ls > /dev/null )
  • 쉘 문장 if while

코드 반복을 피하는 것만으로도 기능을 사용하는 것이 좋습니다. 예를 들어,

run_this(){
    "$@"
}
command="ls -l"
...
run_this "$command"

당신은 작성해야합니다

command() {
    ls -l
}
...
command

런타임에만 명령을 사용할 수있는 경우 실패를 eval유발하는 모든 문제를 처리하도록 특별히 설계된을 사용해야합니다 run_this.

command="ls -l | grep filename > /dev/null"
...
eval "$command"

참고 eval되어 알려진 보안 문제에 대해,하지만 당신이 신뢰할 수없는 출처에서 변수를 전달하는 경우 run_this, 당신은 단지뿐만 아니라 임의의 코드가 실행에 직면하게 될 것이다.


1

선택은 당신입니다. 인용하지 않으면 $@그 값 중 하나가 추가 확장 및 해석됩니다. 인용하면 전달 된 모든 인수는 확장 그대로 그대로 재현됩니다. &>|어쨌든 인수를 직접 파싱하지 않고도 쉘 구문 토큰 등 을 안정적으로 처리 할 수 ​​없습니다. 따라서 함수 중 하나를 전달하는보다 합리적인 선택이 남아 있습니다.

  1. 와 함께 단일 간단한 명령을 실행하는 데 사용 된 단어입니다 "$@".

...또는...

  1. 추가 확장되고 해석 된 인수 버전은와 함께 간단한 명령으로 만 적용됩니다 $@.

의도적이고 의도 한 결과의 영향을 잘 이해한다면 어느 쪽의 방법도 잘못되지 않습니다. 두 가지 방법 모두 장점이 있지만 두 번째 방법의 장점은 특히 유용하지는 않습니다. 아직도...

(run_this(){ $@; }; IFS=@ run_this 'ls@-dl@/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

... 쓸모 없고 별로 많이 사용 되지 않을 것입니다 . 그리고 bash쉘에서, bash기본적 으로 변수 정의를 환경에 고정 하지 않기 때문에, 그 정의가 특수 내장 명령 행 또는 함수,$IFS 은 영향을받지 않으며 선언은 로컬입니다 run_this()통화 에만 .

비슷하게:

(run_this(){ $@; }; set -f; run_this ls -l \*)

ls: cannot access *: No such file or directory

글 로빙도 구성 할 수 있습니다. 따옴표는 목적을 제공합니다-그들은 아무것도 아닙니다. 그것들이 없으면 쉘 확장은 추가 해석- 구성 가능한 해석을 겪습니다 . 예전 에는 아주 오래된 쉘이 $IFS있었지만 확장뿐만 아니라 모든 입력 에 전체적으로 적용되었습니다 . 실제로, 쉘 은의 가치에 대한 모든 입력 단어를 깨뜨렸다는 점에서 매우 똑같이 행동했습니다 . 그리고 만약 당신이 찾고있는 것이 아주 오래된 쉘 동작이라면,을 사용해야 합니다 .run_this()$IFSrun_this()

나는 그것을 찾고 있지 않으며, 현재 유용한 예를 제시하기 위해 상당히 압박을 받고 있습니다. 나는 일반적으로 쉘이 실행하는 명령이 내가 입력하는 명령을 선호합니다. 따라서 선택의 여지가 있다면 나는 거의 항상 그렇습니다 run_that(). 그거 빼고 ...

(run_that(){ "$@"; }; IFS=l run_that 'ls' '-ld' '/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

무엇이든 인용 할 수 있습니다. 명령이 따옴표로 실행됩니다. 명령이 실제로 실행될 때 모든 입력 단어가 이미 따옴표 제거를 겪었 기 때문에 작동합니다. 이는 셸의 입력 해석 프로세스의 마지막 단계입니다. 따라서 쉘이 해석하는 동안 'ls'과 의 차이점은 ls중요합니다. 따라서 인용 ls하면 이름 이 지정된 별칭ls 이 인용 된 ls명령 단어로 대체되지 않습니다 . 그 외에는 따옴표가 영향을 미치는 유일한 것은 단어의 구분입니다. (변수 / 입력-공백 인용이 작동하는 방법과 이유) 과 메타 문자와 예약어의 해석입니다.

그래서:

'for' f in ...
 do   :
 done

bash: for: command not found
bash:  do: unexpected token 'do'
bash:  do: unexpected token 'done'

당신은 절대로 run_this()또는run_that() .

함수 이름, 또는 그러나 $PATH'D 명령, 또는 내장 매크로는 잘 인용 또는 인용 부호가 실행됩니다, 그리고 정확하게 방법 run_this()run_that()처음부터 일을. 당신은 $<>|&(){}그들 중 어떤 것도 유용한 것을 할 수 없을 것 입니다. 의 부족입니다 eval.

(run_that(){ "$@"; }; run_that eval printf '"%s\n"' '"$@"')

eval
printf
"%s\n"
"$@"

그러나 그것 없이는 사용하는 따옴표 때문에 간단한 명령의 한계로 제한됩니다 ( $@명령이 메타 문자로 구문 분석 될 때 프로세스의 시작 부분에서 따옴표처럼 행동 하기 때문에 그렇지 않은 경우에도 ) . 명령 줄 할당 및 리디렉션의 경우에도 동일한 제약 조건이 적용되며 이는 함수의 명령 줄로 제한됩니다. 그러나 그것은 큰 문제가 아닙니다.

(run_that(){ "$@";}; echo hey | run_that cat)

hey

나는 쉽게 가질 수 있었다 <>파이프를 열 때처럼 입력 또는 출력 리디렉션 .

어쨌든, 원형으로, 여기에는 옳고 그른 방법이 없습니다-각 방법마다 그 용도가 있습니다. 사용하려는 의도대로 작성해야하며, 무슨 뜻인지 알아야합니다. 따옴표를 생략하면 목적이있을 수 있습니다. 그렇지 않으면 따옴표 가 전혀 없습니다 . 하지만 목적과 관련이없는 이유로 따옴표를 생략하면 잘못된 코드를 작성하는 것입니다. 당신이 의미하는 것을하십시오; 어쨌든 노력합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.