ฉันจะส่ง stdout ไปยังหลายคำสั่งได้อย่างไร


186

มีคำสั่งบางตัวที่กรองหรือดำเนินการกับอินพุตจากนั้นส่งผ่านไปตามเอาต์พุตฉันคิดว่าปกติแล้วstdout- แต่คำสั่งบางอย่างจะใช้stdinและทำสิ่งที่พวกเขาทำกับมัน

ฉันคุ้นเคยกับ OS X มากที่สุดและมีอยู่สองอย่างที่นึกขึ้นมาทันทีpbcopyและpbpaste- ซึ่งหมายถึงการเข้าถึงคลิปบอร์ดของระบบ

อย่างไรก็ตามฉันรู้ว่าถ้าฉันต้องการ stdout และคายเอาท์พุทเพื่อไปที่ทั้งสองstdoutและไฟล์จากนั้นฉันสามารถใช้teeคำสั่ง และฉันรู้เรื่องเล็กน้อยxargsแต่ฉันไม่คิดว่านั่นคือสิ่งที่ฉันกำลังมองหา

ฉันต้องการทราบว่าฉันสามารถแยกstdoutคำสั่งระหว่างสองคำสั่ง (หรือมากกว่า) ได้อย่างไร ตัวอย่างเช่น:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

อาจมีตัวอย่างที่ดีกว่าตัวอย่างนั้น แต่ฉันสนใจที่จะรู้ว่าฉันสามารถส่ง stdout ไปยังคำสั่งที่ไม่ส่งสัญญาณได้อย่างไรและในขณะที่ไม่stdoutถูก "ปิดเสียง" - ฉันไม่ได้ถามเกี่ยวกับcatไฟล์และgrepส่วนหนึ่งของมันและคัดลอกไปยังคลิปบอร์ด - คำสั่งเฉพาะนั้นไม่สำคัญ

นอกจากนี้ - ฉันไม่ได้ถามว่าจะส่งไฟล์นี้ไปยังไฟล์ได้อย่างไรstdout- อาจเป็นคำถามที่ "ซ้ำกัน" (ขออภัย) แต่ฉันได้ลองดูแล้วจะพบคนที่คล้ายกันที่ถามถึงวิธีการแยกระหว่าง stdout และไฟล์ - และคำตอบสำหรับคำถามเหล่านั้นดูเหมือนจะเป็นteeซึ่งฉันไม่คิดว่าจะทำงานให้ฉันได้

สุดท้ายคุณอาจถามว่า "ทำไมไม่ทำให้ pbcopy เป็นสิ่งสุดท้ายในห่วงโซ่ท่อ" และการตอบสนองของฉันคือ 1) ถ้าฉันต้องการใช้มันและยังเห็นผลลัพธ์ในคอนโซล? 2) จะทำอย่างไรถ้าฉันต้องการใช้สองคำสั่งที่ไม่ได้เอาต์พุตstdoutหลังจากที่ประมวลผลอินพุต?

โอ้และอีกอย่างหนึ่ง - ฉันรู้ว่าฉันสามารถใช้teeและท่อที่มีชื่อ ( mkfifo) แต่ฉันหวังว่าจะสามารถทำแบบอินไลน์ได้อย่างกระชับรัดกุมโดยไม่ต้องตั้งค่าล่วงหน้า :)


สำเนาซ้ำที่เป็นไปได้ของunix.stackexchange.com/questions/26964/…
Nikhil Mulley

คำตอบ:


239

คุณสามารถใช้teeและประมวลผลการทดแทนสำหรับสิ่งนี้:

cat file.txt | tee >(pbcopy) | grep errors

สิ่งนี้จะส่งผลลัพธ์ทั้งหมดcat file.txtไปยังpbcopyและคุณจะได้รับผลลัพธ์grepในคอนโซลของคุณเท่านั้น

คุณสามารถใส่หลายกระบวนการในteeส่วน:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors

21
ไม่กังวลกับpbcopyแต่มูลค่าการกล่าวขวัญทั่วไป: สิ่งที่แสดงผลขั้นตอนการเปลี่ยนตัวจะยังมองเห็นได้ด้วยส่วนท่อถัดไปหลังจากการป้อนข้อมูลต้นฉบับ; เช่น: seq 3 | tee >(cat -n) | cat -e( cat -nตัวเลขบรรทัดอินพุตให้cat -eทำเครื่องหมายบรรทัดใหม่ด้วย$คุณจะเห็นว่าcat -eมีการใช้กับทั้งอินพุตดั้งเดิม (ก่อน) และ (จากนั้น) เอาต์พุตจากcat -n) ผลลัพธ์จากการทดแทนกระบวนการหลายอย่างจะมาถึงในลำดับที่ไม่ได้กำหนดไว้
mklement0

49
งานเฉพาะใน>( bashหากคุณลองใช้งานshมันจะไม่ทำงาน สิ่งสำคัญคือต้องแจ้งให้ทราบล่วงหน้า
AAlvz

10
@AAlvz: ข้อดี: การทดแทนกระบวนการไม่ใช่คุณสมบัติ POSIX; dashซึ่งทำหน้าที่เป็นshUbuntu ไม่สนับสนุนและแม้แต่ Bash เองก็ปิดการใช้งานคุณสมบัติเมื่อมีการเรียกใช้shเมื่อหรือเมื่อset -o posixมีผล อย่างไรก็ตามไม่ใช่เพียง Bash ที่รองรับการทดแทนกระบวนการ: kshและzshสนับสนุนพวกเขาด้วย (ไม่แน่ใจเกี่ยวกับผู้อื่น)
mklement0

2
@ mklement0 ที่ดูเหมือนจะไม่เป็นจริง บน zsh (Ubuntu 14.04) บรรทัดของคุณพิมพ์: 1 1 2 2 3 3 1 $ 2 $ 3 $ ซึ่งน่าเศร้าเพราะฉันต้องการให้ฟังก์ชั่นเป็นอย่างที่คุณพูด
Aktau

2
@ Aktau: แน่นอนคำสั่งตัวอย่างของฉันทำงานตามที่อธิบายไว้ในbashและksh- zshเห็นได้ชัดว่าไม่ได้ส่งออกจากการทดแทนกระบวนการส่งออกผ่านไปป์ไลน์ (arguably ที่ดีกว่าเพราะมันไม่ก่อให้เกิดมลพิษสิ่งที่ถูกส่งไปยังส่วนถัดไป มันยังคงพิมพ์ ) ในเชลล์ทั้งหมดที่กล่าวถึงอย่างไรก็ตามโดยทั่วไปแล้วไม่ใช่ความคิดที่ดีที่จะมีไปป์ไลน์เดี่ยวที่มีเอาต์พุต stdout ปกติและเอาท์พุทจากการทดแทนกระบวนการผสมกัน - การเรียงลำดับผลลัพธ์จะไม่สามารถคาดการณ์ได้ในลักษณะที่อาจมีพื้นผิวไม่บ่อยนัก ชุดข้อมูลเอาต์พุต
mklement0

124

คุณสามารถระบุชื่อไฟล์ได้หลายชื่อteeและนอกจากนี้สามารถส่งออกมาตรฐานไปยังคำสั่งเดียวได้ teeที่จะส่งออกไปยังหลายคำสั่งคุณต้องสร้างท่อหลายและระบุแต่ละของพวกเขาเป็นหนึ่งในการส่งออกของ มีหลายวิธีในการทำเช่นนี้

การทดแทนกระบวนการ

หากเชลล์ของคุณคือ ksh93, bash หรือ zsh คุณสามารถใช้การทดแทนกระบวนการ นี่เป็นวิธีการส่งไพพ์ไปยังคำสั่งที่ต้องการชื่อไฟล์ เชลล์สร้างไพพ์และส่งชื่อไฟล์เหมือนกับ/dev/fd/3คำสั่ง หมายเลขเป็นตัวบ่งชี้ไฟล์ที่ไพพ์เชื่อมต่อด้วย บางสายพันธุ์ยูนิกซ์ไม่สนับสนุน/dev/fd; ในสิ่งเหล่านี้ใช้ไพพ์ที่มีชื่อแทน (ดูด้านล่าง)

tee >(command1) >(command2) | command3

อธิบายไฟล์

ใน POSIX เชลล์ใด ๆ คุณสามารถใช้ตัวอธิบายไฟล์หลายไฟล์ได้อย่างชัดเจน สิ่งนี้ต้องการตัวแปร unix ที่รองรับ/dev/fdเนื่องจากทั้งหมด แต่หนึ่งในผลลัพธ์ของteeต้องระบุด้วยชื่อ

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

ท่อที่มีชื่อ

วิธีการขั้นพื้นฐานที่สุดและแบบพกพาคือการใช้ชื่อ pipes ข้อเสียคือคุณต้องค้นหาไดเรกทอรีที่สามารถเขียนได้สร้างท่อและทำความสะอาดในภายหลัง

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2

10
ขอบคุณมากที่ให้ทั้งสองรุ่นทางเลือกสำหรับผู้ที่ไม่ต้องการพึ่งพาทุบตีหรือ ksh แน่นอน
trr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3ควรจะcommand3 | tee "$tmp_dir/f1" "$tmp_dir/f2"เป็นอย่างที่คุณต้องการ stdout ของcommand3ท่อไปteeใช่ไหม? ฉันทดสอบเวอร์ชันของคุณด้านล่างdashและteeบล็อกการรออินพุตอย่างไม่มีกำหนด แต่การสลับลำดับทำให้เกิดผลลัพธ์ที่คาดหวัง
Adrian Günter

1
@ AdrianGünterเลขทั้งสามตัวอย่างอ่านข้อมูลจากอินพุตมาตรฐานและส่งไปยังแต่ละcommand, และcommand2 command3
Gilles

@Gilles ฉันเห็นว่าฉันตีความความตั้งใจผิดและพยายามใช้ข้อมูลโค้ดอย่างไม่ถูกต้อง ขอขอบคุณสำหรับการชี้แจง!
Adrian Günter

หากคุณมีการควบคุมที่ไม่มีในเปลือกใช้ <command> | bash -c 'tee >(command1) >(command2) | command3'แต่คุณสามารถใช้ทุบตีอย่างชัดเจนที่คุณสามารถทำได้ มันช่วยในกรณีของฉัน
gc5

16

เพียงแค่เล่นกับการทดแทนกระบวนการ

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepเป็นสองไบนารีที่มีเอาต์พุตเดียวกันจากmycommand_execอินพุตเฉพาะกระบวนการของพวกเขา


16

หากคุณใช้งานอยู่zshคุณสามารถใช้ประโยชน์จากMULTIOSฟีเจอร์ของคุณสมบัติเช่นกำจัดteeคำสั่งอย่างสมบูรณ์:

uname >file1 >file2

จะเขียนผลลัพธ์ของunameไฟล์ที่แตกต่างกันสองไฟล์: file1และfile2อะไรที่เทียบเท่ากันuname | tee file1 >file2

การเปลี่ยนเส้นทางในทำนองเดียวกันของอินพุตมาตรฐาน

wc -l <file1 <file2

มีค่าเท่ากับcat file1 file2 | wc -l(โปรดทราบว่านี่ไม่เหมือนกันwc -l file1 file2ในภายหลังนับจำนวนบรรทัดในแต่ละไฟล์แยกกัน)

แน่นอนคุณยังสามารถใช้MULTIOSเพื่อเปลี่ยนเส้นทางผลลัพธ์ไม่ใช่ไฟล์ แต่ไปยังกระบวนการอื่นโดยใช้การทดแทนกระบวนการเช่น:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')

3
ดีแล้วที่รู้. MULTIOSเป็นตัวเลือกที่เปิดอยู่ตามค่าเริ่มต้น (และสามารถปิดได้ด้วยunsetopt MULTIOS)
mklement0

6

สำหรับเอาต์พุตขนาดเล็กที่สมเหตุสมผลที่สร้างโดยคำสั่งเราสามารถเปลี่ยนทิศทางเอาต์พุตไปยังไฟล์ชั่วคราวและส่งไฟล์ชั่วคราวเหล่านั้นไปยังคำสั่งในลูป สิ่งนี้มีประโยชน์เมื่อลำดับของคำสั่งที่ดำเนินการอาจมีความสำคัญ

ตัวอย่างเช่นสคริปต์ต่อไปนี้สามารถทำได้:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

ทดสอบการทำงานบน Ubuntu 16.04 กับ/bin/shเป็นdashเปลือก:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash

5

จับคำสั่งSTDOUTไปยังตัวแปรและนำมาใช้ซ้ำหลาย ๆ ครั้งตามที่คุณต้องการ:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

หากคุณต้องการจับภาพSTDERRเช่นกันให้ใช้2>&1ที่ส่วนท้ายของคำสั่งเช่น:

commandoutput="$(command-to-run 2>&1)"

3
เก็บตัวแปรไว้ที่ไหน หากคุณกำลังจัดการกับไฟล์ขนาดใหญ่หรืออะไรทำนองนั้นจะไม่ทำให้หน่วยความจำมากขึ้นหรือไม่ ตัวแปรมีขนาด จำกัด หรือไม่?
cwd

1
เกิดอะไรขึ้นถ้า $ commandoutput มีขนาดใหญ่จะดีกว่าหากใช้การแทนที่ไพพ์และกระบวนการ
Nikhil Mulley

4
เห็นได้ชัดว่าวิธีการแก้ปัญหานี้เป็นไปได้เฉพาะเมื่อคุณรู้ว่าขนาดของการส่งออกจะพอดีกับหน่วยความจำและคุณก็โอเคกับบัฟเฟอร์การส่งออกทั้งหมดก่อนที่จะใช้คำสั่งต่อไป ท่อแก้ปัญหาทั้งสองนี้โดยอนุญาตให้ข้อมูลความยาวตามอำเภอใจและสตรีมมันแบบเรียลไทม์ไปยังผู้รับเมื่อมันถูกสร้างขึ้น
trr

2
นี่เป็นวิธีแก้ปัญหาที่ดีถ้าคุณมีเอาต์พุตขนาดเล็กและคุณรู้ว่าเอาต์พุตจะเป็นข้อความไม่ใช่ไบนารี (ตัวแปรเชลล์มักจะไม่ปลอดภัยแบบไบนารี)
Rucent88

1
ฉันไม่สามารถทำงานกับข้อมูลไบนารีได้ ฉันคิดว่ามันเป็นสิ่งที่มีก้องพยายามที่จะตีความเป็นโมฆะไบต์หรือข้อมูลที่ไม่ใช่ตัวละครอื่น ๆ
Rolf

1

สิ่งนี้อาจใช้งานได้: http://www.spinellis.gr/sw/dgsh/ (กำกับกราฟเชลล์) ดูเหมือนว่าการแทนที่ทุบตีจะสนับสนุนไวยากรณ์ที่ง่ายขึ้นสำหรับคำสั่ง "multipipe"


0

นี่เป็นทางออกที่บางส่วนได้อย่างรวดเร็วและสกปรกเข้ากันได้กับเปลือกใด ๆ busyboxรวมทั้ง

ปัญหาที่แคบลงก็คือแก้ไข: พิมพ์ที่สมบูรณ์stdoutไปที่หนึ่งคอนโซลและกรองในอีกหนึ่งโดยไม่ต้องใช้ไฟล์ชั่วคราวหรือท่อที่มีชื่อ

  • เริ่มเซสชันอื่นไปยังโฮสต์เดียวกัน เพื่อหาชื่อ TTY ttyของประเภท /dev/pty/2สมมติ
  • ในเซสชั่นแรกเรียกใช้ the_program | tee /dev/pty/2 | grep ImportantLog:

คุณได้รับหนึ่งไฟล์บันทึกที่สมบูรณ์และไฟล์ที่ถูกกรอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.