उस प्रक्रिया से बाहर निकलने की स्थिति प्राप्त करें जो किसी अन्य को दी गई है


287

मेरी दो प्रक्रियाएँ हैं fooऔर bar, एक पाइप से जुड़ी हुई हैं:

$ foo | bar

barहमेशा बाहर निकलता है 0; मुझे बाहर निकलने के कोड में दिलचस्पी है foo। क्या इसे पाने का कोई तरीका है?


जवाबों:


256

यदि आप उपयोग कर रहे हैं bash, तो आप PIPESTATUSपाइपलाइन के प्रत्येक तत्व की निकास स्थिति प्राप्त करने के लिए सरणी चर का उपयोग कर सकते हैं ।

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

यदि आप उपयोग कर रहे हैं zsh, तो उन्हें सरणी कहा जाता है pipestatus(मामले मायने रखता है!) और सरणी सूचकांक एक पर शुरू होते हैं:

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

एक फ़ंक्शन के भीतर उन्हें संयोजित करने के लिए जो मानों को नहीं खोता है:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

ऊपर चलाने के लिए bashया zshआप एक ही परिणाम प्राप्त करेंगे; केवल एक retval_bashऔर retval_zshसेट किया जाएगा। दूसरा खाली होगा। यह एक फ़ंक्शन को समाप्त करने की अनुमति देगा return $retval_bash $retval_zsh(उद्धरण की कमी पर ध्यान दें!)।


9
और pipestatuszsh में। दुर्भाग्य से अन्य गोले में यह सुविधा नहीं है।
गिल्स

8
नोट: zr में एरर इंडेक्स 1 पर counterintuitively से शुरू होता है, इसलिए यह है echo "$pipestatus[1]" "$pipestatus[2]"
क्रिस्टोफ वर्म

5
आप इस तरह से पूरी पाइपलाइन की जाँच कर सकते हैं:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0

7
@ जानहुडेक: शायद आपको मेरे उत्तर के पहले पांच शब्द पढ़ने चाहिए। कृपया यह भी इंगित करें कि प्रश्न ने POSIX-only उत्तर का अनुरोध कहां किया है।
Camh

12
@JHHudec: न ही इसे POSIX टैग किया गया था। आप क्यों मानते हैं कि उत्तर POSIX होना चाहिए? यह निर्दिष्ट नहीं था इसलिए मैंने एक योग्य उत्तर प्रदान किया। मेरे उत्तर के बारे में कुछ भी गलत नहीं है, अन्य मामलों को संबोधित करने के लिए कई उत्तर हैं।
Camh

237

ऐसा करने के 3 सामान्य तरीके हैं:

Pipefail

पहला तरीका pipefailविकल्प ( ksh, zshया bash) सेट करना है । यह सबसे सरल है और यह जो करता है वह मूल रूप से $?गैर-शून्य (या शून्य यदि सभी सफलतापूर्वक बाहर निकलता है) से बाहर निकलने के लिए अंतिम कार्यक्रम के निकास कोड के लिए निकास की स्थिति निर्धारित करता है ।

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

बैश में एक सरणी चर भी होता है जिसे $PIPESTATUS( $pipestatusमें zsh) कहा जाता है जिसमें अंतिम पाइपलाइन में सभी कार्यक्रमों की निकास स्थिति होती है।

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

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

अलग निष्पादित

यह समाधानों का सबसे अधिक वर्णन है। प्रत्येक कमांड को अलग से चलाएं और स्टेटस कैप्चर करें

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1

2
अरे! मैं सिर्फ PIPESTATUS के बारे में पोस्ट करने वाला था।
slm

1
: संदर्भ के लिए वहाँ कई अन्य तकनीकों इस तो सवाल में चर्चा कर रहे हैं stackoverflow.com/questions/1221833/...
SLM

1
@Patrick pipestatus सॉल्यूशन बैश पर काम करता है, अगर मैं ksh स्क्रिप्ट का उपयोग करता हूं तो आपको लगता है कि हम पिपेस्टैटस के समान कुछ पा सकते हैं? , (वास्तव में मैं ksh द्वारा समर्थित नहीं pipestatus देखें)
योएल

2
@ यूल मैं उपयोग नहीं करता ksh, लेकिन इसके संक्षिप्त पृष्ठ पर संक्षिप्त नज़र से देखें, तो यह समर्थन $PIPESTATUSया कुछ भी समान नहीं है। pipefailहालांकि यह विकल्प का समर्थन करता है ।
पैट्रिक

1
मैंने पाइपफेल के साथ जाने का फैसला किया क्योंकि यह मुझे यहां असफल कमांड का दर्जा पाने की अनुमति देता है:LOG=$(failed_command | successful_command)
vmrob

51

यह समाधान बैश विशिष्ट सुविधाओं या अस्थायी फ़ाइलों का उपयोग किए बिना काम करता है। बोनस: अंत में बाहर निकलने की स्थिति वास्तव में एक निकास स्थिति है और फ़ाइल में कुछ स्ट्रिंग नहीं है।

परिस्थिति:

someprog | filter

आप से बाहर निकलने की स्थिति someprogऔर से उत्पादन चाहते हैं filter

यहाँ मेरा समाधान है:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

इस निर्माण का परिणाम, निर्माण के स्टडआउट के filterरूप में और निर्माण की स्थिति से बाहर निकलने की स्थिति से बाहर निकलने की स्थिति से स्टडआउट है someprog


यह निर्माण {...}उपसमूह के बजाय साधारण कमांड ग्रुपिंग के साथ भी काम करता है (...)। सबस्क्रिप्शन के कुछ निहितार्थ हैं, दूसरों के बीच एक प्रदर्शन लागत, जिसकी हमें यहाँ आवश्यकता नहीं है। अधिक जानकारी के लिए ठीक बैश मैनुअल पढ़ें: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

दुर्भाग्य से बैश व्याकरण में घुंघराले ब्रेसिज़ के लिए रिक्त स्थान और अर्धविराम की आवश्यकता होती है ताकि निर्माण अधिक विस्तृत हो जाए।

इस पाठ के बाकी हिस्सों के लिए मैं उप-संस्करण का उपयोग करूंगा।


उदाहरण someprogऔर filter:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

उदाहरण आउटपुट:

filtered line1
filtered line2
filtered line3
42

नोट: चाइल्ड प्रोसेस पैरेंट से ओपन फाइल डिस्क्रिप्टर इनहेरिट करता है। इसका मतलब है कि someprogओपन फाइल डिस्क्रिप्टर 3 और 4 को इनहेरिट किया जाएगा। अगर आप someprogडिस्क्रिप्टर 3 फाइल करने के लिए लिखते हैं तो वह एक्ज़िट स्टेटस बन जाएगा। वास्तविक निकास स्थिति को अनदेखा किया जाएगा क्योंकि readकेवल एक बार पढ़ता है।

यदि आप चिंता करते हैं कि आपका someprogविवरण 3 या 4 फाइल करने के लिए लिख सकता है, तो कॉल करने से पहले फाइल डिस्क्रिप्टर को बंद करना सबसे अच्छा है someprog

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

exec 3>&- 4>&-पहले someprogनिष्पादित करने से पहले फ़ाइल वर्णनकर्ता बंद कर देता है someprogके लिए बहुत someprogउन फ़ाइल वर्णनकर्ता बस मौजूद नहीं हैं।

इसे इस तरह भी लिखा जा सकता है: someprog 3>&- 4>&-


कंस्ट्रक्शन के स्टेप बाई स्टेप स्पष्टीकरण:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

बढ़ना:

  1. फ़ाइल डिस्क्रिप्टर 4 के साथ एक सबस्क्रिप्शन बनाया गया है जिसे stdout पर पुनर्निर्देशित किया गया है। इसका मतलब यह है कि सबटाइटल में फाइल डिस्क्रिप्टर 4 के लिए जो भी प्रिंट किया गया है, वह पूरे निर्माण के स्टडआउट के रूप में समाप्त हो जाएगा।
  2. एक पाइप बनाया जाता है और बाएं ( #part3) और दाएं ( #part2) पर कमांड निष्पादित होते हैं। exit $xsपाइप की अंतिम कमांड भी है और इसका मतलब है कि स्टड से स्ट्रिंग पूरे निर्माण की निकास स्थिति होगी।
  3. एक उपखंड फ़ाइल डिस्क्रिप्टर 3 के साथ बनाया गया है जिसे stdout पर पुनर्निर्देशित किया गया है। इसका मतलब है कि जो कुछ भी इस उपधारा में फाइल डिस्क्रिप्टर 3 के लिए प्रिंट किया गया है वह समाप्त हो जाएगा #part2और बदले में पूरे निर्माण की निकास स्थिति होगी।
  4. एक पाइप बनाया जाता है और बाएं ( #part5और #part6) और दाएं ( filter >&4) पर कमांड निष्पादित होते हैं। filterफ़ाइल डिस्क्रिप्टर 4 में आउटपुट को रीडायरेक्ट किया #part1गया है। फ़ाइल डिस्क्रिप्टर 4 में स्टडआउट पर रीडायरेक्ट किया गया था। इसका मतलब है कि आउटपुट filterपूरे निर्माण का आधार है।
  5. #part6फाइल डिस्क्रिप्टर 3 से एक्ज़िट स्टेटस प्रिंट #part3किया जाता है #part2। फाइल डिस्क्रिप्टर 3 में रीडायरेक्ट किया गया था । इसका मतलब यह है कि बाहर #part6निकलने की स्थिति पूरे निर्माण के लिए अंतिम निकास स्थिति होगी।
  6. someprogनिष्पादित किया जाता है। बाहर निकलने की स्थिति में लिया जाता है #part5। स्टडआउट को पाइप द्वारा अंदर ले जाया जाता है #part4और आगे भेजा जाता है filterfilterजैसा कि इसमें बताया गया है, से आउटपुट स्टडआउट तक पहुंच जाएगा#part4

1
अच्छा लगा। समारोह के लिए मैं बस कर सकता हूँ(read; exit $REPLY)
jthill

5
(exec 3>&- 4>&-; someprog)को सरल करता है someprog 3>&- 4>&-
रोजर पाटे

यह विधि बिना { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
सबस्क्रिप्शन के

36

जबकि वास्तव में आपने जो पूछा था, आप उसका उपयोग नहीं कर सकते

#!/bin/bash -o pipefail

ताकि आपके पाइप अंतिम गैर शून्य रिटर्न लौटाएं।

कोडिंग थोड़ी कम हो सकती है

संपादित करें: उदाहरण

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1

9
set -o pipefailस्क्रिप्ट के अंदर और अधिक मजबूत होना चाहिए, उदाहरण के लिए यदि कोई स्क्रिप्ट के माध्यम से निष्पादित करता है bash foo.sh
मैक्सक्लेपजिग

वह कैसे काम करता है? क्या आपके पास एक उदाहरण है?
जोहान

2
ध्यान दें कि -o pipefailPOSIX में नहीं है।
scy

2
यह मेरे BASH 3.2.25 (1) -release में काम नहीं करता है। / Tmp / ff के शीर्ष पर मेरे पास है #!/bin/bash -o pipefail। त्रुटि है:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
फेलिप अल्वारेज़

2
@FelipeAlvarez: कुछ वातावरण (लाइनक्स सहित) #!पहले वाले से आगे लाइनों पर रिक्त स्थान को पार्स नहीं करते हैं , और इसलिए /bin/bash -o pipefail /tmp/ff, आवश्यक के बजाय यह बन जाता है /bin/bash -o pipefail /tmp/ff- getopt(या समान) पार्सिंग का उपयोग करते हुए optarg, जो कि अगला आइटम है ARGV, तर्क के रूप में। करने के लिए -o, तो यह विफल रहता है। यदि आप एक रैपर बनाने के लिए थे (कहो, bash-pfकि बस किया exec /bin/bash -o pipefail "$@", और उस #!लाइन पर डाल दिया , तो वह काम करेगा। यह भी देखें: en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes

22

मैं क्या जब संभव से बाहर निकलने के कोड को खिलाने के लिए है fooमें bar। उदाहरण के लिए, यदि मुझे पता है कि fooकभी भी केवल अंकों के साथ एक रेखा नहीं बनती है, तो मैं सिर्फ बाहर निकलने के कोड से निपट सकता हूं:

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

या अगर मुझे पता है कि fooकभी भी आउटपुट में सिर्फ एक लाइन नहीं होती है .:

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

यह हमेशा किया जा सकता है, अगर barसभी लाइन पर अंतिम पंक्ति में काम करने का कोई तरीका है , और अंतिम लाइन पर इसके निकास कोड के रूप में पास करें।

यदि barएक जटिल पाइपलाइन है, जिसके आउटपुट की आपको आवश्यकता नहीं है, तो आप एक अलग फ़ाइल डिस्क्रिप्टर पर एक्जिट कोड को प्रिंट करके इसके कुछ भाग को बायपास कर सकते हैं।

exit_codes=$({ { foo; echo foo:"$?" >&3; } |
               { bar >/dev/null; echo bar:"$?" >&3; }
             } 3>&1)

इसके बाद $exit_codesआम तौर पर होता है foo:X bar:Y, लेकिन यह हो सकता है bar:Y foo:Xकि barइसके सभी इनपुट को पढ़ने से पहले क्विट किया जाए या यदि आप अशुभ हैं। मुझे लगता है कि 512 बाइट्स तक के पाइप सभी यूनियनों पर परमाणु हैं, इसलिए जब तक टैग स्ट्रिंग्स 507 बाइट्स के नीचे हैं तब तक भागों foo:$?और bar:$?हिस्सों को इंटरमिक्स नहीं किया जाएगा।

यदि आपको आउटपुट को कैप्चर करने की आवश्यकता है bar, तो यह मुश्किल हो जाता है। आप आउटपुट को barएक आउट कोड कोड के समान दिखने वाली लाइन में शामिल नहीं करने के लिए कभी भी आउटपुट की व्यवस्था करके उपरोक्त तकनीकों को जोड़ सकते हैं , लेकिन यह पूरी तरह से हो जाता है।

output=$(echo;
         { { foo; echo foo:"$?" >&3; } |
           { bar | sed 's/^/^/'; echo bar:"$?" >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

और, ज़ाहिर है, स्थिति को संग्रहीत करने के लिए एक अस्थायी फ़ाइल का उपयोग करने का सरल विकल्प है । सरल, लेकिन उत्पादन में इतना आसान नहीं :

  • यदि कई स्क्रिप्ट्स समवर्ती चल रही हैं, या यदि एक ही स्क्रिप्ट कई स्थानों पर इस पद्धति का उपयोग करती है, तो आपको यह सुनिश्चित करने की आवश्यकता है कि वे अलग-अलग अस्थायी नाम का उपयोग करें।
  • एक साझा निर्देशिका में सुरक्षित रूप से एक अस्थायी फ़ाइल बनाना कठिन है। अक्सर, /tmpएकमात्र ऐसी जगह होती है, जहाँ कोई स्क्रिप्ट फ़ाइलों को लिखने में सक्षम होती है। उपयोग करें mktemp, जो आजकल POSIX नहीं है, लेकिन सभी गंभीर यूनियनों पर उपलब्ध है।
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
अस्थायी फ़ाइल दृष्टिकोण का उपयोग करते समय मैं EXIT के लिए एक जाल जोड़ना पसंद करता हूं जो सभी अस्थायी फ़ाइलों को हटा देता है ताकि स्क्रिप्ट के मरने पर भी कोई कचरा न छोड़ा जाए
चमत्कार 173

17

पाइपलाइन से शुरू:

foo | bar | baz

यहां केवल POSIX शेल और कोई अस्थायी फ़ाइलों का उपयोग करके एक सामान्य समाधान है:

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$error_statuses किसी भी विफल प्रक्रियाओं की स्थिति कोड यादृच्छिक क्रम में अनुक्रमित के साथ बताती है कि किस कमांड ने प्रत्येक स्थिति का उत्सर्जन किया है।

# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null

$error_statusesमेरे परीक्षणों में चारों ओर उद्धरण नोट करें ; उनके बिना grepअंतर नहीं हो सकता है क्योंकि नए स्थान रिक्त स्थान के लिए मजबूर हो जाते हैं।


12

इसलिए मैं lesmana की तरह एक जवाब का योगदान करना चाहता था, लेकिन मुझे लगता है कि मेरा शायद थोड़ा सरल और थोड़ा अधिक लाभकारी शुद्ध-बॉर्न-शेल समाधान है:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

मुझे लगता है कि यह अंदर से सबसे अच्छी तरह से समझाया गया है - कमांड 1 अपने नियमित आउटपुट को प्रिंटआउट (फाइल डिस्क्रिप्टर 1) पर निष्पादित करेगा और प्रिंट करेगा, फिर एक बार यह पूरा हो जाने पर, प्रिंटफ़ निष्पादित करेगा और कमांडो के निकास कोड को अपने स्टडआउट पर प्रिंट करेगा, लेकिन उस स्टडआउट को पुनर्निर्देशित किया जाएगा। फ़ाइल विवरणक 3।

जबकि कमांड 1 चल रहा है, इसके स्टडआउट को कमांड 2 पर प्रिंट किया जा रहा है (प्रिंटफ का आउटपुट कभी भी कमांड 2 पर नहीं जाता है क्योंकि हम इसे 1 के बजाय फाइल डिस्क्रिप्टर 3 में भेजते हैं, जिसे पाइप पढ़ता है)। फिर हम डिस्क्रिप्टर 4 को फाइल करने के लिए कमांड 2 के आउटपुट को रीडायरेक्ट करते हैं, ताकि वह फाइल डिस्क्रिप्टर 1 से भी बाहर रहे - क्योंकि हम फाइल डिस्क्रिप्टर 1 को थोड़े समय बाद के लिए फ्री चाहते हैं, क्योंकि हम फाइल डिस्क्रिप्टर 3 पर प्रिंटफ आउटपुट को फाइल डिस्क्रिप्टर में वापस लाएँगे। 1 - क्योंकि यही कमांड प्रतिस्थापन (बैकटिक्स) है, कैप्चर करेगा और वही है जो वैरिएबल में रखा जाएगा।

जादू का अंतिम बिट यह है कि पहले exec 4>&1हमने एक अलग कमांड के रूप में किया था - यह बाहरी शेल के स्टॉटआउट की एक प्रति के रूप में फाइल डिस्क्रिप्टर 4 को खोलता है। कमांड प्रतिस्थापन इसके अंदर कमांड के परिप्रेक्ष्य से मानक आउट पर जो कुछ भी लिखा गया है, उस पर कब्जा कर लेगा - लेकिन, चूंकि कमांड प्रतिस्थापन के संबंध में कमांड 2 का आउटपुट डिस्क्रिप्टर 4 फाइल करने जा रहा है, कमांड प्रतिस्थापन इसे कैप्चर नहीं करता है - हालांकि, एक बार जब यह कमांड प्रतिस्थापन का "आउट" हो जाता है, तो यह प्रभावी रूप से स्क्रिप्ट की समग्र फ़ाइल डिस्क्रिप्टर 1 पर जा रहा है।

( exec 4>&1एक अलग कमांड होना चाहिए क्योंकि कमांड प्रतिस्थापन के अंदर फ़ाइल डिस्क्रिप्टर पर लिखने की कोशिश करने पर कई सामान्य गोले इसे पसंद नहीं करते हैं, जो कि "बाहरी" कमांड में खोला जाता है जो प्रतिस्थापन का उपयोग कर रहा है। यह करने के लिए सबसे आसान पोर्टेबल तरीका है।)

आप इसे कम तकनीकी और अधिक चंचल तरीके से देख सकते हैं, जैसे कि कमांड के आउटपुट एक-दूसरे को लीपफ्रॉग कर रहे हैं: कमांड 1 से कमांड 2 पाइप, फिर प्रिंटफ का आउटपुट कमांड 2 पर कूदता है ताकि कमांड 2 इसे पकड़ न सके, और फिर कमांड 2 का आउटपुट कमांड प्रतिस्थापन के ऊपर और बाहर प्रिंटफ लैंड के रूप में समय से पहले ही स्थानापन्न द्वारा कब्जा कर लिया जाता है ताकि यह चर में समाप्त हो जाए, और कमांड 2 का उत्पादन मानक तरीके से लिखे जाने के रूप में मानक तरीके से लिखा जा रहा है। एक सामान्य पाइप में।

इसके अलावा, जैसा कि मैं समझता हूं, $?अभी भी पाइप में दूसरी कमांड का रिटर्न कोड होगा, क्योंकि चर असाइनमेंट, कमांड सबस्टेशन और कम्पाउंड कमांड सभी प्रभावी रूप से उनके अंदर कमांड के रिटर्न कोड के लिए पारदर्शी हैं, इसलिए रिटर्न की स्थिति कमांड 2 को प्रचारित किया जाना चाहिए - यह, और एक अतिरिक्त फ़ंक्शन को परिभाषित करने के लिए नहीं है, यही कारण है कि मुझे लगता है कि यह lesmana द्वारा प्रस्तावित एक की तुलना में कुछ बेहतर समाधान हो सकता है।

कैविट्स लेमनमा का उल्लेख करते हुए, यह संभव है कि कमांड 1 फ़ाइल डिस्क्रिप्टर 3 या 4 का उपयोग करके किसी बिंदु पर समाप्त होगा, इसलिए अधिक मजबूत होने के लिए, आप करेंगे:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

ध्यान दें कि मैं अपने उदाहरण में यौगिक आदेशों का उपयोग करता हूं, लेकिन उपधाराएं ( काम के ( )बजाय { }भी काम करेंगी, हालांकि शायद कम कुशल हो सकती हैं।)

फ़ाइल डिस्क्रिप्टर को इनहेरिट करने वाली प्रक्रिया से इनहेरिट करता है, इसलिए संपूर्ण दूसरी पंक्ति फ़ाइल डिस्क्रिप्टर चार को इनहेरिट करेगी, और उसके बाद कंपाउंड कमांड 3>&1फ़ाइल डिस्क्रिप्टर तीन को इनहेरिट करेगी। तो यह 4>&-सुनिश्चित करता है कि आंतरिक कंपाउंड कमांड फाइल डिस्क्रिप्टर चार 3>&-को इनहेरिट नहीं करेगा , और वॉयल फाइल डिस्क्रिप्टर तीन को इनहेरिट नहीं करेगा, इसलिए कमांड 1 को एक 'क्लीनर', अधिक मानक वातावरण मिलता है। आप आंतरिक को भी 4>&-बगल में ले जा सकते हैं 3>&-, लेकिन मैं यह बताता हूं कि क्यों न इसके दायरे को यथासंभव सीमित किया जाए।

मुझे यकीन नहीं है कि चीजें कितनी बार फ़ाइल डिस्क्रिप्टर तीन और चार का सीधे उपयोग करती हैं - मुझे लगता है कि अधिकांश समय प्रोग्राम ऐसे सिस्कल्स का उपयोग करते हैं जो नॉट-ऑन-ऑफ-द-क्षण फ़ाइल डिस्क्रिप्टर लौटाते हैं, लेकिन कभी-कभी कोड डिस्क्रिप्टर 3 को सीधे फाइल करने के लिए लिखते हैं, मैं लगता है (मैं एक प्रोग्राम को देखने के लिए फ़ाइल डिस्क्रिप्टर की जाँच कर सकता हूं कि क्या यह खुला है, और इसका उपयोग कर रहा है या नहीं तो इसके अनुसार अलग-अलग व्यवहार कर रहा है)। इसलिए उत्तरार्द्ध संभवतः सामान्य प्रयोजन के मामलों को ध्यान में रखने और उपयोग करने के लिए सबसे अच्छा है।


दिलचस्प लग रहा है, लेकिन मैं यह पता लगाने के लिए क्या आप इस आदेश की उम्मीद कर सकते हैं, और मेरा कंप्यूटर या तो नहीं कर सकता है; मुझे मिलता है -bash: 3: Bad file descriptor
जी-मैन

@ जी-मैन राइट, मैं भूल रहा हूँ कि बैश को पता नहीं है कि यह क्या कर रहा है, जब यह फाइल डिस्क्रिप्टर में आता है, तो आमतौर पर उपयोग किए जाने वाले गोले (व्यस्त बॉक्स के साथ आने वाली राख) के विपरीत। जब मैं एक कामचलाऊ सोचता हूं जो आपको खुश करता है तो मैं आपको बता देता हूं। इस बीच यदि आपको एक डेबियन बॉक्स काम मिला है, तो आप इसे डैश में आज़मा सकते हैं, या अगर आपको बिजीबॉक्स काम मिल गया है, तो आप इसे बिज़ीबॉक्स ऐश / श के साथ आज़मा सकते हैं।
मृत्युंजय

@ जी-मैन जैसा कि मैं कमांड को क्या करने की उम्मीद करता हूं, और यह अन्य गोले में क्या करता है, कमांड 1 से पुन: निर्देशित है, इसलिए यह कमांड प्रतिस्थापन द्वारा पकड़ा नहीं जाता है, लेकिन एक बार कमांड प्रतिस्थापन के बाहर, यह गिर जाता है fd3 वापस stdout करने के लिए तो यह कमांड 2 की अपेक्षा के अनुसार पाइप किया गया है। जब कमांड 1 बाहर निकलता है, तो प्रिंटफ़ फायर करता है और अपनी निकास स्थिति को प्रिंट करता है, जिसे कमांड प्रतिस्थापन द्वारा चर में कैप्चर किया जाता है। यहाँ बहुत विस्तृत विराम: stackoverflow.com/questions/985876/tee-and-exit-status/… इसके अलावा, आपकी उस टिप्पणी को पढ़ा जाता है जैसे कि यह थोड़े अपमानजनक था?
मृत्युंजय

मुझे कहाँ से शुरू करना चाहिए? (१) मुझे खेद है अगर आपको अपमान महसूस हुआ। "दिलचस्प लग रहा है" ईमानदारी से मतलब था; यह बहुत अच्छा होगा अगर कुछ उतना ही कॉम्पैक्ट हो जितना कि काम किया और साथ ही साथ आपने उससे उम्मीद की। इसके अलावा, मैं कह रहा था, बस, कि मुझे समझ नहीं आया कि आपका समाधान क्या करना चाहिए था। मैं लंबे समय से ( यूनिक्स से पहले) यूनिक्स के साथ काम कर रहा हूं , और, अगर मुझे कुछ समझ नहीं आ रहा है, तो यह एक लाल झंडा है, हो सकता है कि , अन्य लोग इसे या तो समझ नहीं पाएंगे, और यह अधिक स्पष्टीकरण (IMNSHO) की आवश्यकता है। … (Cont'd)
G-Man

(Cont'd) ... चूंकि आप "... यह सोचना पसंद करते हैं ... कि [आप] औसत व्यक्ति की तुलना में हर चीज के बारे में अधिक समझते हैं", शायद आपको यह याद रखना चाहिए कि स्टैक एक्सचेंज का उद्देश्य एक कमांड-लेखन सेवा नहीं है, मंथन करना तुच्छ अलग-अलग सवालों के हजारों एक-बंद समाधान; बल्कि लोगों को अपनी समस्याओं को हल करने के तरीके सिखाने के लिए। और, उस अंत तक, शायद आपको सामान को अच्छी तरह से समझाने की ज़रूरत है कि एक "औसत व्यक्ति" इसे समझ सकता है। एक उत्कृष्ट व्याख्या के उदाहरण के लिए lesmana के उत्तर को देखें। … (Cont'd)
G-Man


7

ऊपर दिए गए नेमन उपप्रकारों को शुरू करने के { .. }बजाय उपमा के बिना भी ऊपर दिए गए घाव का समाधान किया जा सकता है (यह याद रखना कि समूहीकृत कमांड के इस रूप को हमेशा अर्धविराम के साथ समाप्त करना होता है)। कुछ इस तरह:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

मैंने इस निर्माण की जाँच डैश संस्करण 0.5.5 और बैश संस्करणों 3.2.25 और 4.2.42 के साथ की है, इसलिए भले ही कुछ गोले { .. }समूहीकरण का समर्थन न करें , लेकिन यह अभी भी पोसिक्स अनुरूप है।


यह सबसे अधिक गोले के साथ वास्तव में अच्छी तरह से काम करता है जो मैंने इसके साथ कोशिश की है, जिसमें NetBSD श, pdksh, mksh, dash, bash शामिल हैं। हालाँकि, मुझे एटी एंड टी क्ष (93s +, 93u +) या zsh (4.3.9, 5.2) के साथ काम करने के लिए नहीं मिल सकता है, यहां तक ​​कि set -o pipefailksh या किसी भी संख्या में स्प्रिंकल waitकमांड के साथ भी। मुझे लगता है कि यह कम से कम, भाग में, ksh के लिए एक पार्सिंग मुद्दा हो सकता है, जैसे कि अगर मैं उपधाराओं का उपयोग करने के लिए चिपक जाता हूं तो यह ठीक काम करता है, लेकिन यहां तक ​​कि ifksh के लिए उप संस्करण को चुनने के लिए लेकिन दूसरों के लिए यौगिक आदेशों को छोड़ दें, यह विफल रहता है ।
ग्रेग ए। वुड्स

4

यह पोर्टेबल है, यानी किसी भी POSIX अनुरूप शेल के साथ काम करता है, वर्तमान निर्देशिका को लिखने योग्य होने की आवश्यकता नहीं है और एक ही चाल को एक साथ चलाने के लिए कई लिपियों का उपयोग करने की अनुमति देता है।

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

संपादित करें: यहां गिल्स की टिप्पणियों के बाद एक मजबूत संस्करण है:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2: और यहां कुछ हल्का संस्करण है, जो डबियसिम टिप्पणी के बाद है:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))

3
यह कई कारणों से काम नहीं करता है। 1. अस्थायी फ़ाइल को लिखे जाने से पहले पढ़ा जा सकता है। 2. एक अनुमानित नाम के साथ एक साझा निर्देशिका में एक अस्थायी फ़ाइल बनाना असुरक्षित है (तुच्छ DoS, सिमलिंक दौड़)। 3. यदि एक ही स्क्रिप्ट कई बार इस ट्रिक का उपयोग करती है, तो यह हमेशा एक ही फ़ाइल नाम का उपयोग करेगी। 1 को हल करने के लिए, पाइपलाइन पूरा होने के बाद फ़ाइल को पढ़ें। 2 और 3 को हल करने के लिए, बेतरतीब ढंग से उत्पन्न नाम के साथ या एक निजी निर्देशिका में एक अस्थायी फ़ाइल का उपयोग करें।
गिल्स

+1 खैर $ {PIPESTATUS [0]} आसान है, लेकिन यहां मूल विचार काम करता है अगर कोई उन समस्याओं के बारे में जानता है जो गिल्स का उल्लेख करते हैं।
जोहान

आप कुछ उपधाराएं बचा सकते हैं (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)):। @ जोहान: मैं मानता हूं कि यह बैश के साथ आसान है, लेकिन कुछ संदर्भों में, यह जानने के लिए कि बैश से कैसे बचा जाए।
dubiousjim

4

यदि आप सामान्य समाधानों में से एक का उपयोग करने में सक्षम नहीं हैं, तो @Patrik के उत्तर के लिए एक ऐडऑन के रूप में निम्नलिखित है।

यह उत्तर मानता है:

  • आप एक खोल जो के बारे में पता नहीं है $PIPESTATUSऔर न हीset -o pipefail
  • आप समानांतर निष्पादन के लिए एक पाइप का उपयोग करना चाहते हैं, इसलिए कोई अस्थायी फ़ाइलें नहीं।
  • यदि आप स्क्रिप्ट को बाधित करते हैं, तो संभवत: आपके पास अतिरिक्त अव्यवस्था नहीं होनी चाहिए, संभवतः अचानक बिजली आउटेज द्वारा।
  • यह समाधान पढ़ने के लिए अपेक्षाकृत आसान और स्वच्छ होना चाहिए।
  • आप अतिरिक्त उपधाराओं को लागू नहीं करना चाहते हैं।
  • आप मौजूदा फ़ाइल डिस्क्रिप्टर के साथ फ़ाइड नहीं कर सकते, इसलिए स्टडिन / आउट / इरिट को छुआ नहीं जाना चाहिए (हालाँकि आप कुछ नए को अस्थायी रूप से पेश कर सकते हैं)

अतिरिक्त मान्यताओं। आप सभी से छुटकारा पा सकते हैं, लेकिन यह नुस्खा बहुत ज्यादा पसंद करता है, इसलिए यह यहां नहीं है:

  • आप सभी यह जानना चाहते हैं कि PIPE में सभी कमांड्स का कोड 0 है।
  • आपको अतिरिक्त साइड बैंड जानकारी की आवश्यकता नहीं है।
  • आपका शेल सभी पाइप कमांड के लौटने का इंतजार करता है।

इससे पहले: foo | bar | bazहालाँकि, यह केवल अंतिम कमांड का निकास कोड देता है ( baz)

वांटेड: (सत्य) $?नहीं होना चाहिए 0, यदि पाइप में कोई भी कमांड विफल हो गया है

उपरांत:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

# $? now is 0 only if all commands had exit code 0

व्याख्या की:

  • के साथ एक अस्थायी बनाया जाता है mktemp। यह आमतौर पर तुरंत एक फ़ाइल बनाता है/tmp
  • इस टेंपरेचर को फिर लिखने के लिए FD 9 और रीडिंग के लिए FD 8 पर रीडायरेक्ट किया जाता है
  • फिर टेंपरेरी तुरंत हटा दिया जाता है। यह खुला रहता है, हालांकि, जब तक कि दोनों एफडी अस्तित्व से बाहर नहीं जाते हैं।
  • अब पाइप शुरू किया गया है। प्रत्येक चरण केवल एफडी 9 में जुड़ता है, अगर कोई त्रुटि थी।
  • के waitलिए आवश्यक है ksh, क्योंकि kshअन्य सभी पाइप कमांड के खत्म होने का इंतजार नहीं करते हैं। हालाँकि कृपया ध्यान दें कि यदि कुछ पृष्ठभूमि कार्य मौजूद हैं, तो अवांछित साइडइफेक्ट्स हैं, इसलिए मैंने इसे डिफ़ॉल्ट रूप से टिप्पणी की। यदि प्रतीक्षा में दर्द नहीं होता है, तो आप इसे टिप्पणी कर सकते हैं।
  • बाद में फ़ाइल की सामग्री पढ़ी जाती है। यदि यह खाली है (क्योंकि सभी काम किया है) readरिटर्न देता है false, तो trueएक त्रुटि इंगित करता है

यह एक एकल आदेश के लिए एक प्लगइन प्रतिस्थापन के रूप में इस्तेमाल किया जा सकता है और केवल निम्नलिखित की आवश्यकता है:

  • अप्रयुक्त एफडी 9 और 8
  • टेम्पोफाइल का नाम रखने के लिए एक एकल पर्यावरण चर
  • और यह नुस्खा काफी किसी भी शेल के लिए अनुकूलित किया जा सकता है जो आईओ पुनर्निर्देशन की अनुमति देता है
  • इसके अलावा यह काफी मंच अज्ञेय है और इसे जैसी चीजों की आवश्यकता नहीं है /proc/fd/N

कीड़े:

इस स्क्रिप्ट में एक बग /tmpहै जो अंतरिक्ष से बाहर चलता है। आप इस कृत्रिम मामले के खिलाफ संरक्षण की जरूरत है, भी, आप इसे इस रूप में कर सकते हैं, लेकिन इस नुकसान है, कि की संख्या 0में 000पाइप में आदेशों की संख्या पर निर्भर करता है, इसलिए यह थोड़ा और अधिक जटिल है:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

पोर्टबलिटी नोट:

  • kshऔर इसी तरह के गोले जो केवल अंतिम पाइप कमांड के लिए प्रतीक्षा करते हैं, को waitअनियोजित करने की आवश्यकता होती है

  • अंतिम उदाहरण printf "%1s" "$?"इसके बजाय उपयोग करता है echo -n "$?"क्योंकि यह अधिक पोर्टेबल है। हर प्लेटफ़ॉर्म -nसही ढंग से व्याख्या नहीं करता है।

  • printf "$?"printf "%1s"यदि आप स्क्रिप्ट को किसी सचमुच टूटे हुए प्लेटफ़ॉर्म पर चलाते हैं , तो यह भी करेगा । (पढ़ें: यदि आप में कार्यक्रम करने के लिए होता है paranoia_mode=extreme।)

  • एफडी 8 और एफडी 9 उन प्लेटफार्मों पर अधिक हो सकते हैं जो कई अंकों का समर्थन करते हैं। AFAIR एक POSIX अनुरूप शेल को केवल एकल अंकों का समर्थन करने की आवश्यकता होती है।

  • डेबियन 8.2 के साथ परीक्षण किया गया था sh, bash, ksh, ash, sashऔर यहां तक किcsh


3

थोड़ी सावधानी के साथ, यह काम करना चाहिए:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)

जैलगेरे की तरह सफाई कैसे करें? क्या आप फू-स्टेटस नामक फ़ाइल नहीं छोड़ते हैं?
जोहान

@ जोहान: यदि आप मेरे सुझाव को पसंद करते हैं, तो इसे वोट करने में संकोच न करें;) एक फ़ाइल को छोड़ने के अलावा, इसमें कई प्रक्रियाओं को एक साथ चलाने की अनुमति देने का लाभ है और वर्तमान निर्देशिका को लिखने योग्य नहीं होने की आवश्यकता है।
जूलियरे

2

निम्न 'यदि' ब्लॉक तभी चलेगा जब 'कमांड' सफल हुआ:

if command; then
   # ...
fi

विशेष रूप से बोलते हुए, आप कुछ इस तरह से चला सकते हैं:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

जो haconf -makerwअपने स्टडआउट और stderr को "$ haconf_out" पर चलाएगा और स्टोर करेगा । यदि से लौटाया गया मान haconfसत्य है, तो 'if' ब्लॉक निष्पादित grepहोगा और "$ haconf_out" को पढ़ेगा, इसे "क्लस्टर पहले से ही लिखने योग्य" के विरुद्ध मिलान करने की कोशिश करेगा।

ध्यान दें कि पाइप स्वचालित रूप से खुद को साफ करते हैं; पुनर्निर्देशन के साथ आपको "$ haconf_out" को हटाने के लिए सावधानी बरतनी होगी।

के रूप में के रूप में सुरुचिपूर्ण नहीं है pipefail, लेकिन एक वैध विकल्प अगर यह कार्यक्षमता पहुंच के भीतर नहीं है।


1
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$

0

(कम से कम बैश के साथ) set -eएक के साथ संयुक्त एक पाइपलाइफ का स्पष्ट रूप से अनुकरण करने और पाइप की त्रुटि पर बाहर निकलने के लिए उपधारा का उपयोग कर सकता है

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

तो अगर fooकिसी कारण से विफल हो जाता है - बाकी कार्यक्रम निष्पादित नहीं किया जाएगा और स्क्रिप्ट इसी त्रुटि कोड के साथ बाहर निकलता है। (यह मानता है कि fooअपनी त्रुटि को छापता है, जो विफलता के कारण को समझने के लिए पर्याप्त है)


-1

संपादित करें : यह उत्तर गलत है, लेकिन दिलचस्प है, इसलिए मैं इसे भविष्य के संदर्भ के लिए छोड़ दूंगा।


!कमांड में एक जोड़ने पर रिटर्न कोड को निष्क्रिय कर देता है।

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command     # bash: bogus_command: command not found
echo $?                # 127

! ls | bogus_command   # bash: bogus_command: command not found
echo $?                # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #

1
मुझे लगता है कि यह असंबंधित है। अपने उदाहरण में, मैं के निकास कोड जानना चाहते हैं ls, के निकास कोड नहीं उलटनेbogus_command
माइकल Mrozek

2
मैं उस उत्तर को वापस लेने का सुझाव देता हूं।
मैक्सक्लेपजिग

3
खैर, ऐसा प्रतीत होता है कि मैं एक बेवकूफ हूँ। मैं वास्तव में एक स्क्रिप्ट में इसका उपयोग करने से पहले यह सोचता हूं कि यह ओपी चाहता था। अच्छी बात यह है कि मैंने इसे किसी भी महत्वपूर्ण चीज के लिए इस्तेमाल नहीं किया
फाल्मरी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.