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