क्या interleaving से stdout / stderr को रोकता है?


14

कहो कि मैं कुछ प्रक्रियाएँ चलाता हूँ:

#!/usr/bin/env bash

foo &
bar &
baz &

wait;

मैं उपरोक्त स्क्रिप्ट को इस तरह चलाता हूं:

foobarbaz | cat

जहां तक ​​मैं बता सकता हूं, जब कोई भी प्रक्रिया stdout / stderr को लिखती है, तो उनका आउटपुट कभी भी इंटरलेव नहीं होता है - स्टीडियो की प्रत्येक पंक्ति परमाणु लगती है। वह कैसे काम करता है? क्या उपयोगिता नियंत्रित करती है कि प्रत्येक रेखा परमाणु कैसे है?


3
आपका कमांड कितना डेटा आउटपुट करता है? उन्हें कुछ किलोबाइट उत्पादन करने की कोशिश करें।
Kusalananda

आप मतलब है, जहां एक आदेश एक newline से पहले कुछ kb outputs?
अलेक्जेंडर मिल्स

नहीं, कुछ इस तरह से: unix.stackexchange.com/a/452762/70524
muru

जवाबों:


23

वे इंटरलेव करते हैं! आपने केवल लघु आउटपुट फटने की कोशिश की, जो कि अनिश्चित रहता है, लेकिन व्यवहार में यह गारंटी देना कठिन है कि कोई विशेष आउटपुट अप्राप्त रहता है।

आउटपुट बफरिंग

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

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

प्रोग्राम प्रत्येक फ़ाइल को अलग तरीके से व्यवहार करने के लिए फटकार लगा सकते हैं, और बफर को स्पष्ट रूप से फ्लश कर सकते हैं। जब प्रोग्राम फ़ाइल बंद कर देता है या सामान्य रूप से बाहर निकल जाता है तो बफर अपने आप फ्लश हो जाता है।

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

यहां एक उदाहरण है जहां मैं दो कार्यक्रमों से आउटपुट को इंटरलेव करता हूं। मैंने लिनक्स पर GNU कोरुटिल्स का उपयोग किया; इन उपयोगिताओं के विभिन्न संस्करण अलग-अलग व्यवहार कर सकते हैं।

  • yes aaaaaaaaहमेशा के लिए लिखता है जो अनिवार्य रूप से लाइन-बफ़र मोड के बराबर है। yesउपयोगिता वास्तव में एक समय में कई पंक्तियों लिखते हैं, लेकिन हर बार यह उत्पादन का उत्सर्जन करता है, उत्पादन लाइनों की एक पूर्ण संख्या है।
  • echo bbbb; done | grep bbbbbपूरी तरह से बफर मोड में हमेशा के लिए लिखता है। यह 8192 के बफर आकार का उपयोग करता है, और प्रत्येक पंक्ति 5 बाइट्स लंबी है। चूंकि 5 8192 को विभाजित नहीं करता है, इसलिए लिखने के बीच की सीमाएं सामान्य रूप से एक सीमा रेखा पर नहीं होती हैं।

चलो उन्हें एक साथ पिच करते हैं।

$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa

जैसा कि आप देख सकते हैं, हाँ कभी-कभी बाधित grep और इसके विपरीत। केवल 0.001% लाइनें बाधित हुईं, लेकिन ऐसा हुआ। आउटपुट को यादृच्छिक किया जाता है, इसलिए रुकावटों की संख्या अलग-अलग होगी, लेकिन मैंने हर बार कम से कम कुछ व्यवधानों को देखा। यदि लाइनें लंबी थीं, तो बाधित लाइनों का एक बड़ा हिस्सा होगा, क्योंकि एक बाधा की संभावना बढ़ जाती है क्योंकि प्रति बफर लाइनों की संख्या कम हो जाती है।

आउटपुट बफ़रिंग को समायोजित करने के कई तरीके हैं । मुख्य हैं:

  • stdbuf -o0GNU Coreutils और FreeBSD जैसे कुछ अन्य सिस्टमों में पाए गए प्रोग्राम के साथ अपनी डिफ़ॉल्ट सेटिंग्स को बदले बिना उन प्रोग्रामों में बफ़रिंग का उपयोग करें जो stdio लाइब्रेरी को बंद करते हैं । आप वैकल्पिक रूप से लाइन बफरिंग के साथ स्विच कर सकते हैं stdbuf -oL
  • बस इस उद्देश्य के लिए बनाए गए टर्मिनल के माध्यम से प्रोग्राम के आउटपुट को निर्देशित करके लाइन बफरिंग पर स्विच करें unbuffer। कुछ कार्यक्रम अन्य तरीकों से अलग तरह से व्यवहार कर सकते हैं, उदाहरण के लिए grepडिफ़ॉल्ट रूप से रंगों का उपयोग करता है यदि इसका आउटपुट एक टर्मिनल है।
  • उदाहरण के लिए, --line-bufferedGNU grep पास करके प्रोग्राम को कॉन्फ़िगर करें ।

चलो फिर से ऊपर स्निपेट देखें, इस बार दोनों तरफ लाइन बफरिंग के साथ।

{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb

तो इस बार हाँ कभी बाधित नहीं किया, लेकिन कभी कभी हाँ बाधित। मैं बाद में क्यों आता हूँ।

पाइप इंटरलेइंग

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

यदि पाइप के ट्रांसफर बफर में फिट होने की तुलना में कॉपी करने के लिए अधिक डेटा है, तो कर्नेल एक बार में एक बफर कॉपी करता है। यदि कई प्रोग्राम एक ही पाइप से लिख रहे हैं, और पहला प्रोग्राम जो कर्नेल चुनता है, वह एक से अधिक बफर लिखना चाहता है, तो इस बात की कोई गारंटी नहीं है कि कर्नेल दूसरी बार फिर से उसी प्रोग्राम को चुनेगा। उदाहरण के लिए, यदि P बफर आकार है, foo2 * P बाइट्स barलिखना चाहता है और 3 बाइट्स लिखना चाहता है, तो एक संभावित इंटरलेविंग P बाइट्स है foo, फिर 3 बाइट्स bar, और P बाइट्स से foo

ऊपर हाँ + grep उदाहरण के लिए वापस आ रहा है, मेरे सिस्टम पर, yes aaaaएक ही बार में 8192-बाइट बफर में फिट होने वाली कई पंक्तियों को लिखने के लिए होता है। चूंकि लिखने के लिए 5 बाइट्स हैं (4 प्रिंट करने योग्य अक्षर और नई पंक्ति), इसका मतलब है कि यह हर बार 8190 बाइट्स लिखता है। पाइप बफर का आकार 4096 बाइट्स है। इसलिए हाँ से 4096 बाइट्स प्राप्त करना संभव है, फिर क्रेप से कुछ आउटपुट, और फिर हां (8190 - 4096 = 4094 बाइट्स) से बाकी लेखन। 4096 बाइट्स 819 लाइनों के साथ aaaaऔर एक लोन के लिए जगह छोड़ती है a। इसलिए इस लाइन के साथ एक लाइन agrep से लिखी जाती है, जिसके साथ एक लाइन दी जाती है abbbb

यदि आप क्या चल रहा है, इसका विवरण देखना चाहते हैं, तो getconf PIPE_BUF .आपको अपने सिस्टम पर पाइप बफर आकार बताएगा, और आप प्रत्येक प्रोग्राम द्वारा किए गए सिस्टम कॉल की पूरी सूची देख सकते हैं

strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba

स्वच्छ रेखा इंटरलायिंग की गारंटी कैसे दें

यदि लाइन की लंबाई पाइप बफर आकार से छोटी है, तो लाइन बफरिंग गारंटी देती है कि आउटपुट में कोई मिश्रित लाइन नहीं होगी।

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


दिलचस्प है, इसलिए यह सुनिश्चित करने का एक अच्छा तरीका क्या हो सकता है कि सभी लाइनें catपरमाणु रूप से लिखी गई थीं , जैसे कि बिल्ली प्रक्रिया को पूरी पंक्तियाँ या तो फू / बार / बाज से प्राप्त होती हैं, लेकिन एक से आधी रेखा और दूसरी से आधी रेखा नहीं होती है, आदि। क्या मैं बैश स्क्रिप्ट के साथ कुछ कर सकता हूं?
अलेक्जेंडर मिल्स

1
लगता है कि यह मेरे मामले में भी लागू होता है, जहां मेरे पास सैकड़ों फाइलें थीं और awkएक ही आईडी के लिए आउटपुट की दो (या अधिक) लाइनें बनाई गई थीं, find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }' लेकिन इसके साथ find -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'हर आईडी के लिए सही ढंग से केवल एक लाइन का उत्पादन किया गया था।
α atsнιη

किसी भी इंटरलेक्चरिंग को रोकने के लिए, मैं Node.js की तरह प्रोग्रामिंग एनवी में कर सकता हूं, लेकिन बैश / शेल के साथ, यह सुनिश्चित करने के लिए कि यह कैसे करना है।
अलेक्जेंडर मिल्स

1
@JoL यह पाइप बफर भरने के कारण है। मुझे पता था कि मुझे कहानी का दूसरा भाग लिखना होगा ... हो गया।
गाइल्स का SO- बुराई पर रोक '21

1
@OlegzandrDenman TLDR ने कहा: वे इंटरलेव करते हैं। इसकी वजह जटिल है।
गिल्स एसओ- बुराई को रोकना '

1

http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P में इस पर नज़र डाली गई है:

GNU xargs समानांतर में कई नौकरियों को चलाने का समर्थन करता है। -पी n जहां समानांतर में चलने वाली नौकरियों की संख्या है।

seq 100 | xargs -n1 -P10 echo "$a" | grep 5
seq 100 | xargs -n1 -P10 echo "$a" > myoutput.txt

यह कई स्थितियों के लिए ठीक काम करेगा लेकिन इसमें एक भ्रामक दोष है: यदि $ में ~ 1000 से अधिक वर्ण हैं, तो प्रतिध्वनि परमाणु नहीं हो सकती है (इसे कई राइट्स () कॉल में विभाजित किया जा सकता है), और एक जोखिम है कि दो लाइनें मिलाया जाएगा।

$ perl -e 'print "a"x2000, "\n"' > foo
$ strace -e write bash -c 'read -r foo < foo; echo "$foo"' >/dev/null
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 1008) = 1008
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 993) = 993
+++ exited with 0 +++

जाहिर है कि एक ही मुद्दा उठता है अगर वहाँ कई कॉल गूंज या प्रिंट करने के लिए कर रहे हैं:

slowprint() {
  printf 'Start-%s ' "$1"
  sleep "$1"
  printf '%s-End\n' "$1"
}
export -f slowprint
seq 10 | xargs -n1 -I {} -P4 bash -c "slowprint {}"
# Compare to no parallelization
seq 10 | xargs -n1 -I {} bash -c "slowprint {}"
# Be sure to see the warnings in the next Pitfall!

समानांतर नौकरियों से आउटपुट को एक साथ मिलाया जाता है, क्योंकि प्रत्येक नौकरी में दो (या अधिक) अलग-अलग लेखन () कॉल होते हैं।

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


वह धारा गलत है। xargs echoइको बैश बिलिन को कॉल नहीं करता है, लेकिन echoउपयोगिता से $PATH। और वैसे भी मैं bash 4.4 के साथ उस बैश इको व्यवहार को पुन: पेश नहीं कर सकता। लिनक्स पर, 4K से बड़ा एक पाइप (नहीं / dev / null) को लिखता है, हालांकि परमाणु होने की गारंटी नहीं है।
स्टीफन चेज़लस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.