आप जीएनयू 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
लेकिन सावधान रहें कि zsh
NUL वर्णों के साथ बाइनरी इनपुट के लिए अन्य गोले विफल होंगे।
यह अस्थायी फ़ाइलों का उपयोग करने से बचता है, लेकिन इसका मतलब है कि पूरे इनपुट को मेमोरी में संग्रहीत किया जाता है।
किसी भी स्थिति में, आपको इनपुट को मेमोरी या टेम्प फाइल में कहीं स्टोर करना होगा।
वास्तव में, यह काफी दिलचस्प सवाल है, क्योंकि यह हमें यूनिक्स विचार की सीमा दिखाता है जिसमें कई सरल उपकरण एक ही काम में सहयोग करते हैं।
यहाँ, हम इस कार्य के लिए कई उपकरणों का सहयोग करना चाहते हैं:
- एक स्रोत कमांड (यहाँ
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