eval

eval [ arg . . . ]

eval 은 인수로 주어지는 스트링을 한번 evaluation 한 후에 결과를 다시 명령문으로 실행합니다.
그러므로 함수의 인수로 명령문 스트링을 받아서 함수 내에서 eval 로 실행시킬 수도 있습니다.
다음과 같은 경우는 특별히 eval 명령이 필요하지 않습니다.

# 다음의 경우 echo foo bar 명령문이 실행됩니다.
$ $( echo "echo foo bar" )
foo bar

하지만 명령문을 작성할때는 해당 명령에서 사용되는 스트링외에 shell 키워드, 메타문자 그리고 대입연산이 함께 사용됩니다. 이와 같은 문자나 단어들은 변수확장, 명령치환이 일어나기 전에 shell 에의해 해석되므로 다음과 같이 실행할 수 없습니다.

$ $( echo "if test 1 -eq 1; then echo equal; fi" )
if: command not found

$ $( echo "AA=100" )
AA=100: command not found

이럴 때 eval 을 사용할 수 있습니다.

$ eval "$( echo "if test 1 -eq 1; then echo equal; fi" )"
equal

$ eval "$( echo "AA=100" )"
$ echo $AA
100

awk 는 child process 로 실행되므로 실행 중에 현재 shell 의 변수값을 설정할 수 없는데요. 하지만 eval 명령을 활용하면 shell 변수값을 설정할 수 있습니다.

# 명령치환을 이용한 model, stepping, ucode 변수값 설정
for tuple in \
    INTEL_FAM6_KABYLAKE_DESKTOP,0x0B,0x80  \
    INTEL_FAM6_SKYLAKE_X,0x03,0x0100013e   \
    INTEL_FAM6_BROADWELL_CORE,0x04,0x28    \
    INTEL_FAM6_HASWELL_ULT,0x01,0x21       \
    INTEL_FAM6_IVYBRIDGE_X,0x04,0x42a      \
    INTEL_FAM6_SANDYBRIDGE_X,0x07,0x712
do
    model=$(echo $tuple | cut -d, -f1)
    stepping=$(( $(echo $tuple | cut -d, -f2) ))
    ucode=$(echo $tuple | cut -d, -f3)
    if [ "$cpu_model" = "$model" ] && [ "$cpu_stepping" = "$stepping" ]
    then
        . . . .
        . . . .
done
-------------------------------------------------------------------------

# eval 과 awk 를 활용하여 간단히 작성
for tuple in \
    . . .
    . . .
do
    eval "$( echo "$tuple" | 
    awk -F, '{ printf "model=%s; stepping=%s; ucode=%s;",$1,strtonum($2),$3 }' )"
    if [ "$cpu_model" = "$model" ] && [ "$cpu_stepping" = "$stepping" ]
    then
        . . . .
        . . . .
done

참고로 local, declare 를 이용하여도 변수값을 설정할 수 있습니다.

sh 은 함수 내에서 local 만 사용할 수 있습니다.

$ f1() {
    local $( awk 'BEGIN{ print "AA=100 BB=*" }' ) 
    echo "$AA : $BB"
}

$ f1
100 : *

eval 명령의 실행단계

eval 명령은 주어진 인수들을 읽어 들인 후 실행합니다. 여기서 주목해야 될 점은 두 단계를 거쳐서 실행이 된다는 점입니다. 첫째. 읽어들이는 단계 , 둘째. 실행하는 단계. 그래서 결과적으로 인수부분에서 확장과 치환이 두번 일어나게 됩니다. 읽어들일 때 한번 실행할 때 한번.

$ AA=100 BB=200

# 인수 부분에서 확장, 치환이 일어나 $BB 가 200 이되고
# 마지막에 값을 표시하는데 불필요한 quotes 을 삭제한다.
$ echo '$AA' $BB
$AA 200

# 1. eval 읽어 들이는 단계에서 위와 같이 확장, 치환이 되고 quotes 이 삭제된다.
# 2. 실행단계 에서도 확장, 치환이 일어나므로 $AA 는 100 이된다.
$ eval echo '$AA' $BB
100 200

# 1. single quotes 이 escape 됐으므로 read 단계에서 남게된다.
# echo '$AA' 200
# 2. 실행단계 에서 quotes 이 삭제되어 표시된다.
$ eval echo \''$AA'\' $BB
$AA 200

brace 확장에서는 range 값으로 변수를 사용하지 못하는데 ( 변수확장이 뒤에 일어나므로 )
eval 명령을 활용하면 brace 확장에서도 변수를 사용할 수 있습니다.

$ a=1 b=5

# 1. eval 명령 read 단계에서 변수확장이 일어난다.
# echo {1..5}
# 2. 실행단계 에서 brace 확장이 된다.
$ eval echo {$a..$b}
1 2 3 4 5

명령에 선행하는 대입연산을 이용할 때도 eval 명령을 사용할 수 있습니다. 명령 앞에서 대입한 값은 명령이 실행된 후에야 사용할 수 있는데 다음의 경우는 echo 명령이 실행되기 전에 $A, $B, $C 변수확장이 일어나서 기존의 값을 표시하게 됩니다.

$ A=1 B=2 C=3
$ A=4 B=5 C=6 echo $A $B $C
1 2 3

다음과 같이 eval 명령을 사용하면 해결할 수 있습니다.

$ A=1 B=2 C=3

# 1. eval 명령 read 단계에 quotes 이 삭제된다
# echo $A $B $C
# 2. 실행단계 : 이미 eval 명령이 실행중에 있으므로 대입한 값이 사용된다.
$ A=4 B=5 C=6 eval 'echo $A $B $C'
4 5 6
$ echo $A $B $C      # 명령 종료 후에는 원래 값으로 복귀
1 2 3

스크립트 실행 중에 명령문을 만들어 실행하기

다음과 같은 경우 "*.sh" 에서 사용된 double quotes 이 명령 실행시 제거되기 위해서는 eval 명령이 필요합니다.

$ AA='find * -name'

$ BB='"*.sh"'

$ eval "$AA $BB"
args.sh
sub.sh
test.sh
...
...

eval 명령을 사용하다 보면 복잡해 보일 때가 있는데 이때는 읽기 부분은 eval 을 echo 로 바꾸어 실행해 봅니다. echo 명령을 실행해서 나온 결과가 eval 명령이 실행단계에서 실행하게될 명령입니다. 그리고 그 명령이 실행돼서 나온 종료 상태 값이 eval 명령의 종료 상태 값이 됩니다.

$ eval "$(echo hello world | sed 's/hello/wc <<< /')"
1 1 6
$ echo $?
0

# 1. 읽기단계: eval 을 echo 로 변경하여 실행해봄.
$ echo "$(echo hello world | sed 's/hello/wc <<< /')"
wc <<<  world    # 이 명령이 eval 이 실행단계에서 실행하게 될 명령임.

$ wc <<<  world  # 2. 실행단계
1 1 6            # 이 값이 eval 명령의 stdout 값이 되고
$ echo $?
0                # 이 값이 eval 명령의 종료 상태 값이 됩니다.

eval 명령을 이용한 indirection

다음과 같이 eval 명령을 indirection 에 활용할 수 있습니다.

$ AA=BB

$ echo $AA
BB

$ eval $AA=100

$ echo $BB
100
--------------------

$ hello=123

$ linux=hello

$ eval echo \$$linux
123
.....................

if test $hello -eq  $(eval echo \$$linux); then
...
fi

대입연산은 shell keyword 와 같이 변수확장 전에 처리되므로 단순히 $AA=100 로 할 수 없습니다.

$ AA=BB

$ $AA=100
BB=100: command not found

함수를 만들어 실행

$ main() {
>     local fbody='() { echo "function name is : $FUNCNAME"; }'
>     local fname
>     for fname in f{1..10}; do
>         eval "${fname}${fbody}"   # 이 부분에서 함수 정의가 됨
>         $fname                    # 함수명으로 실행
>     done
> }

$ main
function name is : f1
function name is : f2
function name is : f3
...
...

파이프 와 redirection

eval 은 명령이기 때문에 파이프와 같이 사용될 경우 한쪽에만 적용되는 것입니다.
그러므로 다음과 같은 경우 eval 은 파이프 왼쪽에 위치한 명령에만 적용됩니다.

# command1 에만 eval 이 적용
$ eval command1 ... | command2 ...

전체 명령을 eval 하려면 다음과 같이합니다.

# command2 에도 eval 이 적용되려면
$ eval command1 ... | eval command2 ...

# 전체 명령을 quote 하면 하나의 eval 만 사용할 수 있습니다.
$ eval "command1 ... | command2 ..."

# 다음과 같이 파이프를 escape 할 수도 있습니다.
$ eval command1 ... \| command2 ...

# 파이프를 escape 할 경우 redirection 이 사용되면 같이 escape 해줘야 합니다.
$ eval command1  ... '2>&1 |' command2 ...