बाश में क्यों बचना चाहिए, और इसके बजाय मुझे क्या उपयोग करना चाहिए?


107

समय और समय फिर से, मैं evalइस तरह के एक "बुराई" निर्माण के उपयोग के लिए स्टैक ओवरफ्लो पर बैश के जवाब देखता हूं और जवाब धराशायी हो जाता है, इरादा है। evalइतनी बुराई क्यों है ?

यदि evalसुरक्षित रूप से उपयोग नहीं किया जा सकता है, तो मुझे इसके बजाय क्या उपयोग करना चाहिए?

जवाबों:


148

आंख से मिलने से ज्यादा यह समस्या है। हम स्पष्ट के साथ शुरू करेंगे: eval"गंदे" डेटा को निष्पादित करने की क्षमता है। गंदा डेटा वह डेटा है जिसे सुरक्षित-उपयोग-में-स्थिति-XYZ के रूप में फिर से नहीं लिखा गया है; हमारे मामले में, यह कोई भी स्ट्रिंग है जिसे मूल्यांकन के लिए सुरक्षित होने के लिए स्वरूपित नहीं किया गया है।

पहली नज़र में Sanitizing डेटा आसान प्रतीत होता है। मान लें कि हम विकल्पों की एक सूची के आसपास फेंक रहे हैं, बैश पहले से ही अलग-अलग तत्वों को साफ करने का एक शानदार तरीका प्रदान करता है, और एक एकल स्ट्रिंग के रूप में पूरे सरणी को पवित्र करने का एक और तरीका है:

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

अब हम प्रिंटलाइन के तर्क के रूप में आउटपुट को पुनर्निर्देशित करने का विकल्प जोड़ना चाहते हैं। बेशक, हम प्रत्येक कॉल पर प्रिंटलाइन के आउटपुट को पुनर्निर्देशित कर सकते हैं, लेकिन उदाहरण के लिए, हम ऐसा नहीं करने जा रहे हैं। हमें उपयोग करने की आवश्यकता होगी 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 पार्स दो बार कमांड लाइन (किसी भी शेल में)। पार्सिंग के पहले पास पर कोटेशन की एक परत हटा दी जाती है। हटाए गए उद्धरणों के साथ, कुछ चर सामग्री निष्पादित हो जाती है।

हम चर विस्तार के भीतर जगह देकर इसे ठीक कर सकते हैं 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 विकल्प

विशिष्ट उपयोग के मामलों में अक्सर व्यवहार्य विकल्प होते हैं eval। यहाँ एक आसान सूची है। commandयह दर्शाता है कि आप सामान्य रूप से क्या भेजेंगे eval; जो भी आप चाहते हैं उसमें स्थानापन्न करें।

कोई-op

एक साधारण बृहदान्त्र बैश में एक no-op है:

:

एक उप-शेल बनाएं

( 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 को पवित्रा करना संभव है, लेकिन आपके पास यह समय बर्बाद करने का समय है:

echo "${!REF}"

यह सही है, बैश में वैरिएबल इनडायरेक्ट बिल्ट-इन वर्जन 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

सहयोगी सरणियाँ

सहयोगी सरणियों को आंतरिक रूप से बाश में लागू किया जाता है 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

बैश के पुराने संस्करणों में, आप चर अप्रत्यक्ष उपयोग कर सकते हैं:

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"`"
# ...

4
मुझे eval "export $var='$val'"... (?)
Zrin

1
@ श्री संभावना है कि आप क्या उम्मीद नहीं करते हैं। export "$var"="$val"शायद तुम क्या चाहते हो केवल एक बार जब आप अपने फॉर्म का उपयोग कर सकते हैं var='$var2', यदि आप कर रहे हैं, और आप इसे दोगुना करना चाहते हैं - लेकिन आपको ऐसा कुछ भी करने की कोशिश नहीं करनी चाहिए। यदि आपको वास्तव में चाहिए, तो आप उपयोग कर सकते हैं export "${!var}"="$val"
Zenexer

1
@ एनीसेन: आपके मान के लिए, x="echo hello world";फिर जो कुछ भी निहित है उसे निष्पादित करने के लिए x, हम उपयोग कर सकते हैं eval $xहालांकि, $($x)गलत है, है ना? हां: $($x)गलत है क्योंकि यह चलता है echo hello worldऔर फिर कैप्चर किए गए आउटपुट को चलाने की कोशिश करता है (कम से कम संदर्भों में, जहां मुझे लगता है कि आप इसका उपयोग कर रहे हैं), जो तब तक विफल रहेगा जब तक कि आपको helloकिकिंग नामक प्रोग्राम नहीं मिला ।
जोनाथन लेफलर

1
@tmow आह, तो आप वास्तव में eval कार्यक्षमता चाहते हैं। यदि आप चाहते हैं, तो आप eval का उपयोग कर सकते हैं; बस इस बात का ध्यान रखें कि इसमें बहुत सारे सुरक्षा घेरे हैं। यह भी एक संकेत है कि आपके आवेदन में एक डिज़ाइन दोष है।
Zenexer

1
ref="${REF}_2" echo "${!ref}"उदाहरण गलत है, यह काम नहीं करेगा क्योंकि कमांड के निष्पादित होने से पहले बैश के विकल्प चर सकते हैं । यदि refचर वास्तव में पहले अपरिभाषित है, तो प्रतिस्थापन का परिणाम होगा ref="VAR_2" echo "", और यही निष्पादित किया जाएगा।
योरी एन।

17

कैसे 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 बुराई नहीं है - यह सिर्फ गलत समझा है :)


"Token_quote" फ़ंक्शन अपने तर्कों का उपयोग कैसे करता है? मैं नहीं इस सुविधा के बारे में कोई दस्तावेज़ीकरण देख सकते हैं ...
Akito


मुझे लगता है कि मैंने इसे बहुत अस्पष्ट रूप से कहा था। मेरा मतलब था फंक्शन की दलीलें। क्यों नहीं है arg="$1"? लूप के लिए यह कैसे पता चलता है कि कौन से तर्क कार्य के लिए दिए गए थे?
अकीतो

मैं केवल "गलत समझा" से आगे बढ़ूंगा, यह अक्सर दुरुपयोग भी होता है और वास्तव में इसकी आवश्यकता नहीं होती है। ज़ेनेक्सर के उत्तर में इस तरह के बहुत सारे मामले शामिल हैं, लेकिन किसी भी तरह का evalलाल झंडा होना चाहिए और इसकी पुष्टि करने के लिए बारीकी से जांच की जानी चाहिए कि वास्तव में भाषा द्वारा पहले से ही प्रदान किया गया एक बेहतर विकल्प नहीं है।
dimo414
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.