`सेट -u` के साथ खाली सरणी विस्तार बैश


103

मैं एक बैश स्क्रिप्ट लिख रहा हूं set -u, जिसमें मुझे खाली सरणी विस्तार की समस्या है: bash विस्तार के दौरान एक खाली चर के रूप में एक खाली सरणी के इलाज के लिए प्रकट होता है:

$ set -u
$ arr=()
$ echo "foo: '${arr[@]}'"
bash: arr[@]: unbound variable

( declare -a arrया तो मदद नहीं करता है।)

इसके लिए एक सामान्य समाधान ${arr[@]-}इसके बजाय उपयोग करना है, इस प्रकार ("अपरिभाषित") खाली सरणी के बजाय एक रिक्त स्ट्रिंग को प्रतिस्थापित करना है। हालाँकि यह एक अच्छा समाधान नहीं है, क्योंकि अब आप एक सरणी के बीच में एक खाली स्ट्रिंग और एक खाली सरणी के बीच विचार नहीं कर सकते। @ (@ विस्तार बैश में विशेष है, यह विस्तार "${arr[@]}"करता है "${arr[0]}" "${arr[1]}" …, जो इसे कमांड लाइन बनाने के लिए एक आदर्श उपकरण बनाता है।)

$ countArgs() { echo $#; }
$ countArgs a b c
3
$ countArgs
0
$ countArgs ""
1
$ brr=("")
$ countArgs "${brr[@]}"
1
$ countArgs "${arr[@]-}"
1
$ countArgs "${arr[@]}"
bash: arr[@]: unbound variable
$ set +u
$ countArgs "${arr[@]}"
0

तो क्या उस समस्या के बारे में एक तरीका है, एक सरणी में लंबाई की जाँच के अलावा if(नीचे कोड नमूना देखें), या -uउस छोटे टुकड़े के लिए सेटिंग बंद करना?

if [ "${#arr[@]}" = 0 ]; then
   veryLongCommandLine
else
   veryLongCommandLine "${arr[@]}"
fi

अपडेट:bugs ikegami द्वारा स्पष्टीकरण के कारण हटाए गए टैग।

जवाबों:


17

केवल सुरक्षित मुहावरा है${arr[@]+"${arr[@]}"}

यह पहले से ही ikegami के जवाब में सिफारिश है , लेकिन इस थ्रेड में बहुत गलत सूचना और अनुमान है। अन्य पैटर्न, जैसे कि ${arr[@]-}या ${arr[@]:0}, बैश के सभी प्रमुख संस्करणों में सुरक्षित नहीं हैं ।

जैसा कि नीचे दी गई तालिका से पता चलता है, एकमात्र विस्तार जो सभी आधुनिक-ईश बैश संस्करणों में विश्वसनीय है ${arr[@]+"${arr[@]}"}(स्तंभ +") है। ध्यान दें, बाश 4.2 में कई अन्य विस्तार विफल होते हैं, (दुर्भाग्य से) छोटी ${arr[@]:0}मुहावरे सहित , जो सिर्फ एक गलत परिणाम नहीं देता है, लेकिन वास्तव में विफल रहता है। यदि आपको 4.4 से पहले के संस्करणों का समर्थन करने की आवश्यकता है, और विशेष रूप से 4.2 में, यह एकमात्र काम करने वाला मुहावरा है।

संस्करणों में विभिन्न मुहावरों का स्क्रीनशॉट

दुर्भाग्य से अन्य +विस्तार, जो एक नज़र में, समान दिखते हैं वास्तव में विभिन्न व्यवहार का उत्सर्जन करते हैं। :+विस्तार सुरक्षित नहीं है , क्योंकि :-एक्सपेंशन एक खाली तत्व ( ('')) को "अशक्त" के रूप में एक सरणी मानता है और इस तरह एक ही परिणाम के लिए (लगातार) विस्तार नहीं करता है।

नेस्टेड सरणी ( "${arr[@]+${arr[@]}}") के बजाय पूर्ण विस्तार का हवाला देते हुए , जिसकी मुझे लगभग बराबर होने की उम्मीद थी, 4.2 में समान रूप से असुरक्षित है।

आप इस जिस्ट में कई अतिरिक्त संस्करण के लिए परिणाम के साथ इस डेटा को उत्पन्न करने वाले कोड को देख सकते हैं ।


1
मैं आपको परीक्षण नहीं देखता "${arr[@]}"। क्या मैं कुछ भूल रहा हूँ? मैं जो देख सकता हूं, वह कम से कम काम करता है 5.x
एक्स-यूरी

1
@ एक्स-यूरी हाँ, बैश 4.4 ने स्थिति तय की; यदि आप जानते हैं कि आपकी स्क्रिप्ट केवल 4.4+ पर ही चलेगी, लेकिन इस प्रणाली का उपयोग करने की आवश्यकता नहीं है, लेकिन कई प्रणालियाँ अभी भी पुराने संस्करणों पर हैं।
dimo414

पूर्ण रूप से। अच्छा दिखने के बावजूद (जैसे प्रारूपण), अतिरिक्त-स्थान बैश की महान बुराई हैं, जिससे बहुत सारी परेशानियाँ होती हैं
agg3l

81

प्रलेखन के अनुसार,

यदि किसी सबस्क्रिप्ट को मान असाइन किया गया है, तो एक सरणी चर सेट किया जाता है। अशक्त स्ट्रिंग एक मान्य मान है।

किसी भी सबस्क्रिप्ट को मान नहीं दिया गया है, इसलिए सरणी सेट नहीं है।

लेकिन जब प्रलेखन यह बताता है कि एक त्रुटि यहाँ उपयुक्त है, यह 4.4 के बाद की स्थिति नहीं है ।

$ bash --version | head -n 1
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)

$ set -u

$ arr=()

$ echo "foo: '${arr[@]}'"
foo: ''

एक सशर्त है जिसे आप पुराने संस्करणों में जो चाहते हैं उसे प्राप्त करने के लिए इनलाइन का उपयोग कर सकते हैं: ${arr[@]+"${arr[@]}"}इसके बजाय का उपयोग करें "${arr[@]}"

$ function args { perl -E'say 0+@ARGV; say "$_: $ARGV[$_]" for 0..$#ARGV' -- "$@" ; }

$ set -u

$ arr=()

$ args "${arr[@]}"
-bash: arr[@]: unbound variable

$ args ${arr[@]+"${arr[@]}"}
0

$ arr=("")

$ args ${arr[@]+"${arr[@]}"}
1
0: 

$ arr=(a b c)

$ args ${arr[@]+"${arr[@]}"}
3
0: a
1: b
2: c

बैश 4.2.25 और 4.3.11 के साथ परीक्षण किया गया।


4
क्या कोई समझा सकता है कि यह कैसे और क्यों काम करता है? मैं इस बारे में उलझन में हूं कि [@]+वास्तव में क्या करता है और क्यों दूसरा ${arr[@]}एक अनबाउंड त्रुटि का कारण नहीं होगा।
मार्टिन वॉन विटिच

2
${parameter+word}केवल तभी फैलता है wordजब parameterकोई परेशान न हो।
इकेगामी

2
${arr+"${arr[@]}"}कम है और बस के रूप में अच्छी तरह से काम करने लगता है।
प्रति सेडेरबर्ग

3
@Per Cerderberg, काम नहीं करता है। unset arr, arr[1]=a, args ${arr+"${arr[@]}"}बनामargs ${arr[@]+"${arr[@]}"}
Ikegami

1
सटीक होने के लिए, उन मामलों में जहां +विस्तार नहीं होता है (अर्थात्, एक खाली सरणी) विस्तार को कुछ भी नहीं के साथ बदल दिया जाता है , जो कि एक खाली सरणी का विस्तार होता है। :+असुरक्षित है, क्योंकि यह एकल-तत्व ('')सरणी को भी परेशान करता है और समान रूप से कुछ भी नहीं फैलता है, मान खोता है।
dimo414

23

@ ikegami का स्वीकृत उत्तर बिलकुल गलत है! सही झुकाव है ${arr[@]+"${arr[@]}"}:

$ countArgs () { echo "$#"; }
$ arr=('')
$ countArgs "${arr[@]:+${arr[@]}}"
0   # WRONG
$ countArgs ${arr[@]+"${arr[@]}"}
1   # RIGHT
$ arr=()
$ countArgs ${arr[@]+"${arr[@]}"}
0   # Let's make sure it still works for the other case...

अब कोई फर्क नहीं पड़ता। bash-4.4.23: arr=('') && countArgs "${arr[@]:+${arr[@]}}"पैदा करता है 1। लेकिन ${arr[@]+"${arr[@]}"}फ़ॉर्म कोलन जोड़कर / न करके खाली / गैर-खाली मान के बीच अंतर करने की अनुमति देता है।
x-yuri

arr=('') && countArgs ${arr[@]:+"${arr[@]}"}-> 0, arr=('') && countArgs ${arr[@]+"${arr[@]}"}-> 1
x-yuri

1
यह मेरे उत्तर में बहुत पहले तय हो चुका है। (वास्तव में, मुझे यकीन है कि मैंने पहले इस आशय के उत्तर पर एक टिप्पणी छोड़ दी है!)
ikegami

16

हाल ही में जारी (2016/09/16) बैश 4.4 (उदाहरण के लिए डेबियन स्ट्रेच में उपलब्ध) में टर्न आउट सरणी हैंडलिंग को बदल दिया गया है।

$ bash --version | head -n1
bash --version | head -n1
GNU bash, version 4.4.0(1)-release (x86_64-pc-linux-gnu)

अब खाली सरणियों का विस्तार चेतावनी का उत्सर्जन नहीं करता है

$ set -u
$ arr=()
$ echo "${arr[@]}"

$ # everything is fine

मैं पुष्टि कर सकता हूं, bash-4.4.12 "${arr[@]}"पर्याप्त होगा।
x-yuri

14

यह उन लोगों के लिए एक और विकल्प हो सकता है जो गिरफ्तारी नहीं करना चाहते [@] और खाली स्ट्रिंग रखना ठीक है

echo "foo: '${arr[@]:-}'"

मापना:

set -u
arr=()
echo a "${arr[@]:-}" b # note two spaces between a and b
for f in a "${arr[@]:-}" b; do echo $f; done # note blank line between a and b
arr=(1 2)
echo a "${arr[@]:-}" b
for f in a "${arr[@]:-}" b; do echo $f; done

10
यदि आप केवल चर को प्रक्षेपित कर रहे हैं तो यह काम करेगा, लेकिन अगर आप इसमें सरणी का उपयोग करना चाहते हैं, तो forएक खाली स्ट्रिंग के साथ समाप्त हो जाएगा, जब सरणी अपरिभाषित / परिभाषित-जैसा-खाली हो, जहां आप लूप बॉडी चाहते हैं। सरणी परिभाषित नहीं है, तो चलाने के लिए नहीं।
ऐश बर्लिन-टेलर

धन्यवाद @AshBerlin, मैंने अपने जवाब में लूप के लिए जोड़ा ताकि पाठकों को पता चले
Jayen

इस दृष्टिकोण के लिए -1, यह बस गलत है। यह एक खाली सरणी को एकल रिक्त-स्ट्रिंग के साथ बदल देता है, जो समान नहीं है। स्वीकार किए गए उत्तर में सुझाए गए पैटर्न ${arr[@]+"${arr[@]}"}, खाली-खाली स्थिति को सही ढंग से संरक्षित करते हैं।
dimo414

उन स्थितियों को दिखाने वाला मेरा उत्तर भी देखें जहां यह विस्तार टूटता है।
dimo414

यह गलत नहीं है। यह स्पष्ट रूप से कहता है कि यह एक खाली स्ट्रिंग देगा, और दो उदाहरण भी हैं जहाँ आप खाली स्ट्रिंग देख सकते हैं।
शाम

7

@ ikegami का उत्तर सही है, लेकिन मैं वाक्य रचना को ${arr[@]+"${arr[@]}"}भयानक मानता हूं । यदि आप लंबे सरणी चर नामों का उपयोग करते हैं, तो यह सामान्य से तेज स्पेगेटी-ईश लग रहा है।

इसके बजाय यह प्रयास करें:

$ set -u

$ count() { echo $# ; } ; count x y z
3

$ count() { echo $# ; } ; arr=() ; count "${arr[@]}"
-bash: abc[@]: unbound variable

$ count() { echo $# ; } ; arr=() ; count "${arr[@]:0}"
0

$ count() { echo $# ; } ; arr=(x y z) ; count "${arr[@]:0}"
3

ऐसा लगता है कि बैश ऐरे स्लाइस ऑपरेटर बहुत क्षमाशील है।

तो क्यों बैश ने सरणियों के किनारे मामले को संभालना इतना मुश्किल बना दिया? आह। मैं आपको गारंटी नहीं दे सकता कि संस्करण सरणी स्लाइस ऑपरेटर के ऐसे दुरुपयोग की अनुमति देगा, लेकिन यह मेरे लिए काम करता है।

कैविएट: मैं उपयोग कर रहा हूँ GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu) आपका माइलेज भिन्न हो सकता है।


9
ikegami मूल रूप से यह था, लेकिन इसे हटा दिया क्योंकि यह अविश्वसनीय है, दोनों सिद्धांत में (कोई कारण नहीं है कि यह काम करना चाहिए) और व्यवहार में (ओपी के बैश के संस्करण ने इसे स्वीकार नहीं किया)।

@hvd: अपडेट के लिए धन्यवाद। पाठक: कृपया टिप्पणी जोड़ें यदि आपको बैश के संस्करण मिलते हैं जहां उपरोक्त कोड काम नहीं करता है।
केविनपारे

hvp पहले से ही किया था, और मैं आपको भी बताऊंगा: "${arr[@]:0}"देता है -bash: arr[@]: unbound variable
इकेगामी

एक चीज जो संस्करणों में काम करनी चाहिए, वह है एक डिफ़ॉल्ट सरणी मान को सेट करना arr=("_dummy_"), और ${arr[@]:1}हर जगह विस्तार का उपयोग करना । प्रहरी मूल्यों का जिक्र करते हुए अन्य उत्तरों में इसका उल्लेख किया गया है।
init_js

1
@init_js: आपका संपादन दुखद रूप से अस्वीकार कर दिया गया था। मेरा सुझाव है कि आप एक अलग उत्तर के रूप में जोड़ें। (रेफरी: stackoverflow.com/review/suggested-edits/19027379 )
kevinarpe

6

"दिलचस्प" असंगति वास्तव में।

इसके अलावा,

$ set -u
$ echo $#
0
$ echo "$1"
bash: $1: unbound variable   # makes sense (I didn't set any)
$ echo "$@" | cat -e
$                            # blank line, no error

हालांकि मैं मानता हूं कि वर्तमान व्यवहार इस मायने में बग नहीं हो सकता है कि @ikegami बताते हैं, IMO हम कह सकते हैं कि बग स्वयं ("सेट") की परिभाषा में है, और / या तथ्य यह है कि यह असंगत रूप से लागू होता है। मैन पेज में पूर्ववर्ती पैराग्राफ कहता है

... ${name[@]}नाम के प्रत्येक तत्व को एक अलग शब्द में विस्तारित करता है। जब कोई सरणी सदस्य नहीं होते हैं, तो ${name[@]}कुछ भी नहीं फैलता है।

यह पूरी तरह से संगत है जो इसमें स्थितीय मापदंडों के विस्तार के बारे में कहता है "$@" । ऐसा नहीं है कि सरणियों और स्थितीय मापदंडों के व्यवहार में अन्य विसंगतियां नहीं हैं ... लेकिन मेरे लिए कोई संकेत नहीं है कि यह विवरण दोनों के बीच असंगत होना चाहिए।

जारी रखते हुए,

$ arr=()
$ echo "${arr[@]}"
bash: arr[@]: unbound variable   # as we've observed.  BUT...
$ echo "${#arr[@]}"
0                                # no error
$ echo "${!arr[@]}" | cat -e
$                                # no error

इसलिए इतना अनबाउंड arr[]नहीं है कि हम इसके तत्वों (0), या इसकी कुंजियों की एक (खाली) सूची की गिनती प्राप्त नहीं कर सकते हैं? मेरे लिए ये समझदार हैं, और उपयोगी हैं - केवल बाहरी रूप से विस्तार ${arr[@]}(और ${arr[*]}) लगता है ।


2

मैं @ ikegami's (स्वीकृत) और @ kevinarpe's (भी अच्छा) के उत्तर पर पूरक हूं ।

आप "${arr[@]:+${arr[@]}}"समस्या को हल करने के लिए कर सकते हैं । दाएं हाथ-साइड (यानी, बाद :+) एक अभिव्यक्ति प्रदान करता है जिसका उपयोग बाएं-हाथ-पक्ष को परिभाषित / शून्य नहीं होने की स्थिति में किया जाएगा।

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

: example copy arr into arr_copy
arr=( "1 2" "3" )
arr_copy=( "${arr[@]:+${arr[@]}}" ) # good. same quoting. 
                                    # preserves spaces

arr_copy=( ${arr[@]:+"${arr[@]}"} ) # bad. quoting only on RHS.
                                    # copy will have ["1","2","3"],
                                    # instead of ["1 2", "3"]

@Kevinarpe उल्लेखों की तरह, एक कम आर्कन सिंटैक्स सरणी टुकड़ा संकेतन ${arr[@]:0}(बैश संस्करणों पर >= 4.4) का उपयोग करने के लिए है , जो सभी मापदंडों का विस्तार करता है, जो सूचकांक 0. से शुरू होता है। इसे भी अधिक पुनरावृत्ति की आवश्यकता नहीं होती है। यह विस्तार बिना परवाह किए काम करता है set -u, इसलिए आप हर समय इसका उपयोग कर सकते हैं। मैन पेज कहता है ( पैरामीटर विस्तार के तहत ):

  • ${parameter:offset}

  • ${parameter:offset:length}

    ... यदि पैरामीटर एक अनुक्रमित सरणी नाम है जिसे @या तो सब्स्क्राइब्ड किया गया है *, तो परिणाम आरम्भ की लंबाई के सदस्यों के साथ है ${parameter[offset]}। एक नकारात्मक ऑफसेट को निर्दिष्ट सरणी के अधिकतम सूचकांक से एक से अधिक लिया जाता है। यह एक विस्तार त्रुटि है यदि लंबाई शून्य से कम संख्या का मूल्यांकन करती है।

यह @kevinarpe द्वारा प्रदान किया गया उदाहरण है, आउटपुट में साक्ष्य प्रस्तुत करने के लिए वैकल्पिक स्वरूपण के साथ:

set -u
function count() { echo $# ; };
(
    count x y z
)
: prints "3"

(
    arr=()
    count "${arr[@]}"
)
: prints "-bash: arr[@]: unbound variable"

(
    arr=()
    count "${arr[@]:0}"
)
: prints "0"

(
    arr=(x y z)
    count "${arr[@]:0}"
)
: prints "3"

यह व्यवहार बैश के संस्करणों के साथ बदलता रहता है। आपने यह भी देखा होगा कि लंबाई ऑपरेटर ${#arr[@]}हमेशा बिना किसी 'अनबाउंड वैरिएबल एरर' के कारण, 0खाली एरर्स के लिए मूल्यांकन करेगा set -u


दुर्भाग्य से :0बैश 4.2 में मुहावरा विफल रहता है, इसलिए यह एक सुरक्षित तरीका नहीं है। मेरा जवाब देखिए ।
dimo414

1

इस तरह से कुछ करने के तरीके यहां दिए गए हैं, एक प्रहरी का उपयोग कर रहा है और दूसरा सशर्त एपेंड का उपयोग कर रहा है:

#!/bin/bash
set -o nounset -o errexit -o pipefail
countArgs () { echo "$#"; }

arrA=( sentinel )
arrB=( sentinel "{1..5}" "./*" "with spaces" )
arrC=( sentinel '$PWD' )
cmnd=( countArgs "${arrA[@]:1}" "${arrB[@]:1}" "${arrC[@]:1}" )
echo "${cmnd[@]}"
"${cmnd[@]}"

arrA=( )
arrB=( "{1..5}" "./*"  "with spaces" )
arrC=( '$PWD' )
cmnd=( countArgs )
# Checks expansion of indices.
[[ ! ${!arrA[@]} ]] || cmnd+=( "${arrA[@]}" )
[[ ! ${!arrB[@]} ]] || cmnd+=( "${arrB[@]}" )
[[ ! ${!arrC[@]} ]] || cmnd+=( "${arrC[@]}" )
echo "${cmnd[@]}"
"${cmnd[@]}"

0

दिलचस्प असंगति; यह आपको कुछ ऐसा परिभाषित करता है जो "सेट नहीं माना जाता है" अभी तक के आउटपुट में दिखाता हैdeclare -p

arr=()
set -o nounset
echo ${arr[@]}
 =>  -bash: arr[@]: unbound variable
declare -p arr
 =>  declare -a arr='()'

अद्यतन: जैसा कि दूसरों ने उल्लेख किया है, इस उत्तर के पोस्ट किए जाने के बाद जारी 4.4 में तय किया गया है।


यह सिर्फ गलत सरणी सिंटैक्स है; आपको जरूरत है echo ${arr[@]}(लेकिन बश 4.4 से पहले आप अभी भी एक त्रुटि देखेंगे)।
dimo414

धन्यवाद @ dimo414, अगली बार डाउनवोटिंग के बजाय एक संपादन का सुझाव दें। BTW यदि आपने स्वयं प्रयास किया echo $arr[@]होता तो आपने देखा होता कि त्रुटि संदेश अलग है।
मार्ख

-2

सबसे सरल और संगत तरीका लगता है:

$ set -u
$ arr=()
$ echo "foo: '${arr[@]-}'"

1
ओपी ने खुद दिखाया कि यह काम नहीं करता है। यह कुछ नहीं के बजाय एक खाली स्ट्रिंग में फैलता है।
इकेगामी

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