คุณจะแตกต่างสองท่อใน Bash ได้อย่างไร


143

คุณจะแตกต่างสองท่อได้อย่างไรโดยไม่ต้องใช้ไฟล์ชั่วคราวใน Bash สมมติว่าคุณมีท่อคำสั่งสองท่อ:

foo | bar
baz | quux

และคุณต้องการค้นหาdiffผลลัพธ์ของมัน ทางออกหนึ่งที่เห็นได้ชัดคือ:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

เป็นไปได้ไหมที่จะไม่ใช้ไฟล์ชั่วคราวใน Bash? คุณสามารถกำจัดไฟล์ชั่วคราวหนึ่งไฟล์โดยการไพพ์ในไพพ์ไลน์ใดไฟล์หนึ่งเพื่อ diff:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

แต่คุณไม่สามารถท่อทั้งสองแยกเป็น diff พร้อมกัน (อย่างน้อยก็ในลักษณะที่ไม่ชัดเจน) มีเคล็ดลับที่ฉลาดเกี่ยวกับ/dev/fdการทำเช่นนี้โดยไม่ใช้ไฟล์ชั่วคราวหรือไม่?

คำตอบ:


146

บรรทัดเดียวที่มี 2 ไฟล์ tmp (ไม่ใช่สิ่งที่คุณต้องการ) จะเป็น:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

ด้วยทุบตีคุณอาจลอง:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

รุ่นที่ 2 จะเตือนคุณอย่างชัดเจนมากขึ้นว่าอินพุตใดที่แสดงหรือ
-- /dev/stdinเทียบกับ++ /dev/fd/63อะไรบางอย่างแทนที่จะเป็น fds ที่มีหมายเลขสองชุด


ไม่แม้แต่ไพพ์ที่มีชื่อจะปรากฏในระบบไฟล์อย่างน้อยใน OSes ที่ bash สามารถใช้การทดแทนกระบวนการโดยใช้ชื่อไฟล์ที่ต้องการ/dev/fd/63รับชื่อไฟล์ที่คำสั่งสามารถเปิดและอ่านจากจริง ๆ แล้วอ่านจาก descriptor ไฟล์ที่เปิดอยู่แล้ว ก่อนที่จะรันคำสั่ง (เช่น bash ใช้pipe(2)ก่อน fork แล้วจึงdup2เปลี่ยนเส้นทางจากเอาต์พุตของquuxไปยัง file file descriptor สำหรับdiffบน fd 63)

บนระบบที่ไม่มี "เวทมนต์" /dev/fdหรือ/proc/self/fdทุบตีอาจใช้ pipes ที่มีชื่อเพื่อใช้การทดแทนกระบวนการ แต่อย่างน้อยก็จะจัดการได้เองซึ่งแตกต่างจากไฟล์ชั่วคราวและข้อมูลของคุณจะไม่ถูกเขียนลงในระบบไฟล์

คุณสามารถตรวจสอบวิธีการทุบตีดำเนินการทดแทนด้วยecho <(true)การพิมพ์ชื่อไฟล์แทนการอ่านจากมัน มันพิมพ์/dev/fd/63บนระบบ Linux ทั่วไป หรือสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับสิ่งที่ระบบเรียกใช้ bash คำสั่งนี้บนระบบ Linux จะติดตามไฟล์และการเรียกระบบของ file-descriptor

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

โดยไม่ต้องทุบตีคุณสามารถทำให้ไปป์ที่มีชื่อ ใช้-เพื่อบอกdiffให้อ่านอินพุตหนึ่งจาก STDIN และใช้ไพพ์ที่มีชื่อเป็นตัวอื่น:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

โปรดทราบว่าคุณสามารถไพพ์หนึ่งเอาต์พุตไปยังหลายอินพุตด้วยคำสั่ง tee:

ls *.txt | tee /dev/tty txtlist.txt 

คำสั่งดังกล่าวจะแสดงผลลัพธ์ของ ls * .txt ไปยังเทอร์มินัลและส่งออกไปยังไฟล์ข้อความ txtlist.txt

แต่ด้วยการทดแทนกระบวนการคุณสามารถใช้teeฟีดข้อมูลเดียวกันในหลาย ๆ ท่อ:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar

5
แม้ไม่มีทุบตีคุณสามารถใช้mkfifo a; cmd >a& cmd2|diff a -; rm a
ฟีฟ่า

คุณสามารถใช้ท่อปกติสำหรับหนึ่ง args pipeline1 | diff -u - <(pipeline2)นี้: จากนั้นเอาต์พุตจะเตือนคุณอย่างชัดเจนมากขึ้นว่าอินพุตใดซึ่งแสดงโดย-- /dev/stdinเทียบกับ++ /dev/fd/67หรือบางสิ่งบางอย่างแทนการกำหนดหมายเลขสอง fds
Peter Cordes

การทดแทนโปรเซส ( foo <( pipe )) ไม่ได้แก้ไขระบบไฟล์ ท่อเป็นที่ไม่ระบุชื่อ ; มันมีชื่อในระบบแฟ้มไม่มี เปลือกใช้เรียกระบบที่จะสร้างมันขึ้นมาไม่ได้pipe mkfifoใช้strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'เพื่อติดตามการเรียกไฟล์และระบบอธิบายไฟล์หากคุณต้องการดูด้วยตัวคุณเอง บน Linux /dev/fd/63เป็นส่วนหนึ่งของ/procระบบไฟล์เสมือน มันมีรายการสำหรับทุกตัวอธิบายไฟล์โดยอัตโนมัติและไม่ได้คัดลอกเนื้อหา ดังนั้นคุณจึงไม่สามารถเรียกได้ว่า "ไฟล์ชั่วคราว" เว้นแต่จะfoo 3<bar.txtนับ
Peter Cordes

@PeterCordes คะแนนดี ฉันได้รวมความคิดเห็นของคุณไว้ในคำตอบเพื่อให้มองเห็นได้ชัดเจนขึ้น
VonC

1
@PeterCordes ฉันจะปล่อยให้คุณแก้ไข: นั่นคือสิ่งที่ทำให้กองล้นน่าสนใจ: ทุกคนสามารถ "แก้ไข" คำตอบ
VonC

127

ในทุบตีคุณสามารถใช้ subshells เพื่อดำเนินการไปป์ไลน์คำสั่งทีละรายการโดยล้อมรอบไปป์ไลน์ภายในวงเล็บ จากนั้นคุณสามารถเติมคำนำหน้าด้วย <เพื่อสร้างชื่อไพพ์ที่ไม่ระบุชื่อซึ่งคุณสามารถส่งผ่านไปยัง diff ได้

ตัวอย่างเช่น:

diff <(foo | bar) <(baz | quux)

ไปป์ที่มีชื่อไม่ระบุชื่อได้รับการจัดการโดยทุบตีเพื่อสร้างและทำลายโดยอัตโนมัติ (ไม่เหมือนกับไฟล์ชั่วคราว)


1
มีรายละเอียดมากกว่าการตอบกลับของฉันในโซลูชันเดียวกัน - แบตช์ไม่ระบุชื่อ - +1
VonC

4
สิ่งนี้เรียกว่าการทดแทนกระบวนการใน Bash
แฟรงคลินหยู

5

บางคนที่มาถึงหน้านี้อาจกำลังมองหา diff-by-line แตกต่างกันซึ่งควรใช้commหรือgrep -fควรแทน

สิ่งหนึ่งที่ชี้ให้เห็นคือในตัวอย่างทั้งหมดของคำตอบนั้นดิฟเฟอเรนจะไม่เริ่มต้นจนกว่าสตรีมทั้งสองจะเสร็จสิ้น ทดสอบสิ่งนี้ด้วยเช่น:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

หากนี่เป็นปัญหาคุณสามารถลองใช้sd (stream diff) ซึ่งไม่ต้องการการเรียงลำดับ (เช่นcommนั้น) หรือการทดแทนกระบวนการเช่นตัวอย่างข้างต้นคือคำสั่งซื้อหรือขนาดเร็วกว่าgrep -f และรองรับสตรีมที่ไม่มีที่สิ้นสุด

ตัวอย่างการทดสอบที่ฉันเสนอจะเขียนsdดังนี้:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

แต่ความแตกต่างก็คือว่าseq 100จะ diffed กับseq 10ออกไปทางขวา โปรดทราบว่าหากหนึ่งในสตรีมเป็น a tail -fdiff ไม่สามารถทำได้ด้วยการทดแทนกระบวนการ

นี่เป็นblogpostผมเขียนเกี่ยวกับ diffing ลำธารบน terminal sdซึ่งเปิดตัว

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