एक बार में इसी तरह के बैश फ़ंक्शन को कैसे परिभाषित करें


10

मेरे पास ये कार्य हैं ~/.bashrc:

function guard() {
    if [ -e 'Gemfile' ]; then
    bundle exec guard "$@"
    else
    command guard "$@"
    fi
}
function rspec() {
    if [ -e 'Gemfile' ]; then
    bundle exec rspec "$@"
    else
    command rspec "$@"
    fi
}
function rake() {
    if [ -e 'Gemfile' ]; then
        bundle exec rake "$@"
    else
        command rake "$@"
    fi
}

जैसा कि आप देखते हैं कि ये कार्य बहुत समान हैं। मैं एक बार में इन 3 कार्यों को परिभाषित करना चाहता हूं। क्या इसे बनाने का कोई तरीका है?

वातावरण

bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)

जवाबों:


8
$ cat t.sh
#!/bin/bash

for func in guard rspec rake; do
        eval "
        ${func}() {
                local foo=(command ${func})
                [ -e 'Gemfile' ] && foo=(bundle exec ${func})
                \"\${foo[@]}\" \"\$@\"
        }
        "
done

type guard rspec rake

$ ./t.sh
guard is a function
guard ()
{
    local foo=(command guard);
    [ -e 'Gemfile' ] && foo=(bundle exec guard);
    "${foo[@]}" "$@"
}
rspec is a function
rspec ()
{
    local foo=(command rspec);
    [ -e 'Gemfile' ] && foo=(bundle exec rspec);
    "${foo[@]}" "$@"
}
rake is a function
rake ()
{
    local foo=(command rake);
    [ -e 'Gemfile' ] && foo=(bundle exec rake);
    "${foo[@]}" "$@"
}

evalलागू करने के बारे में सामान्य सावधानी ।


क्या यह for loop?मेरे मतलब से नहीं खाया जाता है, for loopआमतौर पर गायब घोषित किए गए चर - मैं समान कारणों के लिए समान कार्यों की अपेक्षा करता हूं।
मोकेसर

आप ऐसा क्यों सोचते हैं? bash -c 'for i in 1; do :; done; echo $i'=> 1typeस्पष्ट रूप से पता चलता है कार्यों पाश के दायरे से बाहर मौजूद है कि।
एड्रियन फ्रुविर्थ

1
@mikeserv bashगतिशील स्कोपिंग के साथ भी आप सभी प्राप्त कर सकते हैं localएक पूरे समारोह के दायरे के लिए एक चर स्थानीय है , चर निश्चित रूप से एक लूप के बाद "गायब" नहीं होते हैं। वास्तव में, चूंकि इसमें कोई फ़ंक्शन शामिल नहीं है, इसलिए localइस मामले में एक चर को परिभाषित करना भी संभव नहीं है ।
एड्रियन फ्रुविर्थ

लूप के लिए राइट - लोकल - वे स्थानीय रूप से स्कूप हैं। जैसे ही उनके माता-पिता लूप शेल करते हैं, वे गायब हो जाते हैं। क्या यहां ऐसा नहीं होता है?
मिकसेर्व

नहीं, जैसा कि मैंने अभी बताया कि शेल स्क्रिप्टिंग और मेरी पोस्ट में "लोकल फॉर द लूप" जैसी कोई अवधारणा नहीं है और ऊपर दिए गए मेरे कमेंट में इसका उदाहरण स्पष्ट रूप से दिखाई देता है।
एड्रियन फ्रुविर्थ

7
_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."

उपरोक्त . source /dev/fd/3जिसे _gem_dec()हर बार फ़ंक्शन में खिलाया जाता है, इसे पूर्व-मूल्यांकन के रूप में कहा जाता है here-document. _gem_dec'sकेवल नौकरी एक पैरामीटर प्राप्त करना है और इसे bundle execलक्ष्य के रूप में और फ़ंक्शन के नाम के रूप में पूर्व-मूल्यांकन करना है जिसमें इसे लक्षित किया गया है।

NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.

हालांकि उपरोक्त मामले में, मुझे नहीं लगता कि कोई जोखिम हो सकता है।

यदि उपरोक्त कोड-ब्लॉक को एक .bashrcफ़ाइल में कॉपी किया जाता है , तो न केवल शेल फ़ंक्शन होंगे _guard(), _rspec()और_rake() लॉगिन पर घोषित किए जाएंगे, बल्कि _gem_dec()फ़ंक्शन आपके शेल प्रॉम्प्ट (या अन्यथा) पर किसी भी समय निष्पादन के लिए उपलब्ध होगा और इसलिए नए अस्थायी कार्य कर सकते हैं जब भी आपको पसंद हो, केवल घोषित किया जाए:

_gem_dec $new_templated_function_name

और मुझे दिखाने के लिए @Andrew के लिए धन्यवाद, ए द्वारा खाया नहीं जाएगा for loop.

पर कैसे?

मैं आदत के बाहर खुला 3रखने के लिए ऊपर दिए गए फ़ाइल डिस्क्रिप्टर का उपयोग करता हूं stdin, stdout, and stderr, or <&0 >&1 >&2- हालांकि, जैसा कि मैं यहां लागू कुछ अन्य डिफ़ॉल्ट सावधानियों के लिए भी कर रहा हूं - क्योंकि परिणामी फ़ंक्शन इतना सरल है, यह वास्तव में आवश्यक नहीं है। यह अच्छा अभ्यास है, हालांकि। कॉलिंग shift $#उन अनावश्यक सावधानियों में से एक है।

फिर भी, जब एक फ़ाइल के रूप में निर्दिष्ट किया जाता है <inputया>output के साथ [optional num]<fileया [optional num]>fileपुनर्निर्देशन गिरी एक फ़ाइल वर्णनकर्ता, के माध्यम से पहुँचा जा सकता है जो में पढ़ता है character deviceमें विशेष फ़ाइलों /dev/fd/[0-9]*यदि [optional num]विनिर्देशक को छोड़ दिया जाता है, तो 0<fileइनपुट के लिए और 1>fileआउटपुट के लिए मान लिया जाता है । इस पर विचार करो:

l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6

( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2

( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6

( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3

( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6

और क्योंकि here-documentहम कोड-ब्लॉक में फ़ाइल इनलाइन का वर्णन करने का केवल एक साधन है, जब हम करते हैं:

<<'HEREDOC'
[$CODE]
HEREDOC

हम भी कर सकते हैं:

echo '[$CODE]' >/dev/fd/0

एक बहुत ही महत्वपूर्ण अंतर के साथ। ऐसा न करने पर एक की तो खोल खोल के लिए इसका मूल्यांकन करेंगे की तरह:"'\quote'"<<"'\LIMITER"'here-document$expansion

echo "[$CODE]" >/dev/fd/0

तो, के लिए _gem_dec(),3<<-FUNC here-document इनपुट पर एक फ़ाइल, एक ही रूप में यह अगर यह थे होगा के रूप में मूल्यांकन किया जाता है 3<~/some.file को छोड़कर कि क्योंकि हम छोड़ FUNCसीमक उद्धरण के नि: शुल्क है, यह पहले के लिए मूल्यांकन किया जाता है $expansion.इस बारे में महत्वपूर्ण बात यह है कि यह इनपुट, जिसका अर्थ है यह केवल के लिए मौजूद है _gem_dec(),लेकिन वह यह भी पहले मूल्यांकन किया जाता है _gem_dec()समारोह रन हमारे खोल पढ़ सकते हैं और इसके मूल्यांकन करने के लिए नहीं है, क्योंकि $expansionsइससे पहले कि इनपुट के रूप में सौंपने इसे बंद।

करने देता है guard,, उदाहरण के लिए:

_gem_dec guard

तो पहले शेल को इनपुट को हैंडल करना होगा, जिसका अर्थ है पढ़ना:

3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC

फ़ाइल डिस्क्रिप्टर 3 में और शेल विस्तार के लिए इसका मूल्यांकन करना। अगर इस समय आप भागे:

cat /dev/fd/3

या:

cat <&3

चूंकि वे दोनों समान आदेश हैं जिन्हें आप देखेंगे:

_guard() { [ ! -e 'Gemfile' ] && { 
    command guard "$@" ; return $?
    } || bundle exec guard "$@"
}

... समारोह में किसी भी कोड से पहले कभी भी निष्पादित करता है। यह सब के बाद <input, फ़ंक्शन है । अधिक उदाहरणों के लिए यहां एक अलग प्रश्न के लिए मेरा उत्तर देखें ।

(* तकनीकी तौर पर यह पूरी तरह सच नहीं है। क्योंकि मैं एक प्रमुख का उपयोग -dashकरने से पहले here-doc limiter, सब से ऊपर बाएं से उचित किया जाएगा। लेकिन मैं प्रयोग किया जाता है -dashतो मैं कर सकता है <tab-insert>पहली जगह में पठनीयता के लिए तो मैं नहीं पट्टी करने जा रहा हूँ <tab-inserts>से पहले आपको इसे पढ़ने की पेशकश ...)

इस बारे में सबसे अच्छा हिस्सा उद्धरण है - ध्यान दें कि '"उद्धरण बने हुए हैं और केवल \उद्धरण छीन लिए गए हैं। यह संभवतः इस कारण से किसी भी अन्य से अधिक है कि यदि आपको एक शेल का दो बार मूल्यांकन करना है तो $expansionमैं सिफारिश करूंगा here-documentक्योंकि उद्धरणों की तुलना में बहुत आसान है eval

वैसे भी, अब उपर्युक्त कोड ठीक उसी तरह से फाइल है जैसे 3<~/heredoc.fileकि _gem_dec()फंक्शन आने के इंतजार में और उसके इनपुट को स्वीकार करने के लिए /dev/fd/3

इसलिए जब हम _gem_dec()पहला काम शुरू करते हैं तो मैं सभी स्थितिगत मापदंडों को टॉस करता हूं , क्योंकि हमारा अगला कदम दो बार मूल्यांकन किया गया शेल विस्तार है और मैं नहीं चाहता कि निहित में $expansionsसे कोई भी मेरे वर्तमान $1 $2 $3...मापदंडों के रूप में व्याख्या की जाए । तो मैं:

shift $#

shiftकई के रूप में छोड देता है positional parametersआपके द्वारा निर्दिष्ट और से शुरू होता है के रूप में $1क्या रहता है के साथ। तो अगर मैं कहा जाता _gem_dec one two threeप्रॉम्प्ट पर _gem_dec's $1 $2 $3स्थितीय मापदंडों होगा one two threeऔर कुल वर्तमान स्थितीय गिनती, या $#3. होगा मैं तो कहा जाता है, तो shift 2,के मूल्यों oneऔरtwo की जाएगी shiftएड का मूल्य दूर, $1करने के लिए बदल जाएगा threeऔर $#करने के लिए विस्तार होगा 1. तो shift $#बस उन सबको दूर फेंक देता है। ऐसा करना पूरी तरह से एहतियाती है और कुछ समय के लिए इस तरह का काम करने के बाद विकसित हुई एक आदत है। यहाँ यह (subshell)स्पष्टता के लिए थोड़ा फैला हुआ है:

( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3

( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1

( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0

वैसे भी, अगला कदम वह होता है जहाँ जादू होता है। यदि आप . ~/some.shशेल प्रॉम्प्ट पर हैं, तो घोषित किए गए सभी फ़ंक्शन और पर्यावरण चर ~/some.shआपके शेल प्रॉम्प्ट पर कॉल करने योग्य होंगे। यही बात लागू होती यहाँ, है हम को छोड़कर हमारे फ़ाइल वर्णनकर्ता के लिए विशेष फ़ाइल, या - जो है, जहां हमारे इन-लाइन फ़ाइल pathed किया गया है - और हम अपने समारोह की घोषणा की है। और इसी तरह यह काम करता है।. sourcecharacter device. /dev/fd/3here-document

_guard

अब आपके _guardफंक्शन को जो भी करना चाहिए, करता है।

परिशिष्ट:

अपनी स्थिति को बचाने के लिए कहने का एक शानदार तरीका:

f() { . /dev/fd/3
} 3<<-ARGS
    args='${args:-"$@"}'
ARGS

संपादित करें:

जब मैंने पहली बार इस प्रश्न का उत्तर दिया तो मैंने एक शेल घोषित करने की समस्या पर अधिक ध्यान केंद्रित किया जो function()अन्य कार्यों को घोषित करने में सक्षम था जो वर्तमान शेल $ENVइस्त्री में जारी रहेगा, जैसा कि मैंने पूछा कि निरंतर कार्यों के साथ क्या करेंगे। तब से मैंने महसूस किया है कि मेरे मूल रूप से हल किए गए समाधान 3<<-FUNCने रूप लिया:

3<<-FUNC
    _${1}() { 
        if [ -e 'Gemfile' ]; then
            bundle exec $1 "\$@"
        else 
            command _${1} "\$@"
    }
FUNC

संभावना के रूप में प्रश्नकर्ता के लिए उम्मीद है क्योंकि मैं विशेष रूप से से कथात्मक समारोह का नाम बदल दिया काम किया है नहीं होगा $1करने के लिए _${1}है, जो अगर की तरह कहा जाता है _gem_dec guardउदाहरण के लिए, में परिणाम होगा _gem_decनाम के एक समारोह की घोषणा _guardके रूप में बस के लिए विरोध किया guard

नोट: इस तरह का व्यवहार मेरे लिए आदत की बात है - मैं आमतौर पर अनुमान पर काम करता हूं कि शेल फ़ंक्शन केवल अपने स्वयं केकब्जे में होने चाहिए_namespaceताकिnamespaceशेलपर उनके घुसपैठ सेcommandsउचित रूपसे बचें।

यह एक सार्वभौमिक आदत नहीं है, हालांकि, जैसा कि commandकॉल करने के लिए पूछने वाले के उपयोग में विकसित किया गया है $1

आगे की परीक्षा मुझे निम्नलिखित पर विश्वास करने के लिए प्रेरित करती है:

पूछने वाला चाहता है कि शेल फ़ंक्शन नाम guard, rspec, or rake, जिसे कहा जाता है, rubyउसी नाम ifके एक फंक्शन को संकलित करेगा जिस फ़ाइल में Gemfileमौजूद है $PATH या if Gemfile मौजूद नहीं है, शेल फ़ंक्शन को rubyउसी नाम के फ़ंक्शन को निष्पादित करना चाहिए ।

यह पहले काम नहीं करता था क्योंकि मैंने पढ़ने के लिए $1कॉल को बदल दिया था command:

command _${1}

जिसके परिणामस्वरूप rubyशेल फ़ंक्शन संकलित नहीं होता है:

bundle exec $1

मुझे आशा है कि आप देख सकते हैं (जैसा कि अंततः मैंने किया था) ऐसा लगता है कि पूछने वाला केवल commandअप्रत्यक्ष रूप से निर्दिष्ट करने के लिए उपयोग कर रहा है namespaceक्योंकि एक ही नाम के शेल फ़ंक्शन पर एक commandनिष्पादन योग्य फ़ाइल को कॉल करना पसंद करेंगे $PATH

यदि मेरा विश्लेषण सही है (जैसा कि मुझे उम्मीद है कि पुष्टिकरण की पुष्टि होगी) तो यह:

_${1}() { [ ! -e 'Gemfile' ] && { 
    command $1 "\$@" ; return \$?
    } || bundle exec $1 "\$@"
}

उन शर्तों को बेहतर तरीके से संतुष्ट करना चाहिए, जो guardप्रॉम्प्ट पर कॉल कर रहे हैं , केवल$PATH नाम में एक निष्पादन योग्य फ़ाइल निष्पादित करने का प्रयास करेंगे, guardजबकि _guardप्रॉम्प्ट पर कॉलिंग Gemfile'sअस्तित्व की जांच करेगी और तदनुसार निष्पादित या निष्पादन guardयोग्य में निष्पादित करेगी $PATHइस तरह namespaceसे संरक्षित है और, जैसा कि मुझे लगता है कि कम से कम, यह पूछने वाले का इरादा अभी भी पूरा हुआ है।

वास्तव में, हमारे खोल समारोह मानते हुए _${1}()और निष्पादन योग्य ${PATH}/${1}हैं केवल दो तरीके हमारे खोल के लिए एक कॉल व्याख्या की जा सकती है या तो $1या _${1}तो के उपयोग commandबिल्कुल भी समारोह में अब पूरी तरह से बेमानी बना दिया है। फिर भी, मैंने इसे वैसे ही रहने दिया जैसे कि मैं एक ही गलती से दो बार करना पसंद नहीं करता ... वैसे भी एक पंक्ति में।

यदि यह पूछने वाले के लिए अस्वीकार्य है और वह / वह _पूरी तरह से दूर करना पसंद करेंगे , तो अपने वर्तमान रूप में, _underscoreसभी संपादनकर्ता को अपनी आवश्यकताओं को पूरा करने के लिए सभी की आवश्यकता होनी चाहिए जैसा कि मैं उन्हें समझता हूं।

उस परिवर्तन के अलावा, मैंने मूल सिंटैक्स के बजाय शॉर्ट-सर्किट सशर्त का उपयोग करने &&और / या|| शेल करने के लिए फ़ंक्शन को भी संपादित किया है । इस तरह से कथन का मूल्यांकन केवल तभी किया जाता है जब वह अंदर न हो हालाँकि, यह सुनिश्चित करने के लिए कि इस घटना के न होने की स्थिति में नहीं चलने के अलावा इस संशोधन की आवश्यकता है, लेकिन फ़ंक्शन 0 के अलावा कुछ भी नहीं देता है if/thencommandGemfile$PATHreturn $?bundleGemfileruby $1

अंतिम, मुझे ध्यान देना चाहिए कि यह समाधान केवल पोर्टेबल शेल निर्माण को लागू करता है। दूसरे शब्दों में, यह POSIX संगतता का दावा करने वाले किसी भी शेल में समान परिणाम प्रस्तुत करना चाहिए। हालांकि, यह निश्चित रूप से, मेरे लिए यह दावा करने के लिए बकवास होगा कि प्रत्येक POSIX- संगत प्रणाली को ruby bundleनिर्देश को संभालना चाहिए , कम से कम शेल कॉलर्स को इस पर व्यवहार करना चाहिए चाहे कॉल शेल shया हो dashइसके अलावा उपरोक्त दोनों ( और वैसे भी कम से कम आधे रास्ते-लेन कीshopts उम्मीद ) के रूप में काम करेगा bashzsh


मैंने आपका कोड डाला ~/.bashrcऔर कॉल किया . ~/.bashrc, फिर इन तीन फ़ंक्शन को निष्पादित किया गया। हो सकता है कि व्यवहार पर्यावरण से भिन्न हो, इसलिए मैंने अपने पर्यावरण को प्रश्न में जोड़ा। इसके अलावा मुझे समझ नहीं आ रहा था कि अंतिम पंक्ति _guard ; _rspec ; _rakeकी आवश्यकता क्यों है। मैंने shiftडिस्क्रिप्टर के बारे में और फ़ाइल खोजा , ऐसा लगता है कि ये मेरी वर्तमान समझ से परे हैं।
ir18and

मैंने सिर्फ यह कहा कि दिखाने के लिए वे कॉल करने योग्य थे। क्षमा करें - मैं अब एक प्रतिध्वनि में डाल दिया। इसलिए आप उन्हें कार्य कह सकते हैं, जैसा आपने प्रदर्शित किया है।
मोकेसर

@ टेट्सू - क्या अब यह बेहतर समझ में आता है?
मिकसेर्व

मैं आपके उत्तर को 3 बार पढ़ता हूं, लेकिन ईमानदारी से कहूं तो मुझे स्पष्टीकरण को समझने के लिए अधिक ज्ञान की आवश्यकता है। हालांकि मैं आपका बहुत आभारी हूं, फिर भी जब मुझे और अनुभव मिले तो मैं इसे फिर से पढ़ूंगा।
ironsand

@ टेट्सू शायद यह अब स्पष्ट है ...? मुझे लगता है कि मैंने महसूस किया है, और अब सही कर दिया है, एक गलती जो मैंने पहले की थी। कृपया मुझे बताएं, यदि आप करेंगे।
मिकसेर

2
function threeinone () {
    local var="$1"
    if [ $# -ne 1 ]; then
        return 1
    fi
    if ! [ "$1" = "guard" -o "$1" = "rspec" -o "$1" = "rake" ]; then
        return 1
    fi
    shift
    if [ -e 'Gemfile' ]; then
        bundle exec "$var" "$@"
    else
        command "$var" "$@"
    fi
}

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