टी + बिल्ली: एक आउटपुट का उपयोग कई बार करते हैं और फिर परिणाम को बदलते हैं


18

अगर मैं कुछ कमांड को कॉल करता हूं, उदाहरण के लिए, echoमैं उस कमांड से कई अन्य कमांड में परिणाम का उपयोग कर सकता हूं tee। उदाहरण:

echo "Hello world!" | tee >(command1) >(command2) >(command3)

बिल्ली के साथ मैं कई आदेशों के परिणाम एकत्र कर सकता हूं। उदाहरण:

cat <(command1) <(command2) <(command3)

मैं एक ही समय में दोनों चीजें करने में सक्षम होना चाहता हूं, ताकि मैं teeउन कमांड को किसी और चीज के आउटपुट पर कॉल कर सकूं (उदाहरण के लिए echoमैंने लिखा है) और फिर एक ही आउटपुट पर अपने सभी परिणाम एकत्र करें cat

परिणामों को क्रम में रखना महत्वपूर्ण है, इसका मतलब आउटपुट में लाइनें हैं command1, command2और command3इसे इंटरव्यू नहीं किया जाना चाहिए, लेकिन आदेश दिए गए हैं (जैसा कि इसके साथ होता है cat)।

वहाँ की तुलना में बेहतर विकल्प हो सकता है catऔर teeलेकिन उन लोगों को मैं अब तक पता कर रहे हैं।

मैं अस्थायी फ़ाइलों का उपयोग करने से बचना चाहता हूं क्योंकि इनपुट और आउटपुट का आकार बड़ा हो सकता है।

मैं ये कैसे करूं?

पीडी: एक और समस्या यह है कि यह एक लूप में होता है, जो अस्थायी फ़ाइलों को संभालने में कठिन बनाता है। यह मेरे पास मौजूद वर्तमान कोड है और यह छोटे टेस्टकेस के लिए काम करता है, लेकिन यह ऑक्सफाइल से पढ़ने और लिखने के दौरान अनंत लूप बनाता है, किसी तरह से मुझे समझ में नहीं आता है।

somefunction()
{
  if [ $1 -eq 1 ]
  then
    echo "Hello world!"
  else
    somefunction $(( $1 - 1 )) > auxfile
    cat <(command1 < auxfile) \
        <(command2 < auxfile) \
        <(command3 < auxfile)
  fi
}

ऑक्सफाइल में रीडिंग और राइटिंग ओवरलैपिंग लगती है, जिससे सब कुछ विस्फोट हो जाता है।


2
हम कितनी बड़ी बात कर रहे हैं? आपकी आवश्यकताएं सब कुछ स्मृति में रखने के लिए मजबूर करती हैं। आदेश में परिणाम रखने का अर्थ है कि कमांड 1 को पहले पूरा करना है (इसलिए यह संभवतः पूरे इनपुट को पढ़ा है और पूरे आउटपुट को प्रिंट किया है), कमांड 2 और कमांड 3 से पहले भी प्रोसेसिंग शुरू कर सकते हैं (जब तक कि आप अपने आउटपुट को मेमोरी में भी इकट्ठा करना चाहते हैं)।
फ्रॉस्ट्सचुटज़

आप सही हैं, कमांड 2 और कमांड 3 का इनपुट और आउटपुट मेमोरी में रखा जा सकता है। मैं उम्मीद कर रहा था कि स्वैप का उपयोग अस्थायी फ़ाइलों का उपयोग करने से बेहतर होगा। मेरे पास एक और समस्या यह है कि यह लूप में होता है, और इससे फाइलों को संभालना भी मुश्किल हो जाता है। मैं एक एकल फ़ाइल का उपयोग कर रहा हूं, लेकिन इस समय किसी कारण से फ़ाइल से पढ़ने और लिखने में कुछ ओवरलैप होता है, जिसके कारण यह विज्ञापन infinitum विकसित होता है। मैं आपको बहुत सारे विवरणों के साथ बोर किए बिना प्रश्न को अपडेट करने का प्रयास करने जा रहा हूं।
Trylks

4
आपको अस्थायी फ़ाइलों का उपयोग करना होगा; या तो इनपुट के लिए echo HelloWorld > file; (command1<file;command2<file;command3<file)या आउटपुट के लिए echo | tee cmd1 cmd2 cmd3; cat cmd1-output cmd2-output cmd3-output। बस यह कैसे काम करता है - टीईई कांटा इनपुट केवल तभी कर सकता है जब सभी कमांड समानांतर में काम और प्रक्रिया करते हैं। अगर एक कमांड सोता है (क्योंकि आप
इंटरलेविंग

जवाबों:


27

आप जीएनयू 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उन्हें पढ़ने के लिए। हालांकि दो चीजों का मतलब होगा:

  1. यह संभावित रूप से बहुत सारी मेमोरी, और इसके अलावा, इसे डुप्लिकेट कर रहा है
  2. कि सभी 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

आप सही हैं, गतिरोध मैं सबसे बड़ी समस्या है जो मैंने अब तक अस्थायी फ़ाइलों का उपयोग करने से बचने के लिए पाया है। ये फाइलें काफी तेज लगती हैं, हालांकि, मुझे नहीं पता कि उन्हें कहीं कैश किया जा रहा है या नहीं, मैं डिस्क एक्सेस टाइम से डरता था, लेकिन वे अब तक उचित हैं।
Trylks

6
+1 अच्छी ASCII कला के लिए एक अतिरिक्त :-)
कर्ट फ़िफ़ेल

3

आप जो प्रस्ताव देते हैं, वह किसी भी मौजूदा कमांड के साथ आसानी से नहीं किया जा सकता है, और वैसे भी इसका कोई मतलब नहीं है। पाइप के पूरे विचार ( |यूनिक्स / लिनक्स में) है कि में है (अधिकतम) राईट उत्पादन एक स्मृति बफर भरण तक, और उसके बाद (अधिकतम) बफर से डाटा पढ़ने रन जब तक यह खाली है। यानी, और एक ही समय में चलाने के लिए, उन दोनों के बीच "उड़ान" में सीमित मात्रा में डेटा से अधिक होने की आवश्यकता नहीं है। यदि आप किसी एकल आउटपुट में कई इनपुट कनेक्ट करना चाहते हैं, यदि पाठकों में से कोई एक दूसरे से पिछड़ जाता है तो आप दूसरों को रोकते हैं (फिर समानांतर में चलने की बात क्या है?) या आप उस आउटपुट को हटा देते हैं जो लैगार्ड ने अभी तक पढ़ा नहीं है? (फिर एक मध्यवर्ती फ़ाइल नहीं होने का क्या मतलब है?)।cmd1 | cmd2cmd1cmd2cmd1cmd2 अधिक जटिल।

यूनिक्स के अनुभव पर मेरे लगभग 30 वर्षों में मुझे ऐसी किसी भी स्थिति के बारे में याद नहीं है जो वास्तव में इस तरह के कई-आउटपुट पाइप के लिए लाभान्वित होगी।

आप एक से अधिक आउटपुट एक धारा आज में, बस किसी भी interleaved तरीके से नहीं जोड़ सकते हैं (कैसे के आउटपुट चाहिए cmd1और cmd2एक पंक्ति बदले में interleaved किया? ले 10 बाइट्स? वैकल्पिक "पैराग्राफ" लेखन बदल जाता है किसी भी तरह से परिभाषित किया? और एक करता है, तो बस नहीं करता है ' t लंबे समय तक कुछ भी लिखना? यह सब संभालना जटिल है)। यह, उदाहरण के द्वारा किया जाता है (cmd1; cmd2; cmd3) | cmd4, कार्यक्रमों cmd1, cmd2और cmd3एक के बाद एक चलाए जा रहे हैं, उत्पादन के लिए इनपुट के रूप में भेजा जाता है cmd4


3

आपकी अतिव्यापी समस्या के लिए, लिनक्स पर (और इसके साथ bashया zshसाथ नहीं ksh93), आप इसे निम्न प्रकार से कर सकते हैं:

somefunction()
(
  if [ "$1" -eq 1 ]
  then
    echo "Hello world!"
  else
    exec 3> auxfile
    rm -f auxfile
    somefunction "$(($1 - 1))" >&3 auxfile 3>&-
    exec cat <(command1 < /dev/fd/3) \
             <(command2 < /dev/fd/3) \
             <(command3 < /dev/fd/3)
  fi
)

प्रत्येक पुनरावृत्ति पर एक नई प्रक्रिया प्राप्त करने के (...)बजाय इसके उपयोग पर ध्यान दें {...}ताकि हमारे पास एक नया करने के लिए एक नया fd 3 हो auxfile< /dev/fd/3उस फ़ाइल को अब डिलीट करने की एक ट्रिक है। यह लिनक्स के अलावा अन्य सिस्टमों पर काम नहीं करेगा जहाँ < /dev/fd/3जैसा है dup2(3, 0)और इसलिए fd 0 फ़ाइल के अंत में कर्सर के साथ राइट-ओनली मोड में खुला होगा।

नेस्टेड फंक्शन के लिए कांटा से बचने के लिए, आप इसे इस प्रकार लिख सकते हैं:

somefunction()
{
  if [ "$1" -eq 1 ]
  then
    echo "Hello world!"
  else
    {
      rm -f auxfile
      somefunction "$(($1 - 1))" >&3 auxfile 3>&-
      exec cat <(command1 < /dev/fd/3) \
               <(command2 < /dev/fd/3) \
               <(command3 < /dev/fd/3)
    } 3> auxfile
  fi
}

शेल प्रत्येक पुनरावृत्ति पर fd 3 का बैकअप लेने का ध्यान रखेगा । हालाँकि आप फ़ाइल डिस्क्रिप्टर से जल्द ही समाप्त हो जाएंगे।

यद्यपि आप पाएंगे कि यह इसे करने के लिए अधिक कुशल है:

somefunction() {
  if [ "$1" -eq 1 ]; then
    echo "Hello world!" > auxfile
  else
    somefunction "$(($1 - 1))"
    { rm -f auxfile
      cat <(command1 < /dev/fd/3) \
          <(command2 < /dev/fd/3) \
          <(command3 < /dev/fd/3) > auxfile
    } 3< auxfile
  fi
}
somefunction 12; cat auxfile

यही कारण है, पुनर्निर्देशन घोंसला नहीं है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.