इस जरूरतों 4.1 पार्टी की योजना बनाई है, तो आप का उपयोग करें {fd}
या local -n
।
बाकी मैं 3.x में आशा है कि काम करना चाहिए। मैं पूरी तरह से सुनिश्चित नहीं हूं printf %q
- यह एक बैश 4 फीचर हो सकता है।
सारांश
वांछित प्रभाव को संग्रहीत करने के लिए आपका उदाहरण निम्नानुसार संशोधित किया जा सकता है:
# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }
e=2
# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
e=4
echo "hello"
}
# Change following line to:
capture ret test1
echo "$ret"
echo "$e"
प्रिंट के रूप में वांछित:
hello
4
ध्यान दें कि यह समाधान:
- के लिए
e=1000
भी काम करता है।
$?
जरूरत पड़ने पर संरक्षित करता है$?
केवल बुरे दुष्प्रभाव हैं:
- इसे आधुनिक की जरूरत है
bash
।
- यह काफी अधिक बार कांटे देता है।
- इसे एनोटेशन की आवश्यकता है (आपके फ़ंक्शन के नाम पर, एक जोड़ा के साथ
_
)
- यह फाइल डिस्क्रिप्टर 3 का बलिदान करता है।
- जरूरत पड़ने पर आप इसे दूसरी एफडी में बदल सकते हैं।
- में
_capture
बस की सभी आवृत्तियां की जगह 3
एक और (उच्च) संख्या के साथ।
निम्नलिखित (जो कि काफी लंबा है, उसके लिए खेद है) उम्मीद करता है कि इस नुस्खे को अन्य लिपियों के लिए भी स्वीकार किया जाए।
समस्या
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4
आउटपुट
0 20171129-123521 20171129-123521 20171129-123521 20171129-123521
जबकि वांछित आउटपुट है
4 20171129-123521 20171129-123521 20171129-123521 20171129-123521
समस्या का कारण
शैल चर (या आम तौर पर बोल, पर्यावरण) को माता-पिता की प्रक्रियाओं से बाल प्रक्रियाओं में पारित किया जाता है, लेकिन इसके विपरीत नहीं।
यदि आप आउटपुट कैप्चरिंग करते हैं, तो यह आमतौर पर एक सब-टाइम में चलाया जाता है, इसलिए बैक चर को पार करना मुश्किल है।
कुछ आपको यह भी बताते हैं, कि इसे ठीक करना असंभव है। यह गलत है, लेकिन समस्या को हल करने के लिए यह लंबे समय से ज्ञात मुश्किल है।
इसे सर्वोत्तम तरीके से हल करने के कई तरीके हैं, यह आपकी आवश्यकताओं पर निर्भर करता है।
यह कैसे करना है पर एक कदम गाइड द्वारा कदम है।
पैतृक खोल में चर चरना
पैतृक खोल के लिए चर को वापस करने का एक तरीका है। हालाँकि यह एक खतरनाक रास्ता है, क्योंकि यह उपयोग करता है eval
। यदि अनुचित तरीके से किया जाता है, तो आप कई बुरी चीजों का जोखिम उठाते हैं। लेकिन अगर ठीक से किया जाए, तो यह पूरी तरह से सुरक्षित है, बशर्ते कि इसमें कोई बग न हो bash
।
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }
x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4
प्रिंट
4 20171129-124945 20171129-124945 20171129-124945 20171129-124945
ध्यान दें कि यह खतरनाक चीजों के लिए भी काम करता है:
danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"
प्रिंट
; /bin/echo *
यह इस कारण से है printf '%q'
, जो सब कुछ को उद्धृत करता है, ताकि आप इसे शेल संदर्भ में सुरक्षित रूप से फिर से उपयोग कर सकें।
लेकिन यह एक दर्द में है ..
यह न केवल बदसूरत दिखता है, यह भी टाइप करना बहुत है, इसलिए यह त्रुटि प्रवण है। बस एक ही गलती और आप बर्बाद हैं, है ना?
खैर, हम शेल स्तर पर हैं, इसलिए आप इसे सुधार सकते हैं। बस एक इंटरफ़ेस के बारे में सोचें जिसे आप देखना चाहते हैं, और फिर आप इसे लागू कर सकते हैं।
ऑगमेंट, शेल कैसे चीजों को प्रोसेस करता है
चलो एक कदम पीछे जाते हैं और कुछ एपीआई के बारे में सोचते हैं जो हमें आसानी से व्यक्त करने की अनुमति देता है, हम क्या करना चाहते हैं।
खैर, हम d()
फ़ंक्शन के साथ क्या करना चाहते हैं ?
हम आउटपुट को वेरिएबल में कैप्चर करना चाहते हैं। ठीक है, तो चलिए इसके लिए एक एपीआई लागू करें:
# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output="$1"
shift
output="$("$@")"
}
अब लिखने के बजाय
d1=$(d)
हम लिख सकते है
capture d1 d
ठीक है, ऐसा लगता है कि हम बहुत बदल नहीं गए हैं, जैसा कि, फिर से, चर d
माता-पिता के खोल से वापस पारित नहीं किए गए हैं , और हमें थोड़ा और टाइप करने की आवश्यकता है।
हालाँकि अब हम इस पर शेल की पूरी शक्ति फेंक सकते हैं, क्योंकि यह अच्छी तरह से एक फ़ंक्शन में लिपटा हुआ है।
इंटरफ़ेस का पुन: उपयोग करने के लिए एक आसान के बारे में सोचो
एक दूसरी बात यह है, कि हम DRY होना चाहते हैं (खुद को दोहराना नहीं)। इसलिए हम निश्चित रूप से कुछ टाइप नहीं करना चाहते हैं
x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4
x
यहां न केवल अनावश्यक, यह त्रुटि हमेशा सही संदर्भ में repeate की संभावना है। क्या होगा यदि आप इसे स्क्रिप्ट में 1000 बार उपयोग करते हैं और फिर एक चर जोड़ते हैं? आप निश्चित रूप से उन सभी 1000 स्थानों को बदलना नहीं चाहते जहाँ कॉल करना d
शामिल है।
इसलिए x
दूर छोड़ दो , तो हम लिख सकते हैं:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }
xcapture() { local -n output="$1"; eval "$("${@:2}")"; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
आउटपुट
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
यह पहले से ही बहुत अच्छा लग रहा है। (लेकिन अभी भी है local -n
जो आम bash
3.x में काम नहीं करता है )
बदलने से बचें d()
अंतिम समाधान में कुछ बड़ी खामियां हैं:
d()
बदलने की जरूरत है
- इसे
xcapture
आउटपुट पास करने के लिए कुछ आंतरिक विवरणों का उपयोग करने की आवश्यकता है ।
- ध्यान दें कि यह छाया (जलता) नाम का एक चर है
output
, इसलिए हम इसे कभी भी वापस नहीं कर सकते।
- इसमें साथ देने की जरूरत है
_passback
क्या हम इससे भी छुटकारा पा सकते हैं?
बिलकुल हम कर सकते हैं! हम एक शेल में हैं, इसलिए ऐसा करने के लिए हमें जो कुछ भी चाहिए वह है।
यदि आप कॉल को थोड़ा करीब से eval
देख सकते हैं, तो आप देख सकते हैं कि इस स्थान पर हमारा 100% नियंत्रण है। "अंदर" eval
हम एक उपधारा में हैं, इसलिए हम माता-पिता के खोल में कुछ बुरा करने के डर के बिना हम सब कुछ कर सकते हैं।
हाँ, अच्छा है, तो चलो एक और आवरण जोड़ते हैं, अब सीधे अंदर हैं eval
:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${@:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; } # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$@")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
प्रिंट
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
हालाँकि, यह, फिर से, कुछ बड़ी खामी है:
!DO NOT USE!
मार्कर, देखते हैं इस में एक बहुत ही बुरा रेस स्थिति है, जो आप आसानी से नहीं देख सकता है, क्योंकि:
>(printf ..)
एक पृष्ठभूमि काम है। तो यह अभी भी _passback x
चल रहा है जबकि निष्पादित हो सकता है।
- यदि आप
sleep 1;
पहले printf
या जोड़ते हैं तो आप इसे स्वयं देख सकते हैं _passback
।
_xcapture a d; echo
फिर आउटपुट x
या a
पहले, क्रमशः।
- का
_passback x
हिस्सा नहीं होना चाहिए _xcapture
, क्योंकि इससे उस नुस्खा का पुन: उपयोग करना मुश्किल हो जाता है।
- इसके अलावा, हमारे पास यहाँ (
$(cat)
) कुछ अनकैप्ड फोर्क हैं , लेकिन जैसा कि इस समाधान !DO NOT USE!
मैं सबसे छोटा रास्ता है।
हालाँकि, यह दिखाता है, कि हम यह कर सकते हैं, संशोधन के बिना d()
(और बिना local -n
)!
कृपया ध्यान दें कि हमें बिल्कुल भी ज़रूरत नहीं है _xcapture
, क्योंकि हम हर सही में लिख सकते हैं eval
।
हालाँकि ऐसा करना आमतौर पर बहुत पठनीय नहीं है। और यदि आप कुछ वर्षों में अपनी स्क्रिप्ट पर वापस आते हैं, तो आप शायद इसे बहुत परेशानी के बिना फिर से पढ़ना चाहते हैं।
दौड़ को ठीक करें
अब चलिए दौड़ की हालत ठीक करते हैं।
चाल तब तक इंतजार कर सकती है जब तक printf
कि यह STDOUT बंद न हो जाए , और फिर आउटपुट x
।
इसे संग्रहीत करने के कई तरीके हैं:
- आप शेल पाइप का उपयोग नहीं कर सकते, क्योंकि पाइप विभिन्न प्रक्रियाओं में चलते हैं।
- एक अस्थायी फ़ाइलों का उपयोग कर सकते हैं,
- या लॉक फाइल या फिफो जैसी कोई चीज। यह लॉक या फीफो के लिए प्रतीक्षा करने की अनुमति देता है,
- या विभिन्न चैनल, सूचना को आउटपुट करने के लिए, और फिर कुछ सही क्रम में आउटपुट को इकट्ठा करते हैं।
अंतिम रास्ता निम्नलिखित लग सकता है (ध्यान दें कि यह printf
अंतिम करता है क्योंकि यह यहां बेहतर काम करता है):
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_xcapture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; _passback x >&3)"; } 3>&1; }
xcapture() { eval "$(_xcapture "$@")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
आउटपुट
4 20171129-144845 20171129-144845 20171129-144845 20171129-144845
यह सही क्यों है?
_passback x
सीधे STDOUT से बात करता है।
- हालाँकि, जैसा कि STDOUT को आंतरिक कमांड में कैप्चर करने की आवश्यकता है, हम पहले इसे '3> और 1' के साथ FD3 (आप दूसरों का उपयोग कर सकते हैं) में "सेव" करते हैं और फिर इसके साथ पुन: उपयोग करते हैं
>&3
।
- के
$("${@:2}" 3<&-; _passback x >&3)
बाद खत्म होता है _passback
, जब उपधारा STDOUT को बंद कर देता है।
- तो
printf
इससे पहले ऐसा नहीं हो सकता _passback
, भले ही कितना समय लगे _passback
।
- ध्यान दें कि
printf
कमांड को पूरा कमांडलाइन इकट्ठा होने से पहले निष्पादित नहीं किया गया है, इसलिए हम printf
स्वतंत्र रूप से कैसे printf
कार्यान्वित किए जाते हैं , से कलाकृतियों को नहीं देख सकते हैं ।
इसलिए पहले _passback
निष्पादित करता है, फिर printf
।
यह रेस को हल करता है, एक निश्चित फाइल डिस्क्रिप्टर का त्याग करते हुए 3. आप निश्चित रूप से, मामले में एक और फाइल डिस्क्रिप्टर का चयन कर सकते हैं, जो कि FD3 आपकी शेलस्क्रिप्ट में मुक्त नहीं है।
कृपया यह भी ध्यान दें कि 3<&-
जो एफडी 3 को कार्य करने के लिए सुरक्षित रखता है।
इसे और सामान्य बनाएं
_capture
इसमें ऐसे भाग शामिल हैं, जो d()
एक पुन: प्रयोज्यता के दृष्टिकोण से खराब हैं। इसे कैसे हल करें?
ठीक है, इसे एक और चीज, एक अतिरिक्त कार्य, जो सही चीजों को वापस करना चाहिए, का परिचय देकर इसे उच्छृंखल तरीके से करें, जिसे मूल कार्य के साथ _
संलग्न किया गया है।
यह फ़ंक्शन वास्तविक फ़ंक्शन के बाद कहा जाता है, और चीजों को बढ़ा सकता है। इस तरह, इसे कुछ एनोटेशन के रूप में पढ़ा जा सकता है, इसलिए यह बहुत पठनीय है:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; "$2_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$@")"; }
d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4
अभी भी प्रिंट करता है
4 20171129-151954 20171129-151954 20171129-151954 20171129-151954
रिटर्न-कोड तक पहुंच की अनुमति दें
केवल गुमशुदा है:
v=$(fn)
$?
जो fn
लौटा वह सेट । तो आप शायद यही चाहते हैं। हालांकि इसके लिए कुछ बड़े बदलाव की जरूरत है:
# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }
# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }
# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf
प्रिंट
23 42 69 FAIL
अभी भी बहुत कुछ सुधार की गुंजाइश है
_passback()
के साथ समाप्त किया जा सकता है passback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture()
के साथ समाप्त किया जा सकता है capture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
समाधान फ़ाइल डिस्क्रिप्टर (यहां 3) को आंतरिक रूप से उपयोग करके प्रदूषित करता है। यदि आप एफडी पास करने के लिए होते हैं तो आपको इसे ध्यान में रखना चाहिए।
ध्यान दें कि bash
4.1 और ऊपर के {fd}
कुछ अप्रयुक्त एफडी का उपयोग करना है।
(शायद मैं अपने आसपास आने पर यहां एक समाधान जोड़ दूंगा।)
ध्यान दें कि मैं इसे अलग-अलग कार्यों में लगाने के लिए उपयोग करता हूं _capture
, क्योंकि इस सब को एक पंक्ति में भरना संभव है, लेकिन इसे पढ़ने और समझने के लिए तेजी से कठिन हो जाता है।
शायद आप कॉल किए गए फ़ंक्शन के STDERR को भी कैप्चर करना चाहते हैं। या आप एक से अधिक फ़ाइलेस्क्रिप्ट से और चरों से भी अंदर और बाहर जाना चाहते हैं।
मेरे पास अभी तक कोई समाधान नहीं है, हालांकि यहां एक से अधिक एफडी को पकड़ने का एक तरीका है , इसलिए हम शायद चर को भी इस तरह से वापस कर सकते हैं।
यह भी न भूलें:
यह एक शेल फ़ंक्शन को कॉल करना चाहिए, न कि एक बाहरी कमांड।
बाहरी चर से बाहर पर्यावरण चर को पारित करने का कोई आसान तरीका नहीं है। (इसके साथ LD_PRELOAD=
यह संभव होना चाहिए, हालांकि!) लेकिन यह तब कुछ पूरी तरह से अलग है।
आखरी श्ब्द
यह एकमात्र संभव समाधान नहीं है। यह एक समाधान के लिए एक उदाहरण है।
हमेशा आपके पास शेल में चीजों को व्यक्त करने के कई तरीके होते हैं। इसलिए बेहतरी के लिए बेझिझक और कुछ बेहतर पाएं।
यहाँ प्रस्तुत समाधान परिपूर्ण होने से काफी दूर है:
- यह लगभग बिल्कुल भी टेस्टेट नहीं था, इसलिए कृपया टाइपोस को माफ कर दें।
- सुधार के लिए बहुत जगह है, ऊपर देखें।
- यह आधुनिक से कई विशेषताओं का उपयोग करता है
bash
, इसलिए शायद अन्य गोले को पोर्ट करना मुश्किल है।
- और कुछ ऐसे प्रश्न हो सकते हैं जिनके बारे में मैंने नहीं सोचा है।
हालाँकि मुझे लगता है कि इसका उपयोग करना काफी आसान है:
- "पुस्तकालय" की सिर्फ 4 पंक्तियों को जोड़ें।
- अपने शेल फ़ंक्शन के लिए "एनोटेशन" की सिर्फ 1 पंक्ति जोड़ें।
- अस्थायी रूप से केवल एक फ़ाइल विवरणक बलिदान।
- और प्रत्येक चरण को वर्षों बाद भी समझना आसान होना चाहिए।