쉘 스크립트의 변수에 명령을 저장하는 방법은 무엇입니까?


113

나중에 사용할 명령을 변수에 저장하고 싶습니다 (명령의 출력이 아니라 명령 자체).

다음과 같은 간단한 스크립트가 있습니다.

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

그러나 조금 더 복잡한 것을 시도하면 실패합니다. 예를 들어

command="ls | grep -c '^'";

출력은 다음과 같습니다.

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

나중에 사용할 수 있도록 이러한 명령 (파이프 / 다중 명령 포함)을 변수에 저장하는 방법에 대한 아이디어가 있습니까?


10
기능을 사용하십시오!
gniourf_gniourf

답변:


146

eval 사용 :

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

27
$ (...) 대신 backticks. y = $ (eval $ x) mywiki.wooledge.org/BashFAQ/082
James Broadhead

14
eval변수의 내용을 신뢰하는 경우 에만 허용되는 관행 입니다. 실행중인 경우 x="ls $name | wc"(또는 심지어 x="ls '$name' | wc") 권한이 적은 사람이 변수를 설정할 수 있다면이 코드는 주입 또는 권한 에스컬레이션 취약성에 대한 빠른 경로입니다. ( /tmp예를 들어의 모든 하위 디렉토리를 반복 합니까? 시스템의 모든 단일 사용자가를 호출하지 않도록 신뢰하는 것이 좋습니다 $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/').
Charles Duffy

9
eval는 예상치 못한 구문 분석 동작의 위험에 대한 경고없이 절대 권장해서는 안되는 거대한 버그 자석입니다 (@CharlesDuffy의 예에서와 같이 악의적 인 문자열이 없어도). 예를 들어, try x='echo $(( 6 * 7 ))'eval $x. "42"가 인쇄 될 것으로 예상 할 수 있지만 그렇지 않을 것입니다. 왜 작동하지 않는지 설명해 주시겠습니까? 내가 왜 "아마"라고 말했는지 설명해 주시겠습니까? 이러한 질문에 대한 답이 명확하지 않은 경우을 만지지 마십시오 eval.
Gordon Davisson

1
@Student, set -x사전에 실행 하여 명령 실행을 기록하면 무슨 일이 일어나고 있는지 쉽게 볼 수 있습니다.
Charles Duffy

1
@Student 나는 또한 일반적인 실수를 지적하기 위해 shellcheck.net 을 추천하고 싶습니다 .
Gordon Davisson 19-07-02

41

마십시오 하지 사용 eval! 임의의 코드가 실행될 위험이 있습니다.

BashFAQ-50- 변수에 명령을 넣으려고하는데 복잡한 경우는 항상 실패합니다.

배열에 넣고 큰 따옴표 "${arr[@]}"로 모든 단어를 확장 하여 단어 분할 로 인해 단어 가 분할 되지 않도록합니다 .IFS

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

내부 배열의 내용을 확인하십시오. 을 declare -p사용하면 각 명령 매개 변수를 사용하여 별도의 인덱스로 배열의 내용을 볼 수 있습니다. 그러한 인수 중 하나에 공백이 포함 된 경우 배열에 추가하는 동안 내부를 인용하면 Word-Splitting으로 인해 분할되지 않습니다.

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

다음과 같이 명령을 실행하십시오.

"${cmdArgs[@]}"
23:15:18

(또는) 모두 bash함수를 사용 하여 명령을 실행합니다.

cmd() {
   date '+%H:%M:%S'
}

다음과 같이 함수를 호출하십시오.

cmd

POSIX sh에는 배열이 없으므로 가장 가까운 방법은 위치 매개 변수에 요소 목록을 작성하는 것입니다. 다음 sh은 메일 프로그램을 실행 하는 POSIX 방법입니다.

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

이 접근 방식은 리디렉션없이 간단한 명령 만 처리 할 수 ​​있습니다. 리디렉션, 파이프 라인, for / while 루프, if 문 등을 처리 할 수 ​​없습니다.

또 다른 일반적인 사용 사례는 curl여러 헤더 필드 및 페이로드로 실행할 때 입니다. 항상 아래와 같이 인수를 정의 curl하고 확장 된 배열 콘텐츠를 호출 할 수 있습니다.

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "${curlArgs[@]}"

다른 예시,

payload='{}'
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

이제 변수가 정의되었으므로 배열을 사용하여 명령 인수를 저장하십시오.

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

이제 적절한 인용 확장을 수행하십시오.

curl "${curlCMD[@]}"

이것은 내가 시도, 나를 위해 작동하지 않습니다 Command=('echo aaa | grep a')그리고 "${Command[@]}"그것은 그대로 명령을 실행 기대 echo aaa | grep a. 그렇지 않습니다. 을 (를) 교체하는 안전한 방법이 있는지 궁금 eval하지만 동일한 힘을 가진 각 솔루션은 eval위험 할 수 있습니다. 그렇지 않습니까?
학생

간단히 말해서 원래 문자열에 파이프 '|'가 포함되어 있으면 어떻게 작동합니까?
학생

@Student, 원본 문자열에 파이프가 포함 된 경우 해당 문자열은 bash 파서의 안전하지 않은 부분을 거쳐 코드로 실행되어야합니다. 이 경우 문자열을 사용하지 마십시오. 대신 함수를 사용하십시오 Command() { echo aaa | grep a; }.-그 후에 Command, 또는 result=$(Command)등을 실행할 수 있습니다 .
Charles Duffy

1
@ 학생, 맞아; 그러나 그것은 의도적으로 실패합니다 . 왜냐하면 당신이 요청하는 것은 본질적으로 안전 하지 않기 때문 입니다.
Charles Duffy

1
@Student : 나는 그것을 특정 조건에서 작동하지 않습니다 언급 마지막에 메모를 추가 한
Inian

25
var=$(echo "asdf")
echo $var
# => asdf

이 방법을 사용하면 명령이 즉시 평가되고 반환 값이 저장됩니다.

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

백틱과 동일

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

에서 eval을 사용하면 $(...)나중에 평가되지 않습니다.

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

eval을 사용하면 사용시 평가 eval됩니다.

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

위의 예에서 인수가있는 명령을 실행해야하는 경우 저장중인 문자열에 입력하십시오.

stored_date="date -u"
# ...

bash 스크립트의 경우 이는 거의 관련이 없지만 마지막 참고 사항입니다. 조심하세요 eval. 사용자가 제어하는 ​​문자열 만 평가하고 신뢰할 수없는 사용자로부터 온 문자열이나 신뢰할 수없는 사용자 입력에서 빌드 된 문자열은 절대로 평가하지 마십시오.

  • 명령을 인용하도록 상기시켜 준 @CharlesDuffy에게 감사드립니다!

명령에 파이프 '|'가 포함 된 원래 문제는 해결되지 않습니다.
학생

@Nate,을 포함 eval $stored_date할 때 충분할 수 있지만 훨씬 더 신뢰할 수 있습니다. 예를 들어 마지막 에 따옴표를 사용하거나 사용하지 않고 실행하십시오 . :)stored_datedateeval "$stored_date"str=$'printf \' * %s\\n\' *'; eval "$str""$str"
Charles Duffy

@CharlesDuffy 감사합니다, 인용하는 것을 잊었습니다. 내가 그것을 실행하는 것을 귀찮게했다면 내 린터가 불평했을 것입니다.
Nate

0

bash의 경우 다음과 같이 명령을 저장하십시오.

command="ls | grep -c '^'"

다음과 같이 명령을 실행하십시오.

echo $command | bash

1
확실하지는 않지만 명령을 실행하는이 방법은 'eval'을 사용하는 것과 동일한 위험이 있습니다.
Derek Hazell

0

다양한 방법을 시도했습니다.

printexec() {
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"
}

산출:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

보시다시피 세 번째 만 "$@"올바른 결과를 제공했습니다.


0

다음 주소로 주문을 등록하십시오. X=$(Command)

이것은 호출되기 전에도 여전히 실행됩니다. 이를 확인하고 확인하려면 다음을 수행하십시오.

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

-1
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

-8

나중에 사용해야하는 경우에도 변수에 명령을 저장할 필요가 없습니다. 정상적으로 실행하십시오. 변수에 저장하는 경우 eval"변수를 실행"하기 위해 일종의 명령문 이 필요 하거나 불필요한 쉘 프로세스를 호출해야합니다.


1
내가 저장할 명령은 내가 보내는 옵션에 따라 달라 지므로, 프로그램의 대량에 많은 조건문을 포함하는 대신 나중에 사용하기 위해 필요한 명령을 저장하는 것이 훨씬 쉽습니다.
Benjamin

1
@Benjamin, 그런 다음 최소한 옵션을 명령이 아닌 변수로 저장하십시오. 예var='*.txt'; find . -name "$var"
kurumi 2011
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.