linux बैश में पाइप आउटपुट और कैप्चर एक्जिट स्टेटस




bash shell (12)

बाश के बाहर, आप कर सकते हैं:

bash -o pipefail  -c "command1 | tee output"

यह उदाहरण के लिए निंजा स्क्रिप्ट में उपयोगी है जहां खोल /bin/sh होने की उम्मीद है।

मैं बैश में एक लंबे समय तक चलने वाले कमांड को निष्पादित करना चाहता हूं, और दोनों अपनी निकास स्थिति को कैप्चर करते हैं, और इसके आउटपुट को tee

तो मैं यह करता हूँ:

command | tee out.txt
ST=$?

समस्या यह है कि परिवर्तनीय एसटी tee के बाहर निकलने की स्थिति को कैप्चर करता है और कमांड की नहीं। इसे कैसे हल किया जा सकता है?

ध्यान दें कि आदेश लंबे समय तक चल रहा है और बाद में इसे देखने के लिए आउटपुट को फ़ाइल में रीडायरेक्ट करना मेरे लिए एक अच्छा समाधान नहीं है।


उबंटू और डेबियन में, आप अधिक apt-get install moreutils कर सकते हैं। इसमें mispipe नामक एक उपयोगिता है जो पाइप में पहले कमांड की निकास स्थिति देता है।


सादा बैश में ऐसा करने का सबसे आसान तरीका एक पाइपलाइन के बजाय प्रक्रिया प्रतिस्थापन का उपयोग करना है । कई मतभेद हैं, लेकिन संभवतः वे आपके उपयोग के मामले के लिए बहुत मायने रखते हैं:

  • पाइपलाइन चलाते समय, बैश प्रतीक्षा करता है जब तक कि सभी प्रक्रियाएं पूरी नहीं हो जातीं।
  • बैश करने के लिए Ctrl-C भेजना इसे पाइपलाइन की सभी प्रक्रियाओं को मारता है, न केवल मुख्य।
  • pipefail विकल्प और PIPESTATUS चर प्रतिस्थापन को संसाधित करने के लिए अप्रासंगिक हैं।
  • शायद अधिक

प्रक्रिया प्रतिस्थापन के साथ, बैश बस प्रक्रिया शुरू करता है और इसके बारे में भूल जाता है, यह jobs में भी दिखाई नहीं देता है।

अलग-अलग मतभेदों का उल्लेख, consumer < <(producer) और producer | consumer producer | consumer अनिवार्य रूप से समकक्ष हैं।

यदि आप फ़्लिप करना चाहते हैं कि कौन सा "मुख्य" प्रक्रिया है, तो आप प्रतिस्थापन की वस्तुएं और दिशा को producer > >(consumer) फ़्लिप करें। आपके मामले में:

command > >(tee out.txt)

उदाहरण:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world

$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

जैसा कि मैंने कहा, पाइप अभिव्यक्ति से मतभेद हैं। प्रक्रिया तब तक चलना बंद नहीं कर सकती है, जब तक कि यह पाइप बंद करने के प्रति संवेदनशील न हो। विशेष रूप से, यह चीजों को आपके stdout पर लिख सकता है, जो भ्रमित हो सकता है।


PIPESTATUS[0] संयोजित करके और PIPESTATUS[0] में exit कमांड निष्पादित करने का नतीजा, आप सीधे अपने प्रारंभिक कमांड के रिटर्न वैल्यू तक पहुंच सकते हैं:

command | tee ; ( exit ${PIPESTATUS[0]} )

यहां एक उदाहरण दिया गया है:

# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"

तुम्हे दूंगा:

return value: 1


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

परिस्थिति:

someprog | filter

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

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

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

echo $?

Unix.stackexchange.com पर एक ही प्रश्न के लिए मेरा उत्तर देखें कि यह कैसे काम करता है और कुछ चेतावनियों के विस्तृत विवरण के लिए।


एक सरणी है जो आपको पाइप में प्रत्येक कमांड की निकास स्थिति देता है।

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1

शुद्ध खोल समाधान:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

और अब दूसरी cat साथ false जगह बदल दी:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

कृपया ध्यान दें कि पहली बिल्ली भी असफल हो जाती है, क्योंकि यह उस पर stdout बंद हो जाता है। लॉग में असफल आदेशों का क्रम इस उदाहरण में सही है, लेकिन इस पर भरोसा न करें।

यह विधि व्यक्तिगत आदेशों के लिए stdout और stderr को कैप्चर करने की अनुमति देती है ताकि यदि कोई त्रुटि उत्पन्न होती है तो आप लॉग फ़ाइल में भी डंप कर सकते हैं, या अगर कोई त्रुटि नहीं है (डीडी के आउटपुट की तरह) तो इसे हटा दें।


यह कभी-कभी बाश के विवरण में खोदने के बजाय बाहरी कमांड का उपयोग करने के लिए सरल और स्पष्ट हो सकता है। pipeline , न्यूनतम प्रक्रिया स्क्रिप्टिंग भाषा execline , दूसरे कमांड के रिटर्न कोड के साथ निकलती है *, जैसे कि एक sh पाइपलाइन करता है, लेकिन sh विपरीत, यह पाइप की दिशा को उलट देता है, ताकि हम रिटर्न कोड को कैप्चर कर सकें निर्माता प्रक्रिया (नीचे सभी sh कमांड लाइन पर है, लेकिन execline स्थापित के साथ):

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world

$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt 
hello world

$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world

$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1

$ pipeline -w tee out.txt "" true; echo $?
0

$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

pipeline का उपयोग देशी बैश पाइपलाइनों के समान अंतर है क्योंकि बैश प्रक्रिया प्रतिस्थापन उत्तर #43972501 में उपयोग किया जाता है।

* असल में pipeline तब तक बाहर नहीं निकलती जब तक कोई त्रुटि न हो। यह दूसरे कमांड में निष्पादित करता है, इसलिए यह दूसरा आदेश है जो लौटने पर करता है।


$PIPESTATUS नामक एक आंतरिक बैश वैरिएबल है; यह एक सरणी है जो कमांड की आपकी पिछली अग्रभूमि पाइपलाइन में प्रत्येक कमांड की निकास स्थिति रखती है।

<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

या दूसरा विकल्प जो अन्य गोले (जैसे zsh) के साथ काम करता है, वह पाइपफेल को सक्षम करना होगा:

set -o pipefail
...

पहला विकल्प थोड़ा अलग सिंटैक्स के कारण zsh के साथ काम नहीं करता है।


बैश के set -o pipefail का उपयोग करना सहायक है

पाइपफेल: एक पाइपलाइन का रिटर्न वैल्यू गैर-शून्य स्थिति से बाहर निकलने के लिए अंतिम कमांड की स्थिति है, या शून्य अगर शून्य से शून्य स्थिति से बाहर निकलने पर कोई आदेश नहीं है


पाइप कमांड रिटर्न के तुरंत बाद PIPESTATUS [@] को सरणी में कॉपी किया जाना चाहिए। PIPESTATUS [@] के किसी भी पाठ सामग्री को मिट जाएगा। यदि आप सभी पाइप कमांड की स्थिति की जांच करने की योजना बनाते हैं तो इसे किसी अन्य सरणी पर कॉपी करें। "$?" "$ {PIPESTATUS [@]}" के अंतिम तत्व के समान मूल्य है, और इसे पढ़ने के लिए "$ {PIPESTATUS [@]}" को नष्ट करना प्रतीत होता है, लेकिन मैंने इसे बिल्कुल सत्यापित नहीं किया है।

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

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


गूंगा समाधान: उन्हें एक नामित पाइप (mkfifo) के माध्यम से कनेक्ट करना। फिर आदेश दूसरे चलाया जा सकता है।

 mkfifo pipe
 tee out.txt < pipe &
 command > pipe
 echo $?




tee