답변:
이 문제에는 눈에 보이는 것보다 더 많은 것이 있습니다. 우리는 명백한 것부터 시작할 것입니다 : eval
"더러운"데이터를 실행할 가능성이 있습니다. 더티 데이터는 현장에서 사용하기에 안전한 XYZ로 다시 작성되지 않은 데이터입니다. 우리의 경우 평가에 안전하도록 형식이 지정되지 않은 문자열입니다.
데이터 삭제는 한눈에 쉽게 나타납니다. 우리가 옵션 목록을 던지고 있다고 가정하면 bash는 이미 개별 요소를 삭제하는 좋은 방법과 전체 배열을 단일 문자열로 삭제하는 또 다른 방법을 제공합니다.
function println
{
# Send each element as a separate argument, starting with the second element.
# Arguments to printf:
# 1 -> "$1\n"
# 2 -> "$2"
# 3 -> "$3"
# 4 -> "$4"
# etc.
printf "$1\n" "${@:2}"
}
function error
{
# Send the first element as one argument, and the rest of the elements as a combined argument.
# Arguments to println:
# 1 -> '\e[31mError (%d): %s\e[m'
# 2 -> "$1"
# 3 -> "${*:2}"
println '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit "$1"
}
# This...
error 1234 Something went wrong.
# And this...
error 1234 'Something went wrong.'
# Result in the same output (as long as $IFS has not been modified).
이제 println에 대한 인수로 출력을 리디렉션하는 옵션을 추가하고 싶다고 가정 해 보겠습니다. 물론 각 호출에서 println의 출력을 리디렉션 할 수도 있지만 예를 들어 그렇게하지 않을 것입니다. 우리는 사용해야합니다 eval
변수가 출력을 리디렉션하는 데 사용 할 수 없기 때문에.
function println
{
eval printf "$2\n" "${@:3}" $1
}
function error
{
println '>&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit $1
}
error 1234 Something went wrong.
좋아 보이지? 문제는 eval이 모든 셸에서 명령 줄을 두 번 구문 분석한다는 것입니다. 구문 분석의 첫 번째 단계에서 인용의 한 계층이 제거됩니다. 따옴표가 제거되면 일부 가변 내용이 실행됩니다.
.NET Framework 내에서 변수 확장이 발생하도록하여이 문제를 해결할 수 있습니다 eval
. 우리가해야 할 일은 모든 것을 작은 따옴표로 묶고 큰 따옴표는 그대로 둡니다. 한 가지 예외 : 이전에 리디렉션을 확장해야 eval
하므로 따옴표 밖에 있어야합니다.
function println
{
eval 'printf "$2\n" "${@:3}"' $1
}
function error
{
println '&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit $1
}
error 1234 Something went wrong.
작동합니다. 그것은만큼도 안전 $1
에가 println
결코 더럽습니다.
이제 잠시만 기다려주세요. 저는 항상 원래 사용했던 따옴표없는 구문을 사용 sudo
합니다! 왜 여기서 작동하고 여기서 작동하지 않습니까? 왜 모든 것을 작은 따옴표로 묶어야 했습니까? sudo
좀 더 현대적입니다. 너무 단순화 된 것이기는하지만 수신하는 각 인수를 따옴표로 묶는 것을 알고 있습니다. eval
단순히 모든 것을 연결합니다.
불행히도 드롭 인 교체가없는 eval
것을 취급 인수처럼 sudo
하지, 같은 eval
내장 쉘입니다; 이는 함수처럼 새로운 스택과 범위를 생성하는 것보다 실행될 때 주변 코드의 환경과 범위를 차지하므로 중요합니다.
특정 사용 사례에는 종종 eval
. 다음은 편리한 목록입니다. command
일반적으로 보낼 대상을 나타냅니다 eval
. 원하는대로 대체하십시오.
간단한 콜론은 bash에서 작동하지 않습니다.
:
( command ) # Standard notation
외부 명령에 의존하지 마십시오. 항상 반환 값을 제어해야합니다. 이것들을 자신의 줄에 넣으십시오.
$(command) # Preferred
`command` # Old: should be avoided, and often considered deprecated
# Nesting:
$(command1 "$(command2)")
`command "\`command\`"` # Careful: \ only escapes $ and \ with old style, and
# special case \` results in nesting.
호출 코드에서 대상에 매핑 &3
(또는 이상 &2
) :
exec 3<&0 # Redirect from stdin
exec 3>&1 # Redirect to stdout
exec 3>&2 # Redirect to stderr
exec 3> /dev/null # Don't save output anywhere
exec 3> file.txt # Redirect to file
exec 3> "$var" # Redirect to file stored in $var--only works for files!
exec 3<&0 4>&1 # Input and output!
일회성 호출이라면 전체 셸을 리디렉션 할 필요가 없습니다.
func arg1 arg2 3>&2
호출되는 함수 내에서 다음으로 리디렉션하십시오 &3
.
command <&3 # Redirect stdin
command >&3 # Redirect stdout
command 2>&3 # Redirect stderr
command &>&3 # Redirect stdout and stderr
command 2>&1 >&3 # idem, but for older bash versions
command >&3 2>&1 # Redirect stdout to &3, and stderr to stdout: order matters
command <&3 >&4 # Input and output!
대본:
VAR='1 2 3'
REF=VAR
나쁜:
eval "echo \"\$$REF\""
왜? REF에 큰 따옴표가 포함되어 있으면 악용 할 코드가 손상되고 열립니다. REF를 삭제하는 것은 가능하지만 다음과 같은 경우에는 시간 낭비입니다.
echo "${!REF}"
맞습니다. bash에는 버전 2부터 변수 간접 지정 기능이 내장되어 eval
있습니다. 더 복잡한 작업을 수행하려는 경우 보다 약간 까다로워집니다 .
# Add to scenario:
VAR_2='4 5 6'
# We could use:
local ref="${REF}_2"
echo "${!ref}"
# Versus the bash < 2 method, which might be simpler to those accustomed to eval:
eval "echo \"\$${REF}_2\""
그럼에도 불구하고 새로운 방법은 더 직관적이지만 eval
.
연관 배열은 bash 4에서 본질적으로 구현됩니다. 한 가지주의 사항 : declare
.
declare -A VAR # Local
declare -gA VAR # Global
# Use spaces between parentheses and contents; I've heard reports of subtle bugs
# on some versions when they are omitted having to do with spaces in keys.
declare -A VAR=( ['']='a' [0]='1' ['duck']='quack' )
VAR+=( ['alpha']='beta' [2]=3 ) # Combine arrays
VAR['cow']='moo' # Set a single element
unset VAR['cow'] # Unset a single element
unset VAR # Unset an entire array
unset VAR[@] # Unset an entire array
unset VAR[*] # Unset each element with a key corresponding to a file in the
# current directory; if * doesn't expand, unset the entire array
local KEYS=( "${!VAR[@]}" ) # Get all of the keys in VAR
이전 버전의 bash에서는 변수 간접 지정을 사용할 수 있습니다.
VAR=( ) # This will store our keys.
# Store a value with a simple key.
# You will need to declare it in a global scope to make it global prior to bash 4.
# In bash 4, use the -g option.
declare "VAR_$key"="$value"
VAR+="$key"
# Or, if your version is lacking +=
VAR=( "$VAR[@]" "$key" )
# Recover a simple value.
local var_key="VAR_$key" # The name of the variable that holds the value
local var_value="${!var_key}" # The actual value--requires bash 2
# For < bash 2, eval is required for this method. Safe as long as $key is not dirty.
local var_value="`eval echo -n \"\$$var_value\""
# If you don't need to enumerate the indices quickly, and you're on bash 2+, this
# can be cut down to one line per operation:
declare "VAR_$key"="$value" # Store
echo "`var_key="VAR_$key" echo -n "${!var_key}"`" # Retrieve
# If you're using more complex values, you'll need to hash your keys:
function mkkey
{
local key="`mkpasswd -5R0 "$1" 00000000`"
echo -n "${key##*$}"
}
local var_key="VAR_`mkkey "$key"`"
# ...
export "$var"="$val"
아마도 당신이 원하는 것입니다. 양식을 사용할 수있는 유일한 시간은 if var='$var2'
이고 이중 역 참조를 원하지만 bash에서 이와 같은 작업을해서는 안됩니다. 꼭 필요한 경우 export "${!var}"="$val"
.
x="echo hello world";
을 위해 , 그러면에 포함 된 것을 실행하기 위해 x
, 우리는 사용할 수 있습니다. eval $x
그러나, $($x)
틀 렸습니다. 예 : $($x)
실행 echo hello world
한 다음 캡처 된 출력을 실행하려고 시도하기 때문에 잘못되었습니다 (적어도 사용하고 있다고 생각하는 컨텍스트에서) hello
.
ref="${REF}_2" echo "${!ref}"
예제가 잘못되었습니다. bash 는 명령이 실행 되기 전에 변수 를 대체하므로 의도 한대로 작동하지 않습니다 . ref
변수가 실제로 정의되지 않은 경우 대체 결과는 ref="VAR_2" echo ""
이고 이것이 실행됩니다.
eval
안전하게 만드는 방법eval
안전하게 사용할 수 있지만 모든 인수를 먼저 인용해야합니다. 방법은 다음과 같습니다.
당신을 위해 그것을 할이 기능 :
function token_quote {
local quoted=()
for token; do
quoted+=( "$(printf '%q' "$token")" )
done
printf '%s\n' "${quoted[*]}"
}
사용 예 :
신뢰할 수없는 사용자 입력 :
% input="Trying to hack you; date"
평가할 명령을 생성합니다.
% cmd=(echo "User gave:" "$input")
겉보기에 올바른 인용으로 평가하십시오 .
% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018
해킹당했습니다. date
문자 그대로 인쇄되지 않고 실행되었습니다.
대신 token_quote()
:
% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%
eval
악이 아닙니다-오해입니다 :)
in words
일부 for
는 선택 사항입니다.
arg="$1"
없습니까? for 루프는 어떤 인수가 함수에 전달되었는지 어떻게 알 수 있습니까?
eval
이며 언어에서 이미 제공하는 더 나은 옵션이 실제로 있는지 확인하기 위해 면밀히 조사되어야합니다.
eval "export $var='$val'"
... (?)