อะไรคือความแตกต่างระหว่าง <<, <<< และ <<ในการทุบตี?


102

คือสิ่งที่แตกต่างระหว่าง<<, <<<และ< <ในทุบตี?


20
อย่างน้อยในช่วงเวลาที่เป็นศูนย์กลางของ Google เหล่านี้มันเป็นการยากที่จะค้นหาผู้ให้บริการที่อ้างอิงสัญลักษณ์เหล่านี้ มีเครื่องมือค้นหาที่คุณสามารถเสียบ "<< <<< <<" และรับสิ่งที่มีประโยชน์หรือไม่
Daniel Griscom

11
@DanielGriscom มีSymbolHoundอยู่
เดนนิส

1
@DanielGriscom Stack Exchange ใช้เพื่อรองรับสัญลักษณ์การค้นหา แต่แล้วบางสิ่งก็พังและไม่มีใครแก้ไขได้
muru

คำตอบ:


115

เอกสารที่นี่

<<เรียกว่าhere-documentโครงสร้าง คุณแจ้งให้โปรแกรมทราบว่าอะไรคือข้อความสิ้นสุดและเมื่อใดก็ตามที่มีการเห็นตัวคั่นโปรแกรมจะอ่านเนื้อหาทั้งหมดที่คุณให้กับโปรแกรมเป็นอินพุตและทำงานตามที่ระบุ

นี่คือสิ่งที่ฉันหมายถึง:

$ wc << EOF
> one two three
> four five
> EOF
 2  5 24

ในตัวอย่างนี้เราบอกให้wcโปรแกรมรอEOFสายอักขระจากนั้นพิมพ์คำห้าคำจากนั้นพิมพ์EOFสัญญาณเพื่อบอกว่าเราป้อนข้อมูลเสร็จแล้ว ผลก็คล้ายกับทำงานwcด้วยตัวเองพิมพ์คำแล้วกดCtrlD

ในทุบตีเหล่านี้จะดำเนินการผ่านไฟล์ temp มักจะอยู่ในรูปแบบ/tmp/sh-thd.<random string>ในขณะที่พวกเขาจะถูกนำไปใช้เป็นประท่อที่ไม่ระบุชื่อ สิ่งนี้สามารถสังเกตได้ผ่านการติดตามการเรียกของระบบด้วยstraceคำสั่ง แทนที่bashด้วยshเพื่อดูวิธี/bin/shการเปลี่ยนเส้นทางนี้

$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'

นี่สตริง

<<<here-stringเป็นที่รู้จักกัน แทนที่จะพิมพ์ข้อความคุณให้สตริงข้อความที่ทำไว้ล่วงหน้าให้กับโปรแกรม ตัวอย่างเช่นด้วยโปรแกรมเช่นที่bcเราสามารถทำได้bc <<< 5*4เพื่อรับเอาท์พุทสำหรับกรณีเฉพาะนั้นไม่จำเป็นต้องรัน bc แบบโต้ตอบ

นี่สตริงในทุบตีจะดำเนินการผ่านไฟล์ชั่วคราวมักจะอยู่ในรูปแบบ/tmp/sh-thd.<random string>ซึ่งจะถูกยกเลิกการเชื่อมโยงในภายหลังจึงทำให้พวกเขาครอบครองพื้นที่หน่วยความจำบางส่วนชั่วคราว แต่ไม่ปรากฏในรายการของ/tmpรายการไดเรกทอรีและมีอยู่อย่างมีประสิทธิภาพเป็นไฟล์ที่ไม่ระบุชื่อ ถูกอ้างอิงผ่าน file descriptor โดยเชลล์เองและ descriptor ไฟล์นั้นจะถูกสืบทอดโดยคำสั่งและทำซ้ำในภายหลังบน file descriptor 0 (stdin) ผ่านทางdup2()ฟังก์ชัน สามารถสังเกตได้ผ่านทาง

$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd

และผ่านการติดตาม syscalls (output สั้นลงเพื่อให้สามารถอ่าน; แจ้งให้ทราบว่าไฟล์ temp จะเปิดเป็น FD 3 ข้อมูลที่เขียนไปแล้วก็เป็นอีกครั้งที่เปิดO_RDONLYสถานะเป็น FD 4 และต่อมายกเลิกการเชื่อมโยงแล้วdup2()บน FD 0 ซึ่งเป็นกรรมพันธุ์ด้วยcatในภายหลัง ):

$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4)         = 4
[pid 10229] write(3, "\n", 1)           = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0)                  = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072)   = 5
[pid 10229] write(1, "TEST\n", 5TEST
)       = 5
[pid 10229] read(0, "", 131072)         = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

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

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

ดังที่tldp.orgอธิบาย

การทดแทนกระบวนการฟีดเอาต์พุตของกระบวนการ (หรือกระบวนการ) ลงใน stdin ของกระบวนการอื่น

ดังนั้นในผลกระทบนี้จะคล้ายกับท่อstdoutecho foobar barfoo | wcของคำสั่งหนึ่งไปยังอีกเช่น แต่แจ้งให้ทราบล่วงหน้า: ในmanpage ทุบตี<(list)คุณจะเห็นว่ามันจะแสดงเป็น ดังนั้นโดยทั่วไปคุณสามารถเปลี่ยนทิศทางเอาต์พุตของคำสั่งหลายคำสั่ง (!)

หมายเหตุ:ในทางเทคนิคเมื่อคุณพูดว่า< <คุณไม่ได้หมายถึงสิ่งหนึ่ง แต่สองเปลี่ยนเส้นทางเดียวกับ การเปลี่ยนเส้นทางและขั้นตอนของการส่งออกจาก<<( . . .)

ตอนนี้จะเกิดอะไรขึ้นถ้าเราแค่ประมวลผลการทดแทน?

$ echo <(echo bar)
/dev/fd/63

อย่างที่คุณเห็นเชลล์สร้างตัวบ่งชี้ไฟล์ชั่วคราว/dev/fd/63โดยที่เอาต์พุตไป (ซึ่งตามคำตอบของ Gilles คือไพพ์ที่ไม่ระบุชื่อ) นั่นหมายถึงการ <เปลี่ยนเส้นทางตัวบอกไฟล์นั้นเป็นอินพุตในคำสั่ง

ตัวอย่างง่ายๆดังนั้นจะทำการทดแทนกระบวนการของการส่งออกจากสองคำสั่ง echo เป็น wc:

$ wc < <(echo bar;echo foo)
      2       2       8

ดังนั้นที่นี่เราทำให้เชลล์สร้าง file descriptor สำหรับผลลัพธ์ทั้งหมดที่เกิดขึ้นในวงเล็บและเปลี่ยนเส้นทางที่เป็นอินพุตตามที่wcคาดไว้ wc ได้รับกระแสนั้นจากสองคำสั่ง echo ซึ่งโดยตัวมันเองจะออกสองบรรทัดแต่ละคำมี และเหมาะสมเรามี 2 คำ 2 บรรทัดและ 6 ตัวอักษรบวกสองบรรทัดใหม่นับ

หมายเหตุด้านข้าง: การทดแทนกระบวนการอาจเรียกว่าbashism (คำสั่งหรือโครงสร้างที่ใช้งานได้ในเชลล์ขั้นสูงเช่นbashแต่ไม่ได้ระบุโดย POSIX) แต่ถูกนำไปใช้kshก่อนการมีอยู่ของ bash เป็นหน้า kshและคำตอบนี้แนะนำ เชลล์ชอบtcshและmkshไม่ได้ทดแทนกระบวนการ แล้วเราจะไปเปลี่ยนเส้นทางเอาต์พุตของหลาย ๆ คำสั่งไปยังคำสั่งอื่นโดยไม่ทดแทนกระบวนการได้อย่างไร? การจัดกลุ่มรวมถึงการวางท่อ!

$ (echo foo;echo bar) | wc
      2       2       8

ได้อย่างมีประสิทธิภาพนี้เป็นเช่นเดียวกับตัวอย่างข้างต้น แต่นี้จะแตกต่างกันภายใต้ประทุนจากกระบวนการทดแทนเนื่องจากเราทำให้ stdout ของ subshell ทั้งหมดและ stdin ของการเชื่อมโยงกับท่อwc ในทางกลับกันการทดแทนกระบวนการทำให้คำสั่งอ่าน descriptor ไฟล์ชั่วคราว

ดังนั้นถ้าเราสามารถจัดกลุ่มกับ piping ทำไมเราถึงต้องการการทดแทนกระบวนการ เพราะบางครั้งเราไม่สามารถใช้ท่อได้ ลองพิจารณาตัวอย่างด้านล่าง - เปรียบเทียบผลลัพธ์ของสองคำสั่งด้วยdiff(ซึ่งต้องการสองไฟล์และในกรณีนี้เราจะให้สองตัวอธิบายไฟล์)

diff <(ls /bin) <(ls /usr/bin)

7
< <ถูกนำมาใช้เมื่อมีการรับ stdin จากทดแทนกระบวนการ cmd1 < <(cmd2)คำสั่งดังกล่าวอาจมีลักษณะเช่น: ตัวอย่างเช่นwc < <(date)
John1024

4
ใช่การทดแทนกระบวนการได้รับการสนับสนุนโดยbash, zsh และ AT&T ksh {88,93} แต่ไม่ใช่โดย pdksh / mksh
John1024

2
< < ไม่ได้เป็นสิ่งที่ด้วยตัวเองในกรณีของกระบวนการทดแทนจะเป็นเพียง<ตามมาด้วยอย่างอื่นที่เกิดขึ้นจะเริ่มต้นด้วย<
immibis

1
@muru เท่าที่ฉันรู้<<<ถูกนำมาใช้ครั้งแรกโดยพอร์ต Unix ของ Plan 9 rc shell จากนั้นนำ zsh, bash และ ksh93 มาใช้ในภายหลัง ฉันจะไม่เรียกมันว่าเป็นการทุบตี
jlliagre

3
อีกตัวอย่างของการที่ piping ไม่สามารถใช้: echo 'foo' | read; echo ${REPLY}จะไม่ส่งคืนfooเนื่องจากreadเริ่มต้นใน sub-shell - piping เริ่ม sub-shell อย่างไรก็ตามread < <(echo 'foo'); echo ${REPLY}ส่งคืนอย่างถูกต้องfooเนื่องจากไม่มี sub-shell
Paddy Landau

26

< < เป็นข้อผิดพลาดทางไวยากรณ์:

$ cat < <
bash: syntax error near unexpected token `<'

< <()เป็นการทดแทนกระบวนการ ( <()) รวมกับการเปลี่ยนเส้นทาง ( <):

ตัวอย่างที่วางแผนไว้:

$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63

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

เพื่อให้ชัดเจนไม่มี< <ผู้ปฏิบัติงาน


ฉันได้รับคำตอบจากคุณว่า <<() มีประโยชน์มากกว่า <() ใช่ไหม?
solfish

1
@solfish <()ให้ชื่อไฟล์ที่เหมือนกันดังนั้นจึงมีประโยชน์มากกว่า - < <()เปลี่ยน stdin โดยที่ไม่จำเป็น ในwcหลังเกิดขึ้นจะมีประโยชน์มากขึ้น มันอาจมีประโยชน์น้อยกว่าที่อื่น
muru

12

< <เป็นข้อผิดพลาดทางไวยากรณ์คุณอาจหมายถึงว่าcommand1 < <( command2 )การเปลี่ยนเส้นทางอินพุตอย่างง่ายตามด้วยการทดแทนกระบวนการและคล้ายกันมาก แต่ไม่เทียบเท่ากับ:

command2 | command1

ความแตกต่างที่สมมติว่าคุณกำลังเรียกใช้bashอยู่command1ถูกเรียกใช้ใน subshell ในกรณีที่สองในขณะที่มันรันในเชลล์ปัจจุบันในอันแรก นั่นหมายความว่าตัวแปรที่ตั้งค่าไว้command1จะไม่สูญหายไปด้วยชุดตัวเลือกการแทนที่กระบวนการ


11

< <จะให้ข้อผิดพลาดทางไวยากรณ์ การใช้งานที่เหมาะสมมีดังนี้:

การอธิบายด้วยความช่วยเหลือของตัวอย่าง:

ตัวอย่างสำหรับ< <():

while read line;do
   echo $line
done< <(ls)

ในตัวอย่างข้างต้นอินพุตไปยัง while loop จะมาจากlsคำสั่งที่สามารถอ่านทีละบรรทัดและechoed ในลูป

<()ใช้สำหรับการทดแทนกระบวนการ ข้อมูลเพิ่มเติมและตัวอย่างสำหรับ<()สามารถพบได้ที่ลิงค์นี้:

กระบวนการทดแทนและท่อ

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