كيف يمكنني إرسال stdout لعملية واحدة إلى عمليات متعددة باستخدام أنابيب (يفضل أن تكون غير مسماة) في Unix (أو Windows)؟

StackOverflow https://stackoverflow.com/questions/60942

سؤال

أرغب في إعادة توجيه stdout للعملية proc1 إلى عمليتين proc2 وproc3:

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

حاولت

 proc1 | (proc2 & proc3)

ولكن لا يبدو أن العمل، أي.

 echo 123 | (tr 1 a & tr 1 b)

يكتب

 b23

إلى stdout بدلا من

 a23
 b23
هل كانت مفيدة؟

المحلول

ملحوظة المحرر:
- >(…) هو استبدال العملية هذا هو ميزة الصدفة غير القياسية ل بعض الأصداف المتوافقة مع POSIX: bash, ksh, zsh.
- ترسل هذه الإجابة بطريق الخطأ مخرجات استبدال عملية الإخراج عبر خط الأنابيب أيضاً: echo 123 | tee >(tr 1 a) | tr 1 b.
- سيتم تشذير المخرجات من بدائل العملية بشكل غير متوقع، باستثناء zsh, ، قد ينتهي خط الأنابيب قبل الأوامر الموجودة بالداخل >(…) يفعل.

في نظام التشغيل Unix (أو نظام التشغيل Mac)، استخدم ملف tee يأمر:

$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23

عادة سوف تستخدم tee لإعادة توجيه الإخراج إلى ملفات متعددة ، ولكن باستخدام> (...) يمكنك إعادة توجيه عملية أخرى.لذلك، بشكل عام،

$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null

سوف تفعل ما تريد.

تحت النوافذ، لا أعتقد أن القشرة المدمجة لها ما يعادلها.مايكروسوفت ويندوز باورشيل لديه tee الأمر بالرغم من ذلك.

نصائح أخرى

وكما قال dF bash يسمح باستخدام >(…) إنشاء تشغيل أمر بدلاً من اسم الملف.(وهناك أيضا <(…) بناء ليحل محل انتاج أمر آخر بدلاً من اسم الملف، ولكن هذا غير ذي صلة الآن، أذكره فقط للاكتمال).

إذا لم يكن لديك bash، أو كنت تعمل على نظام به إصدار أقدم من bash، فيمكنك القيام بما يفعله bash يدويًا، من خلال الاستفادة من ملفات FIFO.

الطريقة العامة لتحقيق ما تريد هي:

  • حدد عدد العمليات التي يجب أن تتلقى مخرجات الأمر الخاص بك، وقم بإنشاء أكبر عدد ممكن من عمليات FIFO، ويفضل أن يكون ذلك في مجلد مؤقت عام:
    subprocesses="a b c d"
    mypid=$$
    for i in $subprocesses # this way we are compatible with all sh-derived shells  
    do
        mkfifo /tmp/pipe.$mypid.$i
    done
  • ابدأ جميع العمليات الفرعية الخاصة بك في انتظار الإدخال من FIFOs:
    for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • قم بتنفيذ الأمر الخاص بك إلى FIFOs:
    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • أخيرًا، قم بإزالة FIFOs:
    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

ملحوظة:لأسباب التوافق، سأفعل $(…) مع الاقتباسات الخلفية، لكنني لم أتمكن من كتابة هذه الإجابة (يتم استخدام الاقتباس الخلفي في SO).عادة، $(…) قديم بما يكفي للعمل حتى في الإصدارات القديمة من ksh، ولكن إذا لم يكن كذلك، قم بإرفاق ملف جزء في الاقتباسات الخلفية.

يونكس (bash, ksh, zsh)

إجابة dF يحتوي على بذرة من الجواب على أساس tee و انتاج بدائل العملية
(>(...)) الذي - التي يمكن اه ويمكن لا العمل حسب متطلباتك:

لاحظ أن بدائل العملية هي أ غير قياسي ميزة (في الغالب) قذائف Posix-Features فقط مثل dash (الذي يعمل ك /bin/sh على Ubuntu ، على سبيل المثال) ، افعل لا يدعم.استهداف البرامج النصية شل /bin/sh يجب لا تعتمد عليها.

echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null

ال المزالق من هذا النهج هي:

  • سلوك الإخراج غير المتزامن وغير المتوقع:تدفقات الإخراج من الأوامر داخل بدائل عملية الإخراج >(...) تتداخل بطرق غير متوقعة.

  • في bash و ksh (في مقابل zsh - ولكن انظر الاستثناء أدناه):

    • قد يصل الإخراج بعد انتهى الأمر.
    • قد يبدأ تنفيذ الأوامر اللاحقة قبل انتهت الأوامر الموجودة في عملية الاستبدال - bash و ksh يفعل لا انتظر حتى تنتهي العمليات الناتجة عن استبدال عملية الإخراج، على الأقل افتراضيًا.
    • jmb يضعها جيدًا في تعليق على إجابة dF.:

انتبه إلى أن الأوامر بدأت في الداخل >(...) يتم فصلها عن الغلاف الأصلي، ولا يمكنك بسهولة تحديد متى تنتهي؛ال tee ستنتهي بعد كتابة كل شيء، لكن العمليات البديلة ستظل تستهلك البيانات من المخازن المؤقتة المختلفة في النواة وإدخال/إخراج الملف، بالإضافة إلى أي وقت يستغرقه التعامل الداخلي مع البيانات.يمكنك أن تواجه حالات سباق إذا استمرت غلافك الخارجي في الاعتماد على أي شيء تنتجه العمليات الفرعية.

  • zsh هي القشرة الوحيدة التي يفعل بشكل افتراضي، انتظر حتى تنتهي العمليات التي يتم تشغيلها في بدائل عملية الإخراج, يستثني اذا كانت com.stderr يتم إعادة توجيهه إلى واحد (2> >(...)).

  • ksh (على الأقل اعتبارًا من الإصدار 93u+) يسمح باستخدام وسيطة أقل wait لانتظار انتهاء العمليات التي تم إنشاؤها بواسطة استبدال عملية الإخراج.
    لاحظ أنه في الجلسة التفاعلية قد يؤدي ذلك إلى انتظار أي تعليق وظائف الخلفية أيضا، ولكن.

  • bash v4.4+ يمكن أن تنتظر آخر المستجدات أطلقت استبدال عملية الإخراج مع wait $!, ، ولكن بلا حجة wait يفعل لا العمل، مما يجعل هذا غير مناسب لأمر به عديد بدائل عملية الإخراج

  • لكن، bash و ksh يمكن ان يكون قسري بالانتظار \ في الانتظار عن طريق توجيه الأمر إلى | cat, ، ولكن لاحظ أن هذا يجعل الأمر يعمل في ملف قذيفة فرعية. تحفظات:

    • ksh (اعتبارا من ksh 93u+) لا يدعم الإرسال com.stderr لاستبدال عملية الإخراج (2> >(...));مثل هذه المحاولة هي تجاهلت بصمت.

    • بينما zsh هو (جدير بالثناء) متزامن بشكل افتراضي مع (أكثر شيوعا) com.stdout بدائل عملية الإخراج، حتى | cat لا يمكن للتقنية أن تجعلها متزامنة مع com.stderr بدائل عملية الإخراج (2> >(...)).

  • لكن، حتى لو تأكدت التنفيذ المتزامن, ، مشكلة إخراج معشق بشكل غير متوقع بقايا.

الأمر التالي، عند تشغيله bash أو ksh, ، يوضح السلوكيات الإشكالية (قد تضطر إلى تشغيله عدة مرات لترى كلاهما أعراض):ال AFTER سيتم طباعة عادة قبل الناتج من بدائل المخرجات، ويمكن تشذير الناتج من الأخير بشكل غير متوقع.

printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER

باختصار:

  • ضمان تسلسل إخراج معين لكل أمر:

    • لا bash ولا ksh ولا zsh دعم ذلك.
  • التنفيذ المتزامن:

    • قابلة للتنفيذ، إلا مع com.stderr-بدائل عملية الإخراج المصدرية:
      • في zsh, ، هم بثبات غير متزامن.
      • في ksh, ، هم لا تعمل على الإطلاق.

إذا كنت تستطيع التعايش مع هذه القيود، فإن استخدام بدائل عمليات الإخراج يعد خيارًا قابلاً للتطبيق (على سبيل المثال، إذا كانت جميعها تكتب في ملفات إخراج منفصلة).


لاحظ أن tzot هو الحل الأكثر تعقيدًا، ولكن من المحتمل أن يكون متوافقًا مع POSIX يُظهر أيضًا سلوك إخراج غير متوقع;ومع ذلك، باستخدام wait يمكنك التأكد من عدم بدء تنفيذ الأوامر اللاحقة إلا بعد انتهاء جميع العمليات في الخلفية.
انظر القاع ل تنفيذ مخرجات أكثر قوة وتزامنًا وتسلسلًا.


الوحيد واضحة bash حل مع سلوك الإخراج يمكن التنبؤ به هو ما يلي، والذي، مع ذلك، هو بطيئة للغاية مع مجموعات الإدخال الكبيرة, لأن حلقات الصدفة بطيئة بطبيعتها.
لاحظ أيضًا أن هذا المناوبين خطوط الإخراج من الأوامر الهدف.

while IFS= read -r line; do 
  tr 1 a <<<"$line"
  tr 1 b <<<"$line"
done < <(echo '123')

يونكس (باستخدام جنو الموازي)

التثبيت جنو parallel تمكن أ حل قوي مع الإخراج المتسلسل (لكل أمر). الذي يسمح بالإضافة إلى ذلك التنفيذ الموازي:

$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23

parallel بشكل افتراضي يضمن عدم تداخل الإخراج من الأوامر المختلفة (يمكن تعديل هذا السلوك - راجع man parallel).

ملحوظة:تأتي بعض توزيعات Linux مع ملحق مختلف parallel الأداة المساعدة، والتي لن تعمل مع الأمر أعلاه؛يستخدم parallel --version لتحديد أي واحد لديك، إن وجد.


شبابيك

إجابة جاي بازوزي المفيدة يوضح كيفية القيام بذلك في بوويرشيل.هكذا قال:إجابته هي التناظرية للحلقات bash الإجابة أعلاه، سيكون بطيئة للغاية مع مجموعات الإدخال الكبيرة و أيضا المناوبين خطوط الإخراج من الأوامر الهدف.



bashحل Unix قائم على نظام Unix ولكنه محمول مع التنفيذ المتزامن وتسلسل المخرجات

ما يلي هو تنفيذ بسيط، ولكن قوي إلى حد معقول للنهج المعروض في إجابة تسوت الذي يوفر بالإضافة إلى ذلك:

  • التنفيذ المتزامن
  • الإخراج المتسلسل (المجمع).

على الرغم من أنه ليس متوافقًا بشكل صارم مع POSIX، لأنه bash البرنامج النصي، ينبغي أن يكون محمول على أي منصة يونكس التي لديها bash.

ملحوظة:يمكنك العثور على تطبيق أكثر اكتمالاً تم إصداره بموجب ترخيص MIT في هذا جوهر.

إذا قمت بحفظ الكود أدناه كبرنامج نصي fanout, ، اجعله قابلاً للتنفيذ وأدخله في ملفك PATH, ، سيعمل الأمر من السؤال على النحو التالي:

$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
# tr 1 a
a23
# tr 1 b
b23

fanout كود مصدر البرنامج النصي:

#!/usr/bin/env bash

# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )

# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap 'rm -rf "$tmpDir"' EXIT 

# Determine the number padding for the sequential FIFO / output-capture names, 
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"

# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
  printf -v suffix "$fmtString" $i
  aFifos[i]="$tmpDir/fifo-$suffix"
  aOutFiles[i]="$tmpDir/out-$suffix"
done

# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit

# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
  fifo=${aFifos[i]}
  outFile=${aOutFiles[i]}
  cmd=${aCmds[i]}
  printf '# %s\n' "$cmd" > "$outFile"
  eval "$cmd" < "$fifo" >> "$outFile" &
done

# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit

# Wait for all background processes to finish.
wait

# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"

منذ @dF:ذكرت أن PowerShell لديه نقطة الإنطلاق، واعتقدت أنني سأعرض طريقة للقيام بذلك في PowerShell.

PS > "123" | % { 
    $_.Replace( "1", "a"), 
    $_.Replace( "2", "b" ) 
}

a23
1b3

لاحظ أن كل كائن يخرج من الأمر الأول تتم معالجته قبل إنشاء الكائن التالي.يمكن أن يسمح هذا بالتوسيع إلى مدخلات كبيرة جدًا.

طريقة أخرى للقيام بذلك ستكون،

 eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`

انتاج:

a23
b23

لا حاجة لإنشاء قذيفة فرعية هنا

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top