शेल पाइप में त्रुटि कोड पकड़ना


97

मेरे पास वर्तमान में एक स्क्रिप्ट है जो कुछ ऐसा करती है

./a | ./b | ./c

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

ऐसा करने का सबसे सरल / साफ तरीका क्या होगा?


7
वास्तव में कुछ ऐसा करने की आवश्यकता है, &&|जिसका अर्थ होगा "केवल पाइप जारी रखें यदि पूर्ववर्ती आदेश सफल था"। मुझे लगता है कि आप भी हो सकते हैं, |||जिसका अर्थ होगा "पाइप जारी रखें यदि पूर्ववर्ती आदेश विफल हो गया" (और संभवतः बैश 4 की तरह त्रुटि संदेश को पाइप कर सकता है |&)।
अगली सूचना तक रोक दिया गया।

6
@ डेनिसविलियम्सन, आप "पाइप को रोक नहीं सकते" a, क्योंकि b, cकमांड क्रमिक रूप से नहीं बल्कि समानांतर में चलाए जाते हैं। दूसरे शब्दों में, डेटा क्रमिक रूप से प्रवाहित होता aहै c, लेकिन वास्तविक a, bऔर cकमांड एक ही समय में शुरू होता है (लगभग)।
जियाकोमो

जवाबों:


20

यदि आप वास्तव में नहीं चाहते हैं कि दूसरा कमांड तब तक आगे बढ़े जब तक कि पहला सफल होने के लिए नहीं जाना जाता है, तो आपको संभवतः अस्थायी फ़ाइलों का उपयोग करने की आवश्यकता है। इसका सरल संस्करण है:

tmp=${TMPDIR:-/tmp}/mine.$$
if ./a > $tmp.1
then
    if ./b <$tmp.1 >$tmp.2
    then
        if ./c <$tmp.2
        then : OK
        else echo "./c failed" 1>&2
        fi
    else echo "./b failed" 1>&2
    fi
else echo "./a failed" 1>&2
fi
rm -f $tmp.[12]

'1> और 2' पुनर्निर्देशन भी संक्षिप्त किया जा सकता है '> और 2'; हालाँकि, MKS शेल के एक पुराने संस्करण ने पूर्ववर्ती '1' के बिना त्रुटि पुनर्निर्देशन को गलत कर दिया है, इसलिए मैंने उम्र के लिए विश्वसनीयता के लिए उस अस्पष्ट अधिसूचना का उपयोग किया है।

यदि आप किसी चीज को बाधित करते हैं तो यह लीक हो जाती है। बम प्रूफ (अधिक या कम) शेल प्रोग्रामिंग का उपयोग करता है:

tmp=${TMPDIR:-/tmp}/mine.$$
trap 'rm -f $tmp.[12]; exit 1' 0 1 2 3 13 15
...if statement as before...
rm -f $tmp.[12]
trap 0 1 2 3 13 15

पहली ट्रैप लाइन कहती है 'कमांड्स चलाएं' rm -f $tmp.[12]; exit 1'जब कोई भी सिग्नल 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE, या 15 SIGTERM होते हैं, या 0 (जब शेल किसी भी कारण से बाहर निकलता है)। यदि आप एक शेल स्क्रिप्ट लिख रहे हैं, तो अंतिम ट्रैप को केवल 0 पर जाल को हटाने की आवश्यकता है, जो शेल एग्जिट ट्रैप है (आप इस तरह से अन्य संकेतों को छोड़ सकते हैं क्योंकि प्रक्रिया वैसे भी समाप्त होने वाली है)।

मूल पाइपलाइन में, 'ग' के लिए 'बी' से डेटा पढ़ना संभव है, इससे पहले कि 'ए' समाप्त हो गया है - यह आमतौर पर वांछनीय है (यह उदाहरण के लिए कई कोर काम करने के लिए देता है)। यदि 'b' एक 'सॉर्ट' चरण है, तो यह लागू नहीं होगा - 'b' को अपना कोई भी आउटपुट उत्पन्न करने से पहले उसके सभी इनपुट को देखना होगा।

यदि आप पता लगाना चाहते हैं कि कौन सी कमांड विफल है, तो आप उपयोग कर सकते हैं:

(./a || echo "./a exited with $?" 1>&2) |
(./b || echo "./b exited with $?" 1>&2) |
(./c || echo "./c exited with $?" 1>&2)

यह सरल और सममित है - यह 4-भाग या एन-भाग पाइपलाइन तक विस्तारित करने के लिए तुच्छ है।

'सेट-ई' के साथ सरल प्रयोग से कोई मदद नहीं मिली।


1
मैं mktempया उपयोग करने की सलाह दूंगा tempfile
अगली सूचना तक रोक दिया गया।

@ डेनिस: हाँ, मुझे लगता है कि मुझे इस तरह के मैकटैम्प या टैम्पाइल के रूप में कमांड करने की आदत डालनी चाहिए; जब मैंने इसे सीखा, तो वे इतने साल पहले शेल स्तर पर मौजूद नहीं थे। चलो एक त्वरित जांच करते हैं। मुझे मैकओएस एक्स पर मैकटेम मिल गया; मेरे पास सोलारिस पर mktemp है, लेकिन केवल इसलिए कि मैंने जीएनयू उपकरण स्थापित किए हैं; ऐसा लगता है कि एंटीक एचपी-यूएक्स पर मटकेप मौजूद है। मुझे यकीन नहीं है कि प्लेटफ़ॉर्म पर काम करने वाले mktemp का एक सामान्य आह्वान है या नहीं। POSIX न तो mktemp और न ही tmpfile का मानकीकरण करता है। मुझे उन प्लेटफार्मों पर tmpfile नहीं मिला, जिनकी पहुंच मेरे पास है। नतीजतन, मैं पोर्टेबल शेल स्क्रिप्ट में कमांड का उपयोग नहीं कर पाऊंगा।
जोनाथन लेफ्लर

1
उपयोग करते समय trapध्यान रखें कि उपयोगकर्ता हमेशा SIGKILLइसे समाप्त करने के लिए एक प्रक्रिया को भेज सकता है, जिस स्थिति में आपका जाल प्रभावी नहीं होगा। जब आपके सिस्टम को पावर आउटेज का अनुभव होता है, तब भी वही होता है। अस्थायी फ़ाइलें बनाते समय, उपयोग करना सुनिश्चित करें mktempक्योंकि यह आपकी फ़ाइलों को कहीं रख देगा, जहाँ उन्हें रिबूट (आमतौर पर /tmp) के बाद साफ किया जाता है ।
जॉस

155

में बैश आप उपयोग कर सकते हैं set -eऔर set -o pipefailअपनी फ़ाइल की शुरुआत में। ./a | ./b | ./cतीन स्क्रिप्ट्स में से कोई भी विफल होने पर एक बाद की कमांड विफल हो जाएगी। रिटर्न कोड पहली असफल स्क्रिप्ट का रिटर्न कोड होगा।

ध्यान दें कि pipefailमानक में उपलब्ध नहीं है ।


मुझे पता नहीं था कि वास्तव में बहुत आसान है।
फिल जैक्सन

यह एक स्क्रिप्ट में डालने का इरादा है, इंटरैक्टिव शेल में नहीं। आपके व्यवहार को सेट करने के लिए सरलीकृत किया जा सकता है; असत्य; यह शेल प्रक्रिया से भी बाहर निकलता है, जो अपेक्षित व्यवहार है;)
मिशेल सामिया

11
नोट: यह अभी भी सभी तीन लिपियों को निष्पादित करेगा, और पहली त्रुटि पर पाइप को बंद नहीं करेगा।
हाइड

1
@ n2liquid-GuilhermeVieira Btw, "विभिन्न रूपांतरों" से, मेरा विशेष रूप से मतलब था, set( एक या दोनों को अलग-अलग कुल 4 संस्करणों के लिए), और देखें कि अंतिम के आउटपुट को कैसे प्रभावित करता है echo
हाइड

1
@ जोश मुझे यह पेज तब मिला जब मुझे यह पता चला कि इसे गूगल से कैसे किया जाए और हालांकि, "यह वही है जो मैं खोज रहा हूं"। मुझे ऐसे कई लोगों पर संदेह है जो उत्तर को उकसाते हैं, एक समान विचार प्रक्रिया से गुजरते हैं और टैग और टैग की परिभाषा की जाँच नहीं करते हैं।
ट्रॉय डेनियल

43

आप ${PIPESTATUS[]}पूर्ण निष्पादन के बाद भी सरणी की जांच कर सकते हैं , जैसे यदि आप चलाते हैं:

./a | ./b | ./c

तब ${PIPESTATUS}पाइप में प्रत्येक कमांड से एरर कोड्स की एक सरणी होगी, इसलिए यदि मध्य कमांड विफल हो गया, echo ${PIPESTATUS[@]}तो उसमें कुछ ऐसा होगा:

0 1 0

और कमांड के बाद ऐसा कुछ चलता है:

test ${PIPESTATUS[0]} -eq 0 -a ${PIPESTATUS[1]} -eq 0 -a ${PIPESTATUS[2]} -eq 0

आपको यह जांचने की अनुमति देगा कि पाइप में सभी कमांड सफल रहे।


11
यह बैश है --- यह बैश एक्सटेंशन है और पॉज़िक्स मानक का हिस्सा नहीं है, इसलिए डैश और ऐश जैसे अन्य गोले इसका समर्थन नहीं करेंगे। इसका मतलब है कि यदि आप इसे स्क्रिप्ट में उपयोग करने की कोशिश करते हैं तो आप मुसीबत में पड़ सकते हैं #!/bin/sh, जो कि शुरू होता है , क्योंकि अगर shयह नहीं है, तो यह काम नहीं करेगा। ( #!/bin/bashइसके बजाय उपयोग करने के लिए याद करके आसानी से तय करने योग्य।)
डेविड ने

2
echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'- रिटर्न 0 यदि $PIPESTATUS[@]केवल 0 और रिक्त स्थान (यदि पाइप में सभी आदेशों सफल होता है) शामिल हैं।
मैटबियानको

@MattBianco यह मेरे लिए सबसे अच्छा समाधान है। यह && उदाहरण के साथ भी काम करता है। command1 && command2 | command3यदि कोई भी आपका समाधान विफल हो जाता है, तो वह शून्य नहीं होता है।
डेविड सीपी

8

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

यहां एक समाधान है जो पाइप में सभी त्रुटियों को पकड़ता है और न केवल पिछले घटक की त्रुटियां। तो यह बैश के पिपलीफल की तरह है, इस अर्थ में अधिक शक्तिशाली है कि आप सभी त्रुटि कोड प्राप्त कर सकते हैं ।

res=$( (./a 2>&1 || echo "1st failed with $?" >&2) |
(./b 2>&1 || echo "2nd failed with $?" >&2) |
(./c 2>&1 || echo "3rd failed with $?" >&2) > /dev/null 2>&1)
if [ -n "$res" ]; then
    echo pipe failed
fi

यह पता लगाने के लिए कि क्या कुछ विफल हुआ, कोई echoभी कमांड विफल होने पर एक कमांड मानक त्रुटि पर प्रिंट करता है। फिर संयुक्त मानक त्रुटि आउटपुट को $resबाद में सहेजा और जांच की जाती है। यही कारण है कि सभी प्रक्रियाओं की मानक त्रुटि को मानक आउटपुट पर पुनर्निर्देशित किया जाता है। आप उस आउटपुट को भेज सकते हैं /dev/nullया इसे छोड़ सकते हैं क्योंकि अभी तक एक और संकेतक है कि कुछ गलत हो गया है। यदि आप /dev/nullअंतिम कमांड के आउटपुट को कहीं भी स्टोर करना चाहते हैं, तो आप अंतिम रीडायरेक्ट को एक फाइल से बदल सकते हैं ।

इस निर्माण के साथ और अधिक खेलने के लिए और अपने आप को यह समझाने के लिए कि यह वास्तव में वही करता है, जिसे मैंने प्रतिस्थापित किया ./a, ./bऔर ./cउपधाराओं द्वारा जो निष्पादित होते हैं echo, catऔर exit। आप इसका उपयोग यह जांचने के लिए कर सकते हैं कि यह निर्माण वास्तव में सभी आउटपुट को एक प्रक्रिया से दूसरी प्रक्रिया में अग्रेषित करता है और त्रुटि कोड सही तरीके से दर्ज किए जाते हैं।

res=$( (sh -c "echo 1st out; exit 0" 2>&1 || echo "1st failed with $?" >&2) |
(sh -c "cat; echo 2nd out; exit 0" 2>&1 || echo "2nd failed with $?" >&2) |
(sh -c "echo start; cat; echo end; exit 0" 2>&1 || echo "3rd failed with $?" >&2) > /dev/null 2>&1)
if [ -n "$res" ]; then
    echo pipe failed
fi
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.