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




bash shell (10)

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

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

command | tee out.txt
ST=$?

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

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


@ ब्रायन-एस-विल्सन के जवाब पर आधार; यह बाश सहायक समारोह:

pipestatus() {
  local S=("${PIPESTATUS[@]}")

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]
  fi
}

इस प्रकार इस्तेमाल किया:

1: get_bad_things सफल होना चाहिए, लेकिन इसे कोई आउटपुट नहीं देना चाहिए; लेकिन हम आउटपुट देखना चाहते हैं कि यह उत्पादन करता है

get_bad_things | grep '^'
pipeinfo 0 1 || return

2: सभी पाइपलाइन सफल होना चाहिए

thing | something -q | thingy
pipeinfo || return

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


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

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

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

# 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 निष्पादित करेगा और stdout (फ़ाइल डिस्क्रिप्टर 1) पर अपने नियमित आउटपुट को प्रिंट करेगा, फिर एक बार यह हो जाने के बाद, printf निष्पादित करेगा और आईएसओएमएंड 1 के एक्ज़िट कोड को अपने स्टडआउट पर प्रिंट करेगा, लेकिन उस स्टडआउट को रीडायरेक्ट किया गया है फाइल डिस्क्रिप्टर 3।

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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


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

परिस्थिति:

someprog | filter

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

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

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

echo $?

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


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

% 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 को कैप्चर करने की अनुमति देती है ताकि यदि कोई त्रुटि उत्पन्न होती है तो आप लॉग फ़ाइल में भी डंप कर सकते हैं, या अगर कोई त्रुटि नहीं है (डीडी के आउटपुट की तरह) तो इसे हटा दें।


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

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

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

set -o pipefail
...

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


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





tee