বাশ-এ কেন এড়ানো উচিত এবং এর পরিবর্তে আমার কী ব্যবহার করা উচিত?


107

সময় এবং সময় আবার, আমি স্ট্যাক ওভারফ্লো ব্যবহার করে বাশ উত্তরগুলি দেখতে পাই evalএবং উত্তরগুলি এইরকম "দুষ্টু" কনস্ট্রাক্টের ব্যবহারের জন্য ভিত্তিযুক্ত, শ্লেষযুক্ত হয়ে থাকে। evalএত খারাপ কেন ?

যদি evalনিরাপদে ব্যবহার না করা যায় তবে এর পরিবর্তে আমার কী ব্যবহার করা উচিত?

উত্তর:


148

চোখের সাথে মিলিত হওয়ার চেয়ে এই সমস্যাটি আরও রয়েছে। আমরা স্পষ্টতই শুরু করব: eval"নোংরা" ডেটা কার্যকর করার সম্ভাবনা রয়েছে। নোংরা ডেটা হ'ল এমন কোনও ডেটা যা ব্যবহারে-পরিস্থিতি-এক্স-জাইজেড হিসাবে নিরাপদ হিসাবে পুনরায় লেখা হয়নি; আমাদের ক্ষেত্রে, এটি এমন কোনও স্ট্রিং যা ফর্ম্যাট করা হয়নি যাতে মূল্যায়নের জন্য নিরাপদ থাকতে পারে।

স্যানিটাইজিং ডেটা প্রথম নজরে সহজেই উপস্থিত হয়। ধরে নিই আমরা বিকল্পগুলির একটি তালিকা ঘুরে বেড়াচ্ছি, ব্যাশ ইতিমধ্যে স্বতন্ত্র উপাদানগুলির স্যানিটাইজ করার একটি দুর্দান্ত উপায় এবং একক স্ট্রিং হিসাবে পুরো অ্যারেটিকে স্যানিটাইজ করার অন্য একটি উপায় সরবরাহ করে:

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, যাতে উদ্ধৃতিগুলির বাইরে থাকতে হবে:

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; আপনি যা খুশি তার পরিবর্তে।

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\""

কেন? যদি আরএফ-এ একটি ডাবল উক্তি থাকে তবে এটি কোডটি ভেঙে শোষণের জন্য খুলবে। আরএফকে স্যানিটাইজ করা সম্ভব, তবে আপনার যখন এটি থাকে তখন এটি নষ্ট হয়:

echo "${!REF}"

এটি ঠিক আছে, ভার্শনের ভার্শিয়াল ইনডিয়ারেশন 2 ভার্সন হিসাবে অন্তর্নির্মিত রয়েছে you 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 এ স্বতন্ত্রভাবে প্রয়োগ করা হয় One একটি সতর্কতা: সেগুলি অবশ্যই ব্যবহার করে তৈরি করা উচিত 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'"... (?)
15-28 এ জ্রিন

1
@ জ্রিন সম্ভাবনা হ'ল যা আপনি প্রত্যাশা করেন না do export "$var"="$val"সম্ভবত আপনি কি চান আপনি কেবল একবার আপনার ফর্মটি ব্যবহার var='$var2'করতে পারেন তা হ'ল এবং আপনি এটি দ্বিগুণ-ডিফারেন্স করতে চান - তবে আপনাকে বাশে এমন কিছু করার চেষ্টা করা উচিত নয়। আপনি যদি সত্যিই আবশ্যক, আপনি ব্যবহার করতে পারেন export "${!var}"="$val"
জেনেক্সার

1
@ নানসানে: আপনার ধরুন, x="echo hello world";তারপরে যা রয়েছে তা কার্যকর xকরতে আমরা ব্যবহার করতে পারি eval $xতবে, $($x)ভুল, তাই না? হ্যাঁ: $($x)এটি ভুল কারণ এটি চালিত হয় echo hello worldএবং তারপরে ক্যাপচার আউটপুটটি চালানোর চেষ্টা করে (কমপক্ষে এমন প্রসঙ্গে যেখানে আমি মনে করি আপনি এটি ব্যবহার করছেন), যা ব্যর্থ হবে যদি না আপনি helloচারপাশে লাথি মারার প্রোগ্রাম পান ।
জোনাথন লেফলার

1
@tmow আহ, যাতে আপনি প্রকৃতপক্ষে কার্যকর কার্যকারিতা চান। যদি আপনি এটি চান তবে আপনি ইওল ব্যবহার করতে পারেন; এটি মনে রাখবেন যে এতে প্রচুর পরিমাণে সুরক্ষা রয়েছে। এটি আপনার লক্ষ্যে একটি ডিজাইনের ত্রুটি রয়েছে তাও একটি লক্ষণ।
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 মন্দ নয় - এটি কেবল ভুল বোঝা হয়েছে :)


"টোকেন_কোট" ফাংশন কীভাবে তার যুক্তি ব্যবহার করে? আমি এই বৈশিষ্ট্যে কোনও ডকুমেন্টেশন পাচ্ছি না ...
আকিতো


আমার ধারণা আমি এটি খুব অস্পষ্টভাবে বলেছি। আমি ফাংশন আর্গুমেন্ট মানে। কেন নেই arg="$1"? লুপটি কীভাবে জানতে পারে যে কোন আর্গুমেন্টটি ফাংশনে পাস হয়েছিল?
আকিতো

আমি কেবল "ভুল বোঝাবুঝি" না করে আরও এগিয়ে যাব, এটি প্রায়শই অপব্যবহারও হয় এবং সত্যই প্রয়োজন হয় না। জেনেক্সারের উত্তরে এরকম অনেকগুলি কেস কভার করা থাকে তবে যে কোনও ব্যবহারের জন্য evalএকটি লাল পতাকা হওয়া উচিত এবং এটি নিশ্চিত করার জন্য নিখুঁতভাবে পরীক্ষা করা উচিত যে ভাষা ইতিমধ্যে সরবরাহ করা আরও ভাল বিকল্প নয়।
dimo414
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.