Bash의 eval 명령 및 일반적인 용도
bash 매뉴얼 페이지를 읽은 후이 게시물 과 관련하여 .
나는 eval
명령이 정확히 무엇을 하고 어떤 것이 일반적인 용도 인지 이해하는 데 여전히 어려움을 겪고 있습니다. 예를 들어 다음과 같은 경우 :
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
여기서 정확히 무슨 일이 일어나고 있으며 달러 기호와 백 슬래시는 어떻게 문제와 관련이 있습니까?
eval
문자열을 인수로 사용하여 명령 줄에 해당 문자열을 입력 한 것처럼 평가합니다. 여러 인수를 전달하면 먼저 인수 사이에 공백이 결합됩니다.
${$n}
bash의 구문 오류입니다. 중괄호 안에 변수 이름과 접두사 및 접미사를 사용할 수 있지만 임의의 bash 구문을 사용할 수 없으며 특히 변수 확장을 사용할 수 없습니다. "이름이이 변수에있는 변수의 값"이라고 말하는 방법이 있습니다.
echo ${!n}
one
$(…)
서브 쉘에서 괄호 안에 지정된 명령을 실행하고 (즉, 현재 쉘의 변수 값과 같은 모든 설정을 상속하는 별도의 프로세스에서) 출력을 수집합니다. 따라서 쉘 명령으로 echo $($n)
실행 $n
되며 출력을 표시합니다. 이후 $n
로 평가 1
, $($n)
명령을 실행하려는 시도 1
가 존재하지 않습니다.
eval echo \${$n}
에 전달 된 매개 변수를 실행합니다 eval
. 확장 후 매개 변수는 echo
및 ${1}
입니다. 따라서 eval echo \${$n}
명령을 실행합니다 echo ${1}
.
대부분의 경우 변수 대체 및 명령 대체 (즉, a가있을 때 $
)를 큰 따옴표로 묶어야합니다 "$foo", "$(foo)"
. 변수 및 명령 대체를 항상 큰 따옴표로 묶지 마십시오 . 큰 따옴표가 없으면 쉘은 필드 분할을 수행합니다 (즉, 변수 값 또는 명령의 출력을 개별 단어로 분할). 그런 다음 각 단어를 와일드 카드 패턴으로 처리합니다. 예를 들면 다음과 같습니다.
$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *
eval
자주 사용되지 않습니다. 일부 쉘에서 가장 일반적으로 사용되는 것은 런타임까지 이름을 알 수없는 변수의 값을 얻는 것입니다. bash에서는 ${!VAR}
구문 덕분에 필요하지 않습니다 . eval
연산자, 예약어 등을 포함하는 더 긴 명령을 구성해야하는 경우에도 여전히 유용합니다.
eval을 "실행 전에 한 번 더 표현 평가"라고 생각하면됩니다.
eval echo \${$n}
echo $1
1 차 평가 후 가됩니다 . 주목해야 할 세 가지 변경 사항 :
- 이
\$
되었다$
(백 슬래시가 필요하다, 그렇지 않으면 평가하는 시도${$n}
라는 변수 즉,{$n}
허용되지 않습니다) $n
~에 평가되었다1
eval
사라
두 번째 라운드에서는 기본적으로 echo $1
직접 실행할 수 있습니다.
따라서 eval <some command>
먼저 평가합니다 <some command>
(여기서는 대체 변수를 의미하고 이스케이프 문자를 올바른 문자로 대체하는 등)하고 결과 식을 다시 한 번 실행합니다.
eval
변수를 동적으로 만들거나 이와 같이 읽도록 특별히 설계된 프로그램의 출력을 읽으려는 경우에 사용됩니다. 예는 http://mywiki.wooledge.org/BashFAQ/048 을 참조 하십시오 . 또한이 링크에는 일반적인 사용 방법 eval
및 이와 관련된 위험이 포함되어 있습니다.
내 경험상, eval의 "일반적인"사용은 환경 변수를 설정하기 위해 쉘 명령을 생성하는 명령을 실행하는 것입니다.
환경 변수 모음을 사용하는 시스템이 있고 설정해야 할 값과 해당 값을 결정하는 스크립트 나 프로그램이있을 수 있습니다. 스크립트 나 프로그램을 실행할 때마다 포크 프로세스로 실행되므로 종료 될 때 환경 변수에 직접 수행하는 모든 작업이 손실됩니다. 그러나 해당 스크립트 나 프로그램은 내보내기 명령을 stdout으로 보낼 수 있습니다.
eval이 없으면 stdout을 임시 파일로 리디렉션하고 임시 파일을 소싱 한 다음 삭제해야합니다. eval을 사용하면 다음을 수행 할 수 있습니다.
eval "$(script-or-program)"
따옴표는 중요합니다. 이 (고려 된) 예제를 보자.
# activate.sh
echo 'I got activated!'
# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")
$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!
eval 문은 쉘에게 eval의 인수를 명령으로 취하여 명령 행을 통해 실행하도록 지시합니다. 다음과 같은 상황에서 유용합니다.
스크립트에서 변수로 명령을 정의하고 나중에 해당 명령을 사용하려면 eval을 사용해야합니다.
/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
업데이트 : 어떤 사람들은 eval을 사용해서는 안된다고 말합니다. 동의하지 않습니다. 손상된 입력을에 전달할 수있을 때 위험이 발생한다고 생각합니다 eval
. 그러나 위험하지 않은 많은 일반적인 상황이 있으므로 어떤 경우에도 eval을 사용하는 방법을 아는 것이 좋습니다. 이 stackoverflow 답변 은 평가의 위험과 평가의 대안을 설명합니다. 평가가 안전하고 효율적인지 여부를 결정하는 것은 궁극적으로 사용자의 몫입니다.
bash eval
문을 사용하면 bash 스크립트로 계산하거나 얻은 코드 줄을 실행할 수 있습니다.
아마도 가장 간단한 예는 다른 bash 스크립트를 텍스트 파일로 열고 각 텍스트 줄을 읽고 eval
순서대로 실행 하는 bash 프로그램 일 것입니다. source
가져온 스크립트의 내용에 대해 어떤 종류의 변환 (예 : 필터링 또는 대체)을 수행 할 필요가없는 한 bash 문과 본질적으로 동일한 동작 입니다.
나는 거의 필요하지 않지만 다른 변수에 할당 된 문자열에 이름 이 포함 된 eval
변수를 읽거나 쓰는 것이 유용하다는 것을 알았습니다 . 예를 들어, 코드 풋 프린트를 작게 유지하면서 중복성을 피하면서 변수 세트에 대한 조치를 수행합니다.
eval
개념적으로 간단합니다. 그러나 bash 언어의 엄격한 구문과 bash 인터프리터의 구문 분석 순서는 미묘한 차이가 있으며 eval
암호로 보이며 사용하기 어렵거나 이해하기 어렵습니다. 필수 사항은 다음과 같습니다.
전달 된 인수 는 런타임시 계산
eval
되는 문자열 표현식 입니다.eval
인수의 최종 구문 분석 결과를 스크립트 의 실제 코드 줄로 실행합니다 .구문 및 구문 분석 순서는 엄격합니다. 결과가 스크립트 범위에서 실행 가능한 bash 코드 행이 아닌 경우 프로그램은
eval
가비지를 실행하려고 할 때 명령문에서 충돌 합니다.테스트 할 때
eval
명령문을 바꾸고echo
표시되는 것을 볼 수 있습니다 . 현재 컨텍스트에서 합법적 인 코드이면 코드를 실행하는 것이eval
좋습니다.
다음 예제는 eval의 작동 방식을 명확히하는 데 도움이 될 수 있습니다 ...
예 1 :
eval
'정상적인'코드 앞에있는 진술은 NOP
$ eval a=b
$ eval echo $a
b
위의 예에서 첫 번째 eval
진술은 목적이 없으며 제거 할 수 있습니다. eval
코드에 대한 동적 측면이 없기 때문에 첫 번째 행에서 의미가 없습니다. 즉, 이미 bash 코드의 마지막 행으로 구문 분석되었으므로 bash 스크립트의 일반 코드 명령문과 동일합니다. 두 번째도 의미 eval
가 없습니다. 왜냐하면 구문 분석 단계 $a
가 문자 그대로의 문자열 로 변환 되기는하지만 간접적 인 것은 없습니다 (예 : 실제 bash 명사 또는 bash-held 스크립트 변수 의 문자열 값을 통한 참조 없음 ). eval
접두사가 없는 코드 줄로 .
예 2 :
문자열 값으로 전달 된 var 이름을 사용하여 var 할당을 수행하십시오.
$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval
에 있었다면 echo $key=$val
출력은 다음과 같습니다.
mykey=myval
즉 , 문자열 구문 분석의 최종 결과는 eval에 의해 실행될 것이므로 결국 echo 문의 결과는 ...
예 3 :
예 2에 더 많은 간접 추가
$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing
위의 예제는 앞의 예제보다 약간 더 복잡하여 bash의 구문 분석 순서와 특성에 더 크게 의존합니다. eval
라인은 대략 다음과 같은 순서로 내부적으로 분석 얻을 것이다 (다음 문 그냥 문이 최종 결과에 도달하기 위해 내부적으로 단계로 나누어받을 것입니다 방법을 보여 시도, 의사가 아닌 실제 코드이다 참고) .
eval eval \$$keyA=\$$valA # substitution of $keyA and $valA by interpreter
eval eval \$keyB=\$valB # convert '$' + name-strings to real vars by eval
eval $keyB=$valB # substitution of $keyB and $valB by interpreter
eval that=amazing # execute string literal 'that=amazing' by eval
가정 된 구문 분석 순서가 어떤 평가가 충분한 지 설명하지 않으면, 세 번째 예는 구문 분석을 더 자세히 설명하여 진행 상황을 명확하게 설명 할 수 있습니다.
예 4 :
이름 이 문자열에 포함 된 var 자체에 문자열 값이 포함되어 있는지 확인하십시오 .
a="User-provided"
b="Another user-provided optional value"
c=""
myvarname_a="a"
myvarname_b="b"
myvarname_c="c"
for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
eval varval=\$$varname
if [ -z "$varval" ]; then
read -p "$varname? " $varname
fi
done
첫 번째 반복에서 :
varname="myvarname_a"
배쉬의 인수를 구문 분석 eval
하고, eval
실행시 문자 그대로이보고 :
eval varval=\$$myvarname_a
다음 의사 코드 는 bash가 위의 실제 코드 줄을 해석하여에 의해 실행되는 최종 값에 도달 하는 방법 을 보여 줍니다 . (다음 줄은 정확한 bash 코드가 아니라 설명 적입니다) :eval
1. eval varval="\$" + "$varname" # This substitution resolved in eval statement
2. .................. "$myvarname_a" # $myvarname_a previously resolved by for-loop
3. .................. "a" # ... to this value
4. eval "varval=$a" # This requires one more parsing step
5. eval varval="User-provided" # Final result of parsing (eval executes this)
모든 파싱이 완료되면 결과가 실행되고 그 효과가 분명해 eval
지며, 그 자체에 대해 특별히 신비한 것이 없음을 나타내며, 인수 의 파싱 에 복잡성이 있습니다.
varval="User-provided"
The remaining code in the example above simply tests to see if the value assigned to $varval is null, and, if so, prompts the user to provide a value.
I originally intentionally never learned how to use eval, because most people will recommend to stay away from it like the plague. However I recently discovered a use case that made me facepalm for not recognizing it sooner.
If you have cron jobs that you want to run interactively to test, you might view the contents of the file with cat, and copy and paste the cron job to run it. Unfortunately, this involves touching the mouse, which is a sin in my book.
Lets say you have a cron job at /etc/cron.d/repeatme with the contents:
*/10 * * * * root program arg1 arg2
You cant execute this as a script with all the junk in front of it, but we can use cut to get rid of all the junk, wrap it in a subshell, and execute the string with eval
eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)
The cut command only prints out the 6th field of the file, delimited by spaces. Eval then executes that command.
I used a cron job here as an example, but the concept is to format text from stdout, and then evaluate that text.
The use of eval in this case is not insecure, because we know exactly what we will be evaluating before hand.
I've recently had to use eval
to force multiple brace expansions to be evaluated in the order I needed. Bash does multiple brace expansions from left to right, so
xargs -I_ cat _/{11..15}/{8..5}.jpg
expands to
xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg
but I needed the second brace expansion done first, yielding
xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg
The best I could come up with to do that was
xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)
This works because the single quotes protect the first set of braces from expansion during the parsing of the eval
command line, leaving them to be expanded by the subshell invoked by eval
.
There may be some cunning scheme involving nested brace expansions that allows this to happen in one step, but if there is I'm too old and stupid to see it.
You asked about typical uses.
One common complaint about shell scripting is that you (allegedly) can't pass by reference to get values back out of functions.
But actually, via "eval", you can pass by reference. The callee can pass back a list of variable assignments to be evaluated by the caller. It is pass by reference because the caller can allowed to specify the name(s) of the result variable(s) - see example below. Error results can be passed back standard names like errno and errstr.
Here is an example of passing by reference in bash:
#!/bin/bash
isint()
{
re='^[-]?[0-9]+$'
[[ $1 =~ $re ]]
}
#args 1: name of result variable, 2: first addend, 3: second addend
iadd()
{
if isint ${2} && isint ${3} ; then
echo "$1=$((${2}+${3}));errno=0"
return 0
else
echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
return 1
fi
}
var=1
echo "[1] var=$var"
eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
echo "errstr=$errstr"
echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"
eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
echo "errstr=$errstr"
echo "errno=$errno"
fi
echo "[3] var=$var (successfully changed)"
The output looks like this:
[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)
There is almost unlimited band width in that text output! And there are more possibilities if the multiple output lines are used: e.g., the first line could be used for variable assignments, the second for continuous 'stream of thought', but that's beyond the scope of this post.
I like the "evaluating your expression one additional time before execution" answer, and would like to clarify with another example.
var="\"par1 par2\""
echo $var # prints nicely "par1 par2"
function cntpars() {
echo " > Count: $#"
echo " > Pars : $*"
echo " > par1 : $1"
echo " > par2 : $2"
if [[ $# = 1 && $1 = "par1 par2" ]]; then
echo " > PASS"
else
echo " > FAIL"
return 1
fi
}
# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"
# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var
The Curious results in Option 2 are that we would have passed 2 parameters as follows:
- First Parameter:
"value
- Second Parameter:
content"
How is that for counter intuitive? The additional eval
will fix that.
Adapted from https://stackoverflow.com/a/40646371/744133
In the question:
who | grep $(tty | sed s:/dev/::)
outputs errors claiming that files a and tty do not exist. I understood this to mean that tty is not being interpreted before execution of grep, but instead that bash passed tty as a parameter to grep, which interpreted it as a file name.
There is also a situation of nested redirection, which should be handled by matched parentheses which should specify a child process, but bash is primitively a word separator, creating parameters to be sent to a program, therefore parentheses are not matched first, but interpreted as seen.
I got specific with grep, and specified the file as a parameter instead of using a pipe. I also simplified the base command, passing output from a command as a file, so that i/o piping would not be nested:
grep $(tty | sed s:/dev/::) <(who)
works well.
who | grep $(echo pts/3)
is not really desired, but eliminates the nested pipe and also works well.
In conclusion, bash does not seem to like nested pipping. It is important to understand that bash is not a new-wave program written in a recursive manner. Instead, bash is an old 1,2,3 program, which has been appended with features. For purposes of assuring backward compatibility, the initial manner of interpretation has never been modified. If bash was rewritten to first match parentheses, how many bugs would be introduced into how many bash programs? Many programmers love to be cryptic.
참고URL : https://stackoverflow.com/questions/11065077/eval-command-in-bash-and-its-typical-uses
'IT story' 카테고리의 다른 글
UnboundLocalError가 발생하는 이유를 이해하지 못함 (0) | 2020.06.13 |
---|---|
node.js, SSL을 사용한 socket.io (0) | 2020.06.13 |
git 리포지토리를 GitLab에서 GitHub로 옮길 수 있습니까? (0) | 2020.06.13 |
memset보다 bzero를 사용하는 이유는 무엇입니까? (0) | 2020.06.13 |
Oracle에서 여러 행의 열 값을 연결하는 SQL 쿼리 (0) | 2020.06.13 |