변수에 저장된 명령을 어떻게 실행할 수 있습니까?


34
$ ls -l /tmp/test/my\ dir/
total 0

위의 명령을 실행하는 다음 방법이 실패하거나 성공하는 이유가 궁금합니다.

$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0


답변:


54

이것은 유닉스 SE에 대한 여러 가지 질문에서 논의되었으며, 여기서 내가 할 수있는 모든 문제를 수집하려고 노력할 것입니다. 끝에 참조.


왜 실패

이러한 문제에 직면하는 이유는 단어 분할 이며 변수에서 확장 된 따옴표는 따옴표로 작용하지 않고 단지 평범한 문자 일뿐입니다.

질문에 제시된 사례 :

$ abc='ls -l "/tmp/test/my dir"'

여기서는 $abc분할 ls되어 두 개의 인수 "/tmp/test/mydir"첫 번째 앞면과 두 번째 뒷면의 따옴표를 가져옵니다 .

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

여기에서는 확장이 인용되어 있으므로 한 단어로 유지됩니다. 쉘은 ls -l "/tmp/test/my dir", 공백 및 따옴표가 포함 된 프로그램을 찾으려고합니다 .

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

그리고 첫 번째 단어 또는 만 $abc인수로 사용 -c되므로 Bash ls는 현재 디렉토리에서 실행됩니다 . 다른 말은 bash에 대한 인수 $0이며 $1, 등 을 채우는 데 사용됩니다 .

$ bash -c $abc
'my dir'

으로 bash -c "$abc"하고 eval "$abc", 거기에 따옴표 작업을 수행 추가적인 쉘 처리 단계,하지만 모든 쉘 확장이 다시 처리되도록 당신은 매우 아니라면 그래서 실수로 사용자가 제공 한 데이터로부터 명령 확장을 실행의 위험이있다, 인용에주의하십시오.


더 나은 방법

명령을 저장하는 두 가지 더 좋은 방법은 a) 대신 함수를 사용하고 b) 배열 변수 (또는 위치 매개 변수)를 사용하는 것입니다.

기능 사용하기 :

내부에 명령이있는 함수를 선언하고 마치 명령 인 것처럼 함수를 실행하십시오. 함수 내 명령의 확장은 명령이 정의 된 경우가 아니라 명령이 실행될 때만 처리되며 개별 명령을 인용 할 필요가 없습니다.

# define it
myls() {
    ls -l "/tmp/test/my dir"
}

# run it
myls

배열 사용하기 :

배열을 사용하면 개별 단어에 공백이 포함 된 다중 단어 변수를 만들 수 있습니다. 여기서 개별 단어는 별개의 배열 요소로 저장되며 "${array[@]}"확장은 각 요소를 별도의 셸 단어로 확장합니다.

# define the array
mycmd=(ls -l "/tmp/test/my dir")

# run the command
"${mycmd[@]}"

구문은 약간 끔찍하지만 배열을 사용하면 명령 줄을 하나씩 만들 수도 있습니다. 예를 들면 다음과 같습니다.

mycmd=(ls)               # initial command
if [ "$want_detail" = 1 ]; then
    mycmd+=(-l)          # optional flag
fi
mycmd+=("$targetdir")    # the filename

"${mycmd[@]}"

또는 명령 행의 일부를 일정하게 유지하고 배열을 사용하여 그 일부, 옵션 또는 파일 이름 만 채 웁니다.

options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir

transmutate "${options[@]}" "${files[@]}" "$target"

배열의 단점은 표준 기능이 아니기 때문에 일반 POSIX 셸 (예 dash: /bin/sh데비안 / 우분투 의 기본값 )은 지원하지 않습니다 (아래 참조). 그러나 Bash, ksh 및 zsh는 시스템에 배열을 지원하는 쉘이있을 수 있습니다.

사용 "$@"

명명 된 배열을 지원하지 않는 쉘에서, 여전히 위치 매개 변수 (의사 배열 "$@")를 사용하여 명령의 인수를 보유 할 수 있습니다.

다음은 이전 섹션의 코드 비트와 동등한 휴대용 스크립트 비트 여야합니다. 배열은 "$@"위치 매개 변수 목록 인으로 대체됩니다 . 설정 "$@"은로 이루어지며 set큰 따옴표 "$@"는 중요합니다 (이로 인해 목록의 요소가 개별적으로 인용됩니다).

먼저, 인수가있는 명령을 저장하고 실행하기 만하면 "$@"됩니다.

set -- ls -l "/tmp/test/my dir"
"$@"

명령에 대한 명령 행 옵션의 일부를 조건부로 설정합니다.

set -- ls
if [ "$want_detail" = 1 ]; then
    set -- "$@" -l
fi
set -- "$@" "$targetdir"

"$@"

"$@"옵션 및 피연산자 에만 사용 :

set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir

transmutate "$@"

(물론, "$@"일반적으로 스크립트 자체에 대한 인수로 채워져 있으므로 용도를 변경하기 전에 어딘가에 저장해야합니다 "$@".)


조심해 eval!

으로 eval인용 및 확장 처리 소개합니다 추가 수준, 당신은 사용자 입력에주의 할 필요가있다. 예를 들어, 사용자가 작은 따옴표를 입력하지 않는 한 작동합니다.

read -r filename
cmd="ls -l '$filename'"
eval "$cmd";

그러나 그들이 input을 제공하면 '$(uname)'.txt스크립트는 행복하게 명령 대체를 실행합니다.

배열이있는 버전은 단어가 항상 분리되어 있기 때문에의 내용에 대한 인용이나 다른 처리가 없습니다 filename.

read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"

참고 문헌


2
을 사용하여 평가 견적을 해결할 수 있습니다 cmd="ls -l $(printf "%q" "$filename")". 예쁘지 않지만 사용자가를 사용하여 죽은 eval경우 도움이됩니다. 또한 이와 같은 ssh foohost "ls -l $(printf "%q" "$filename")"질문이나 비슷한 질문을 통해 명령을 보내는 데 매우 유용합니다 ssh foohost "$cmd".
Patrick

직접 관련이 없지만 디렉토리를 하드 코딩 했습니까? 이 경우 별칭을보고 싶을 수 있습니다. 뭔가 같은 : $ alias abc='ls -l "/tmp/test/my dir"'
호핑 버니

4

(사소하지 않은) 명령을 실행하는 가장 안전한 방법은 eval입니다. 그런 다음 명령 행에서와 같이 명령을 작성할 수 있으며 방금 입력 한 것처럼 정확하게 실행됩니다. 그러나 모든 것을 인용해야합니다.

간단한 경우 :

abc='ls -l "/tmp/test/my dir"'
eval "$abc"

그렇게 간단한 경우는 아닙니다.

# command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
abc='awk '\''! a[$0]++ { print "foo: " $0; }'\'' inputfile'
eval "$abc"

3

두 번째 따옴표는 명령을 어 깁니다.

내가 실행할 때 :

abc="ls -l '/home/wattana/Desktop'"
$abc

그것은 나에게 오류를 주었다.

하지만 내가 달릴 때

abc="ls -l /home/wattana/Desktop"
$abc

전혀 오류가 없습니다

한 번 에이 문제를 해결할 수있는 방법은 없지만 디렉토리 이름에 공백이 없어 오류를 피할 수 있습니다.

이 대답 은 eval 명령을 사용 하여이 문제를 해결할 수는 있지만 작동하지 않는다고 말했습니다.


1
예, 공백이 포함 된 파일 이름 (또는 glob 문자가 포함 된 파일 이름)이 필요하지 않는 한 작동합니다.
ilkkachu

0

이 기능이 작동하지 않으면 ''다음을 사용해야합니다 ``.

abc=`ls -l /tmp/test/my\ dir/`

더 나은 방법으로 업데이트하십시오.

abc=$(ls -l /tmp/test/my\ dir/)

명령 결과를 변수에 저장합니다. OP는 (이상하게) 명령 자체를 변수에 저장하려고합니다. 아, 그리고 당신은 $( command... )백틱 대신에 실제로 사용하기 시작해야합니다 .
roaima

설명과 팁을 주셔서 대단히 감사합니다!
balon

0

python3 원 라이너는 어떻습니까?

bash-4.4# pwd
/home
bash-4.4# CUSTOM="ls -altr"
bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)"
total 8
drwxr-xr-x    2 root     root          4096 Mar  4  2019 .
drwxr-xr-x    1 root     root          4096 Nov 27 16:42 ..
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.