बैश में गतिशील चर नाम


159

मैं बैश स्क्रिप्ट को लेकर उलझन में हूं।

मेरे पास निम्नलिखित कोड हैं:

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

मैं एक चर नाम बनाना चाहता हूं जिसमें कमांड का पहला तर्क हो और जैसे कि अंतिम पंक्ति का मान हो ls

इसलिए मैं जो चाहता हूं उसे स्पष्ट करने के लिए:

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

तो, मुझे कैसे परिभाषित / घोषित $magic_way_to_define_magic_variable_$1करना चाहिए और मुझे इसे स्क्रिप्ट के भीतर कैसे कॉल करना चाहिए?

मैं कोशिश की है eval, ${...}, \$${...}, लेकिन मैं अभी भी उलझन में हूँ।


3
मत करो। डेटा के लिए कमांड नाम को मैप करने के लिए एक साहचर्य सरणी का उपयोग करें।
chepner

3
वीएआर = एक; VAL = 333; "$ VAR" <<< "$ VAL" पढ़ें; इको "ए = $ ए"
ग्रिगोरी के

जवाबों:


150

कुंजी के रूप में कमांड नामों के साथ एक साहचर्य सरणी का उपयोग करें।

# Requires bash 4, though
declare -A magic_variable=()

function grep_search() {
    magic_variable[$1]=$( ls | tail -1 )
    echo ${magic_variable[$1]}
}

यदि आप सहयोगी सरणियों का उपयोग नहीं कर सकते हैं (उदाहरण के लिए, आपको bash3 का समर्थन करना चाहिए ), तो आप declareगतिशील चर नाम बनाने के लिए उपयोग कर सकते हैं :

declare "magic_variable_$1=$(ls | tail -1)"

और मान तक पहुँचने के लिए अप्रत्यक्ष पैरामीटर विस्तार का उपयोग करें।

var="magic_variable_$1"
echo "${!var}"

बशफा देखें: अप्रत्यक्ष - अप्रत्यक्ष / संदर्भ चर का मूल्यांकन


5
@DeaDEnD -aएक अनुक्रमित सरणी घोषित करता है, न कि एक सहयोगी सरणी। जब तक तर्क grep_searchएक संख्या नहीं है, तब तक इसे एक संख्यात्मक मान के साथ एक पैरामीटर के रूप में माना जाएगा (जो कि पैरामीटर खो जाने पर 0 से चूक जाता है)।
चेपनर

1
हम्म। मैं बैश का उपयोग कर रहा हूं 4.2.45(2)और घोषित करता हूं कि यह एक विकल्प के रूप में सूचीबद्ध नहीं है declare: usage: declare [-afFirtx] [-p] [name[=value] ...]। हालांकि यह सही ढंग से काम कर रहा है।
fent

declare -hमेरे शो में 4.2.45 (2) के लिए declare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]। आप यह देख सकते हैं कि आप वास्तव में 4.x और 3.2 नहीं चला रहे हैं।
चेपनर

5
सिर्फ क्यों नहीं declare $varname="foo"?
बेन डेविस

1
${!varname}बहुत सरल और व्यापक रूप से संगत है
ब्रैड हेन

227

मैं हाल ही में इसे करने के बेहतर तरीके की तलाश कर रहा हूं। सहयोगी सरणी मेरे लिए ओवरकिल की तरह लग रही थी। देखो मुझे क्या मिला:

suffix=bzz
declare prefix_$suffix=mystr

...और फिर...

varname=prefix_$suffix
echo ${!varname}

यदि किसी फ़ंक्शन के अंदर एक वैश्विक घोषित करना चाहते हैं, तो bash> = 4.2 में "डिक्लेयर-जी" का उपयोग कर सकते हैं। पहले के बैश में, "डिक्लेयर" के बजाय "आसानी से" का उपयोग कर सकते हैं, इसलिए जब तक आप बाद में मूल्य को बदलना नहीं चाहते। कॉन्फ़िगरेशन के लिए या आपके पास क्या हो सकता है।
सैम वाटकिंस

7
एन्कैप्सुलेटेड वैरिएबल फॉर्मेट का उपयोग करना सबसे अच्छा: prefix_${middle}_postfix(यानी आपकी फॉर्मेटिंग काम नहीं करेगी varname=$prefix_suffix)
msciwoj

1
मैं बैश 3 के साथ फंस गया था और साहचर्य सरणियों का उपयोग नहीं कर सकता था; जैसा कि यह एक जीवन रक्षक था। $ {...} उस पर Google के लिए आसान नहीं है। मुझे लगता है कि यह सिर्फ एक var नाम का विस्तार करता है।
नील मैकगिल

10
@NeilMcGill: "man bash" gnu.org/software/bash/manual/html_node/… देखें : पैरामीटर विस्तार का मूल रूप $ {पैरामीटर} है। <...> यदि पैरामीटर का पहला वर्ण विस्मयादिबोधक बिंदु (!) है, तो चर अप्रत्यक्ष का एक स्तर पेश किया जाता है। बैश बाकी पैरामीटर से बने चर के मूल्य का उपयोग करता है, चर के नाम के रूप में; इस चर का फिर विस्तार किया जाता है और उस मूल्य का उपयोग बाकी सबस्टेशन में किया जाता है, बजाय पैरामीटर के मूल्य के।
योरिक .सर

1
@syntaxerror: आप मानों को असाइन कर सकते हैं जितना आप ऊपर "घोषित" कमांड के साथ चाहते हैं।
योरिक .सर

48

सहयोगी सरणियों से परे, बैश में गतिशील चर प्राप्त करने के कई तरीके हैं। ध्यान दें कि ये सभी तकनीकें जोखिम को प्रस्तुत करती हैं, जिन पर इस उत्तर के अंत में चर्चा की जाती है।

निम्नलिखित उदाहरणों में मैं मान लूंगा i=37कि आप उस चर का नाम देना चाहते हैं var_37जिसका प्रारंभिक मूल्य है lolilol

विधि 1. एक "पॉइंटर" चर का उपयोग करना

आप केवल एक अप्रत्यक्ष चर में चर का नाम स्टोर कर सकते हैं, सी पॉइंटर के विपरीत नहीं। बैश तब अलियास किए गए चर को पढ़ने के लिए एक सिंटैक्स है : चर ${!name}के मूल्य तक फैलता है जिसका नाम चर का मूल्य है name। आप इसे दो-चरणीय विस्तार के रूप में सोच सकते हैं: ${!name}विस्तार करता है $var_37, जिसका विस्तार होता है lolilol

name="var_$i"
echo "$name"         # outputs “var_37”
echo "${!name}"      # outputs “lolilol”
echo "${!name%lol}"  # outputs “loli”
# etc.

दुर्भाग्य से, उपनामित चर को संशोधित करने के लिए कोई समकक्ष वाक्यविन्यास नहीं है । इसके बजाय, आप निम्न में से किसी एक ट्रिक से असाइनमेंट प्राप्त कर सकते हैं।

1 क। साथ सौंपनाeval

evalबुराई है, लेकिन हमारे लक्ष्य को प्राप्त करने का सबसे सरल और सबसे पोर्टेबल तरीका भी है। आपको असाइनमेंट के दाईं ओर ध्यान से बचना होगा, क्योंकि इसका मूल्यांकन दो बार किया जाएगा । ऐसा करने का एक आसान और व्यवस्थित तरीका पहले से दाहिने हाथ की ओर (या उपयोग करने के लिए printf %q) का मूल्यांकन करना है ।

और आपको मैन्युअल रूप से जांच करनी चाहिए कि बाएं हाथ की ओर एक वैध चर नाम है, या सूचकांक के साथ एक नाम (यदि यह था तो क्या है evil_code #?)। इसके विपरीत, नीचे दिए गए अन्य सभी तरीके इसे स्वचालित रूप से लागू करते हैं।

# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit

value='babibab'
eval "$name"='$value'  # carefully escape the right-hand side!
echo "$var_37"  # outputs “babibab”

downsides:

  • चर नाम की वैधता की जाँच नहीं करता है।
  • eval बुराई है।
  • eval बुराई है।
  • eval बुराई है।

1b। साथ सौंपनाread

readBuiltin आप एक चर जिनमें से आप नाम, एक तथ्य है जो यहाँ-तार के साथ संयोजन के रूप में इस्तेमाल किया जा सकता देने के लिए मान निर्दिष्ट कर सकते हैं:

IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37"  # outputs “babibab\n”

IFSहिस्सा है और विकल्प-r लगता है कि मूल्य के रूप में-है असाइन किया गया है, जबकि विकल्प बनाने के -d ''बहु लाइन मान निर्दिष्ट कर सकते हैं। इस अंतिम विकल्प के कारण, कमांड एक गैर-शून्य निकास कोड के साथ लौटता है।

ध्यान दें, क्योंकि हम यहाँ एक स्ट्रिंग का उपयोग कर रहे हैं, एक नया वर्ण मान में जोड़ा जाता है।

downsides:

  • कुछ अस्पष्ट है;
  • एक गैर-शून्य निकास कोड के साथ रिटर्न;
  • मान के लिए एक नई रेखा जोड़ता है।

1c। साथ सौंपनाprintf

बाश 3.1 (2005 जारी) के बाद से, printfबिल्डिन अपना परिणाम एक चर को भी दे सकता है जिसका नाम दिया गया है। पिछले समाधानों के विपरीत, यह सिर्फ काम करता है, चीजों को भागने, विभाजन को रोकने और इतने पर अतिरिक्त प्रयास की आवश्यकता नहीं है।

printf -v "$name" '%s' 'babibab'
echo "$var_37"  # outputs “babibab”

downsides:

  • कम पोर्टेबल (लेकिन, अच्छी तरह से)।

विधि 2. एक "संदर्भ" चर का उपयोग करना

बैश 4.3 (2014 में रिलीज़) के बाद से, declareबिल्डिन के पास -nएक वैरिएबल बनाने के लिए एक विकल्प होता है , जो कि दूसरे वेरिएबल के लिए "नाम संदर्भ" होता है, बहुत कुछ जैसे C ++ संदर्भ। जैसे विधि 1 में, संदर्भ अलियास किए गए चर के नाम को संग्रहीत करता है, लेकिन हर बार संदर्भ एक्सेस किया जाता है (या तो पढ़ने या असाइन करने के लिए), बैश स्वचालित रूप से अप्रत्यक्ष हल करता है।

इसके अलावा, बैश के पास संदर्भ के मूल्य को प्राप्त करने के लिए एक विशेष और बहुत ही भ्रामक वाक्यविन्यास है, अपने आप से न्यायाधीश ${!ref}:।

declare -n ref="var_$i"
echo "${!ref}"  # outputs “var_37”
echo "$ref"     # outputs “lolilol”
ref='babibab'
echo "$var_37"  # outputs “babibab”

यह नीचे बताए गए नुकसान से नहीं बचता है, लेकिन कम से कम यह वाक्यविन्यास को सीधा बनाता है।

downsides:

  • पोर्टेबल नहीं है।

जोखिम

इन सभी अलियासिंग तकनीकों में कई जोखिम हैं। हर बार जब आप अप्रत्यक्ष को हल करते हैं (या तो पढ़ने या असाइन करने के लिए) तो पहले वाला मनमाना कोड निष्पादित कर रहा है । वास्तव में, एक स्केलर वैरिएबल नाम के बजाय, जैसे var_37, आप एलीस एरेस सबस्क्रिप्ट, जैसे हो सकते हैं arr[42]। लेकिन बैश हर बार जरूरत पड़ने पर वर्ग कोष्ठक की सामग्री का मूल्यांकन करता है, इसलिए एलियासिंग arr[$(do_evil)]पर अप्रत्याशित प्रभाव पड़ेगा ... परिणामस्वरूप, इन तकनीकों का उपयोग केवल तब करें जब आप उपनाम की सिद्धता को नियंत्रित करते हैं

function guillemots() {
  declare -n var="$1"
  var="«${var}»"
}

arr=( aaa bbb ccc )
guillemots 'arr[1]'  # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]'  # writes twice into date.out
            # (once when expanding var, once when assigning to it)

दूसरा जोखिम चक्रीय उर्फ ​​पैदा कर रहा है। जैसा कि बैश चर उनके नाम से पहचाने जाते हैं और उनके दायरे से नहीं, आप अनजाने में खुद के लिए एक उपनाम बना सकते हैं (यह सोचते हुए कि यह एक घेरने के दायरे से एक चर को उर्फ ​​कर देगा)। यह विशेष रूप से सामान्य चर नाम (जैसे var) का उपयोग करते समय हो सकता है । परिणामस्वरूप, इन तकनीकों का उपयोग केवल तब करें जब आप उपनाम वाले चर के नाम को नियंत्रित करते हैं

function guillemots() {
  # var is intended to be local to the function,
  # aliasing a variable which comes from outside
  declare -n var="$1"
  var="«${var}»"
}

var='lolilol'
guillemots var  # Bash warnings: “var: circular name reference”
echo "$var"     # outputs anything!

स्रोत:


1
यह सबसे अच्छा जवाब है, खासकर जब से ${!varname}तकनीक के लिए एक मध्यवर्ती संस्करण की आवश्यकता होती है varname
रिचवेल

यह समझना कठिन है कि इस उत्तर को उच्चतर नहीं बनाया गया है
मार्कोस

18

$ Name_of_var के रिटर्न वैल्यू के नीचे उदाहरण

var=name_of_var
echo $(eval echo "\$$var")

4
echoकमांड प्रतिस्थापन के साथ दो एस का घोंसला बनाना (जो उद्धरण याद करता है) अनावश्यक है। साथ ही, विकल्प -nदिया जाना चाहिए echo। और, हमेशा की तरह, evalअसुरक्षित है। लेकिन यह सब अनावश्यक है क्योंकि बैश में इस उद्देश्य के लिए एक सुरक्षित, स्पष्ट और छोटा वाक्यविन्यास है ${!var}:।
मैलन

4

यह काम करना चाहिए:

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"

4

यह भी काम करेगा

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

आपके मामले में

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val


3

उपयोग declare

अन्य उत्तरों की तरह उपसर्गों का उपयोग करने की कोई आवश्यकता नहीं है, न ही सरणियाँ। बस declare, दोहरे उद्धरण चिह्नों और पैरामीटर विस्तार का उपयोग करें ।

मैं अक्सर निम्न तरकीबों का उपयोग one to nतर्क- वितर्क सूचियों को पार्स करने के लिए करता हूँ key=value otherkey=othervalue etc=etc, जैसे स्वरूपित तर्क :

# brace expansion just to exemplify
for variable in {one=foo,two=bar,ninja=tip}
do
  declare "${variable%=*}=${variable#*=}"
done
echo $one $two $ninja 
# foo bar tip

लेकिन जैसे argv सूची का विस्तार

for v in "$@"; do declare "${v%=*}=${v#*=}"; done

अतिरिक्त सुझाव

# parse argv's leading key=value parameters
for v in "$@"; do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
done
# consume argv's leading key=value parameters
while (( $# )); do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
  shift
done

1
यह एक बहुत साफ समाधान की तरह दिखता है। कोई दुष्ट बिब और बोब नहीं है और आप ऐसे उपकरणों का उपयोग करते हैं जो चर से संबंधित हैं, अस्पष्ट प्रतीत नहीं होते असंबंधित या यहां तक ​​कि खतरनाक कार्य जैसे कि printfयाeval
kvtour

2

वाह, अधिकांश वाक्यविन्यास भयानक है! यदि आपको अप्रत्यक्ष रूप से संदर्भ सरणियों की आवश्यकता है, तो कुछ सरल सिंटैक्स के साथ एक समाधान यहां दिया गया है:

#!/bin/bash

foo_1=("fff" "ddd") ;
foo_2=("ggg" "ccc") ;

for i in 1 2 ;
do
    eval mine=( \${foo_$i[@]} ) ;
    echo ${mine[@]} ;
done ;

सरल उपयोग मामलों के लिए मैं उन्नत बैश-स्क्रिप्टिंग गाइड में वर्णित वाक्यविन्यास की सलाह देता हूं ।


2
एबीएस अपने उदाहरणों में बुरी प्रथाओं को दिखाने के लिए कुख्यात है। कृपया बैश-हैकर्स विकी या वूलेज विकी पर झुकाव पर विचार करें - जिसके बजाय सीधे ऑन-विषय प्रविष्टि BashFAQ # 6 - है।
चार्ल्स डफी

2
यह केवल तभी कारगर साबित में प्रविष्टियों foo_1और foo_2खाली स्थान के और विशेष प्रतीक का स्वतंत्र हैं। समस्याग्रस्त प्रविष्टियों के उदाहरण: 'a b'अंदर दो प्रविष्टियाँ बनाएंगे mine''अंदर एक प्रविष्टि नहीं बनाएंगे mine'*'कार्यशील निर्देशिका की सामग्री का विस्तार करेगा। आप इन समस्याओं को उद्धृत करके रोक सकते हैं:eval 'mine=( "${foo_'"$i"'[@]}" )'
सोकोवि

@Socowi BASH में किसी भी ऐरे से लूपिंग की सामान्य समस्या है। यह IFS को अस्थायी रूप से बदलकर (और फिर निश्चित रूप से इसे वापस बदलकर) हल किया जा सकता है। यह देखने के लिए अच्छा है कि उद्धृत काम किया गया।
ingyhere

@ मैं कहीं से भीख माँगता हूँ। यह एक सामान्य समस्या नहीं है। एक मानक समाधान है: हमेशा उद्धरण का [@]निर्माण करता है। "${array[@]}"विभाजन की सही सूची के लिए हमेशा विस्तार होगा जैसे शब्द विभाजन या विस्तार *। इसके अलावा, शब्द बंटवारे की समस्या को केवल तब ही दरकिनार किया जा सकता है IFSजब आप किसी भी गैर-अशक्त चरित्र को जानते हैं जो कभी भी सरणी के अंदर प्रकट नहीं होता है। इसके अलावा शाब्दिक उपचार *सेटिंग द्वारा प्राप्त नहीं किया जा सकता है IFS। या तो आप IFS='*'तारों पर सेट और विभाजित होते हैं या आप सेट IFS=somethingOtherऔर *विस्तार करते हैं।
सोकोवई

@Socowi आप मान रहे हैं कि शेल का विस्तार अवांछनीय है, और यह हमेशा ऐसा नहीं होता है। डेवलपर्स बग की शिकायत करते हैं जब सब कुछ उद्धृत करने के बाद शेल अभिव्यक्ति का विस्तार नहीं होता है। एक अच्छा समाधान डेटा को जानना है और लिपियों का उचित रूप से निर्माण करना है, यहां तक कि आईएफएस का उपयोग करके |या के LFरूप में। फिर से, लूप में सामान्य समस्या यह है कि टोकन डिफ़ॉल्ट रूप से होता है ताकि उद्धरण में विस्तारित स्ट्रिंग्स की अनुमति देने के लिए विशेष समाधान होता है जिसमें टोकन होते हैं। (यह या तो ग्लोबिंग / पैरामीटर विस्तार या उद्धृत स्ट्रिंग्स है, लेकिन दोनों नहीं हैं।) यदि किसी संस्करण को पढ़ने के लिए 8 उद्धरण आवश्यक हैं, तो शेल गलत भाषा है।
निगलना

1

अनुक्रमित सरणियों के लिए, आप उन्हें इस तरह से संदर्भित कर सकते हैं:

foo=(a b c)
bar=(d e f)

for arr_var in 'foo' 'bar'; do
    declare -a 'arr=("${'"$arr_var"'[@]}")'
    # do something with $arr
    echo "\$$arr_var contains:"
    for char in "${arr[@]}"; do
        echo "$char"
    done
done

साहचर्य सरणियों को समान रूप से संदर्भित किया जा सकता है, लेकिन इसके बदले -Aस्विच की आवश्यकता होती declareहै -a


1

एक अतिरिक्त विधि जो आपके द्वारा उपयोग किए जाने वाले शेल / बैश संस्करण पर निर्भर नहीं करती है envsubst। उदाहरण के लिए:

newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)

0

मैं एक वैरिएबल नाम बनाना चाहता हूं जिसमें कमांड का पहला तर्क हो

script.sh फ़ाइल:

#!/usr/bin/env bash
function grep_search() {
  eval $1=$(ls | tail -1)
}

परीक्षा:

$ source script.sh
$ grep_search open_box
$ echo $open_box
script.sh

प्रति के रूप में help eval:

एक शेल कमांड के रूप में बहस निष्पादित करें।


आप बैश ${!var}अप्रत्यक्ष विस्तार का भी उपयोग कर सकते हैं , जैसा कि पहले ही उल्लेख किया गया है, हालांकि यह सरणी सूचकांकों को पुनः प्राप्त करने का समर्थन नहीं करता है।


आगे पढ़ने या उदाहरणों के लिए, Indirection के बारे में BashFAQ / 006 की जाँच करें ।

हम ऐसी किसी भी ट्रिक से अवगत नहीं हैं evalजो बिना POSIX या बॉर्न के गोले में उस कार्यक्षमता को डुप्लिकेट कर सकती है, जिसे सुरक्षित रूप से करना मुश्किल हो सकता है। तो, इसे अपने जोखिम हैक पर उपयोग पर विचार करें

हालांकि, आपको निम्नलिखित नोटों के अनुसार अप्रत्यक्ष रूप से उपयोग पर फिर से विचार करना चाहिए।

आम तौर पर, बैश स्क्रिप्टिंग में, आपको अप्रत्यक्ष संदर्भों की आवश्यकता नहीं होगी। आम तौर पर, लोग इसे एक समाधान के लिए देखते हैं जब वे बाश एरे के बारे में नहीं समझते हैं या जानते हैं या फ़ंक्शन के रूप में अन्य बैश सुविधाओं पर पूरी तरह से विचार नहीं करते हैं।

मापदंडों के अंदर परिवर्तनशील नामों या किसी अन्य बैश सिंटैक्स को रखना अक्सर गलत तरीके से और अनुचित परिस्थितियों में उन समस्याओं को हल करने के लिए होता है जिनके बेहतर समाधान होते हैं। यह कोड और डेटा के बीच अलगाव का उल्लंघन करता है, और जैसे कि आप बग और सुरक्षा के मुद्दों पर फिसलन ढलान पर डालते हैं। अप्रत्यक्ष आपके कोड को कम पारदर्शी और पालन करने में कठिन बना सकता है।


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.