आप जीएनयू stdbuf की और एक संयोजन का उपयोग कर सकते peeसे moreutils :
echo "Hello world!" | stdbuf -o 1M pee cmd1 cmd2 cmd3 > output
पेशाब popen(3)उन 3 शेल कमांड लाइनों है और फिर freadइनपुट है और fwriteयह है सभी तीन, जो 1M करने के लिए बफ़र कर दिया जाएगा।
विचार यह है कि इनपुट के रूप में बड़ा बफर कम से कम हो। इस तरह से भले ही तीनों कमांड एक ही समय में शुरू किए गए हों, लेकिन वे केवल pee pcloseतीन कमांड क्रमिक रूप से आने वाले इनपुट को देखेंगे ।
प्रत्येक पर pclose, peeबफर को कमांड में फ्लश करता है और इसके समापन की प्रतीक्षा करता है। यह गारंटी देता है कि जब तक उन cmdxआदेशों से कुछ भी आउटपुट शुरू नहीं होता है, जब तक कि उन्हें कोई इनपुट नहीं मिला है (और एक प्रक्रिया को कांटा नहीं है जो उनके माता-पिता के वापस आने के बाद आउटपुट जारी रख सकते हैं), तीन कमांड का आउटपुट नहीं होगा interleaved।
वास्तव में, यह मेमोरी में एक अस्थायी फ़ाइल का उपयोग करने जैसा है, जिसमें यह कमी है कि 3 कमांड समवर्ती रूप से शुरू किए जाते हैं।
समवर्ती रूप से कमांड शुरू करने से बचने के लिए, आप peeशेल फ़ंक्शन के रूप में लिख सकते हैं :
pee() (
input=$(cat; echo .)
for i do
printf %s "${input%.}" | eval "$i"
done
)
echo "Hello world!" | pee cmd1 cmd2 cmd3 > out
लेकिन सावधान रहें कि zshNUL वर्णों के साथ बाइनरी इनपुट के लिए अन्य गोले विफल होंगे।
यह अस्थायी फ़ाइलों का उपयोग करने से बचता है, लेकिन इसका मतलब है कि पूरे इनपुट को मेमोरी में संग्रहीत किया जाता है।
किसी भी स्थिति में, आपको इनपुट को मेमोरी या टेम्प फाइल में कहीं स्टोर करना होगा।
वास्तव में, यह काफी दिलचस्प सवाल है, क्योंकि यह हमें यूनिक्स विचार की सीमा दिखाता है जिसमें कई सरल उपकरण एक ही काम में सहयोग करते हैं।
यहाँ, हम इस कार्य के लिए कई उपकरणों का सहयोग करना चाहते हैं:
- एक स्रोत कमांड (यहाँ
echo)
- प्रेषण कमांड (
tee)
- कुछ फिल्टर आदेश (
cmd1, cmd2, cmd3)
- और एक एकत्रीकरण कमांड (
cat)।
यह अच्छा होगा यदि वे सभी एक साथ एक ही समय पर चल सकें और डेटा पर अपनी कड़ी मेहनत कर सकें जो कि उपलब्ध होते ही प्रोसेस करने के लिए हैं।
एक फ़िल्टर कमांड के मामले में, यह आसान है:
src | tee | cmd1 | cat
सभी कमांड समवर्ती रूप cmd1से चलाए जाते हैं, srcजैसे ही यह उपलब्ध है , डेटा को चबाना शुरू कर देता है।
अब, तीन फ़िल्टर कमांड के साथ, हम अभी भी ऐसा ही कर सकते हैं: उन्हें समवर्ती रूप से शुरू करें और उन्हें पाइप से जोड़ दें:
┏━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━┓
┃ ┃░░░░2░░░░░┃cmd1┃░░░░░5░░░░┃ ┃
┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃
┏━━━┓▁▁▁▁▁▁▁▁▁▁┃ ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃ ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃░░░░1░░░░░┃tee┃░░░░3░░░░░┃cmd2┃░░░░░6░░░░┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔┗━━━┛
┃ ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃ ┃
┃ ┃░░░░4░░░░░┃cmd3┃░░░░░7░░░░┃ ┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛
जिसे हम नामित पाइपों के साथ अपेक्षाकृत आसानी से कर सकते हैं :
pee() (
mkfifo tee-cmd1 tee-cmd2 tee-cmd3 cmd1-cat cmd2-cat cmd3-cat
{ tee tee-cmd1 tee-cmd2 tee-cmd3 > /dev/null <&3 3<&- & } 3<&0
eval "$1 < tee-cmd1 1<> cmd1-cat &"
eval "$2 < tee-cmd2 1<> cmd2-cat &"
eval "$3 < tee-cmd3 1<> cmd3-cat &"
exec cat cmd1-cat cmd2-cat cmd3-cat
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'
(ऊपर से } 3<&0इस तथ्य के चारों ओर काम करना है कि &पुनर्निर्देशित stdinहोता है /dev/null, और हम <>पाइप के उद्घाटन से बचने के लिए उपयोग करते हैं जब तक कि दूसरे छोर तक ( cat) ने भी खोला है)
या नामित पाइपों से बचने के लिए, zshकोप्रोक के साथ थोड़ा और दर्दनाक :
pee() (
n=0 ci= co= is=() os=()
for cmd do
eval "coproc $cmd $ci $co"
exec {i}<&p {o}>&p
is+=($i) os+=($o)
eval i$n=$i o$n=$o
ci+=" {i$n}<&-" co+=" {o$n}>&-"
((n++))
done
coproc :
read -p
eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'
अब, सवाल यह है: एक बार सभी प्रोग्राम शुरू होने और कनेक्ट होने के बाद, क्या डेटा प्रवाहित होगा?
हमें दो विरोधाभास मिले हैं:
tee एक ही दर पर अपने सभी आउटपुट फ़ीड करता है, इसलिए यह केवल इसकी सबसे धीमी आउटपुट पाइप की दर से डेटा भेज सकता है।
cat केवल दूसरे पाइप (ऊपर ड्राइंग में 6 पाइप) से पढ़ना शुरू कर देगा जब सभी डेटा पहले (5) से पढ़ा गया हो।
इसका मतलब यह है कि डेटा पाइप 6 में प्रवाहित नहीं होगा जब तक cmd1कि समाप्त नहीं हुआ है। और, tr b Bऊपर के मामले की तरह , इसका मतलब यह हो सकता है कि डेटा पाइप 3 में प्रवाहित नहीं होगा, जिसका अर्थ है कि यह किसी भी पाइप 2, 3 या 4 में प्रवाहित नहीं होगा क्योंकि teeसभी 3 की सबसे धीमी दर पर फ़ीड करता है।
व्यवहार में उन पाइपों में एक अशक्त आकार होता है, इसलिए कुछ डेटा प्राप्त करने के लिए प्रबंधन करेंगे, और मेरे सिस्टम पर कम से कम, मैं इसे काम करने के लिए प्राप्त कर सकता हूं:
yes abc | head -c $((2 * 65536 + 8192)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c -c
उस पार, साथ
yes abc | head -c $((2 * 65536 + 8192 + 1)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c
हमें एक गतिरोध मिला है, जहां हम इस स्थिति में हैं:
┏━━━┓▁▁▁▁2▁▁▁▁▁┏━━━━┓▁▁▁▁▁5▁▁▁▁┏━━━┓
┃ ┃░░░░░░░░░░┃cmd1┃░░░░░░░░░░┃ ┃
┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃
┏━━━┓▁▁▁▁1▁▁▁▁▁┃ ┃▁▁▁▁3▁▁▁▁▁┏━━━━┓▁▁▁▁▁6▁▁▁▁┃ ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃██████████┃tee┃██████████┃cmd2┃██████████┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃ ┃▔▔▔▔▔▔▔▔▔┗━━━┛
┃ ┃▁▁▁▁4▁▁▁▁▁┏━━━━┓▁▁▁▁▁7▁▁▁▁┃ ┃
┃ ┃██████████┃cmd3┃██████████┃ ┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛
हमने पाइप 3 और 6 (64kiB प्रत्येक) भरे हैं। teeअतिरिक्त बाइट पढ़ा है, यह करने के लिए इसे खिलाया है cmd1, लेकिन
- यह अब पाइप 3 पर लिखना बंद कर दिया है क्योंकि यह इसे
cmd2खाली करने के लिए इंतजार कर रहा है
cmd2इसे खाली नहीं कर सकते क्योंकि यह पाइप 6 पर लिखना बंद कर दिया गया है, catइसे खाली करने की प्रतीक्षा कर रहा है
cat इसे खाली नहीं कर सकते क्योंकि यह तब तक प्रतीक्षा कर रहा है जब तक पाइप 5 पर कोई और इनपुट नहीं है।
cmd1नहीं बता सकते हैं कि catकोई और इनपुट नहीं है क्योंकि यह स्वयं अधिक इनपुट के लिए प्रतीक्षा कर रहा है tee।
- और
teeनहीं बता सकता कि cmd1कोई और इनपुट नहीं है क्योंकि यह अवरुद्ध है ... और इसी तरह।
हमें एक निर्भरता लूप मिला है और इस प्रकार एक गतिरोध।
अब, उपाय क्या है? बड़ा पाइप 3 और 4 (काफी बड़ा है जिसमें सभी srcआउटपुट होते हैं)। हम डालने से कर सकता है कि उदाहरण के लिए pv -qB 1Gके बीच teeऔर cmd2/3जहां pvके लिए इंतज़ार कर डेटा के 1G तक जमा कर सकता है cmd2और cmd3उन्हें पढ़ने के लिए। हालांकि दो चीजों का मतलब होगा:
- यह संभावित रूप से बहुत सारी मेमोरी, और इसके अलावा, इसे डुप्लिकेट कर रहा है
- कि सभी 3 आदेशों को विफल करने में विफल रहा है क्योंकि
cmd2वास्तव में केवल डेटा को संसाधित करना शुरू होगा जब cmd1 समाप्त हो गया है।
दूसरी समस्या का हल पाइप 6 और 7 को भी बड़ा बनाना होगा। यह मानते हुए कि cmd2और cmd3उत्पादन ज्यादा उत्पादन के रूप में वे उपभोग करते हैं, कि और अधिक स्मृति का उपभोग नहीं होगा।
डेटा को डुप्लिकेट करने से बचने का एकमात्र तरीका (पहली समस्या में) डिस्पैचर में डेटा की अवधारण को लागू करना होगा, जो कि इस पर एक भिन्नता को लागू करता है teeकि सबसे तेज़ आउटपुट की दर पर डेटा फ़ीड कर सकता है (डेटा को खिलाने के लिए होल्डिंग डेटा) अपनी गति से धीमी गति वाले)। वास्तव में तुच्छ नहीं।
इसलिए, अंत में, प्रोग्रामिंग के बिना हम जो सबसे बेहतर रूप से प्राप्त कर सकते हैं वह संभवतः कुछ ऐसा है (Zsh वाक्य रचना):
max_hold=1G
pee() (
n=0 ci= co= is=() os=()
for cmd do
if ((n)); then
eval "coproc pv -qB $max_hold $ci $co | $cmd $ci $co | pv -qB $max_hold $ci $co"
else
eval "coproc $cmd $ci $co"
fi
exec {i}<&p {o}>&p
is+=($i) os+=($o)
eval i$n=$i o$n=$o
ci+=" {i$n}<&-" co+=" {o$n}>&-"
((n++))
done
coproc :
read -p
eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
yes abc | head -n 1000000 | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c