หมายเหตุ: คำตอบสะท้อนถึงความเข้าใจของฉันเกี่ยวกับกลไกเหล่านี้เป็นปัจจุบันสะสมมากกว่าการวิจัยและการอ่านคำตอบของเพื่อนในเว็บไซต์นี้และunix.stackexchange.comและจะได้รับการปรับปรุงเมื่อเวลาผ่านไป อย่าลังเลที่จะถามคำถามหรือเสนอแนะการปรับปรุงในความคิดเห็น ฉันยังแนะนำให้คุณลองดูว่า syscalls ทำงานอย่างไรในเชลล์ด้วยstrace
คำสั่ง นอกจากนี้โปรดอย่าถูกข่มขู่ด้วยแนวคิดเรื่องภายในหรือตึกระฟ้า - คุณไม่จำเป็นต้องรู้หรือสามารถใช้มันเพื่อทำความเข้าใจว่าเชลล์ทำสิ่งต่าง ๆ อย่างไร แต่พวกเขาช่วยทำความเข้าใจได้อย่างแน่นอน
TL; DR
|
ไพพ์ไม่เกี่ยวข้องกับรายการบนดิสก์ดังนั้นจึงไม่มีหมายเลขinodeของระบบไฟล์ดิสก์ (แต่มี inode ในpipefsระบบไฟล์เสมือนใน kernel-space) แต่การเปลี่ยนเส้นทางมักเกี่ยวข้องกับไฟล์ซึ่งมีรายการดิสก์และดังนั้นจึงมีความสอดคล้องกัน inode
- ไปป์ไม่
lseek()
สามารถทำได้ดังนั้นคำสั่งไม่สามารถอ่านข้อมูลบางส่วนแล้วย้อนกลับ แต่เมื่อคุณเปลี่ยนเส้นทางด้วย>
หรือ<
โดยปกติจะเป็นไฟล์ที่lseek()
สามารถวัตถุได้ดังนั้นคำสั่งจึงสามารถนำทางได้ตามต้องการ
- การเปลี่ยนเส้นทางเป็นการจัดการกับ file descriptors ซึ่งมีได้หลายอย่าง pipes มีคำอธิบายไฟล์เพียงสองคำเท่านั้นคำสั่งสำหรับคำสั่ง left และคำสั่ง right สำหรับคำสั่ง
- การเปลี่ยนเส้นทางบนสตรีมและไพพ์มาตรฐานนั้นทั้งคู่ถูกบัฟเฟอร์
- ท่อมักจะเกี่ยวข้องกับการฟอร์กดังนั้นจึงมีคู่ของกระบวนการที่เกี่ยวข้อง การเปลี่ยนเส้นทาง - ไม่เสมอไปแม้ว่าในทั้งสองกรณีผลลัพธ์ของตัวอธิบายไฟล์จะได้รับมรดกโดยกระบวนการย่อย
- ไพพ์เชื่อมต่อตัวอธิบายไฟล์ (คู่) เสมอการเปลี่ยนเส้นทาง - ใช้ชื่อพา ธ หรือตัวอธิบายไฟล์
- ไปป์เป็นวิธีการสื่อสารระหว่างกระบวนการในขณะที่การเปลี่ยนเส้นทางเป็นเพียงการจัดการกับไฟล์ที่เปิดหรือวัตถุที่มีลักษณะคล้ายไฟล์
- ทั้งสองใช้
dup2()
syscalls ใต้ฝากระโปรงเพื่อจัดทำสำเนาของตัวให้คำอธิบายไฟล์ซึ่งการไหลของข้อมูลที่เกิดขึ้นจริง
- การเปลี่ยนเส้นทางสามารถนำไปใช้ "ทั่วโลก" ด้วย
exec
คำสั่งในตัว (ดูนี้และสิ่งนี้ ) ดังนั้นหากคุณทำexec > output.txt
ทุกคำสั่งจะเขียนถึงoutput.txt
จากนั้นเป็นต้นไป |
ไพพ์จะใช้กับคำสั่งปัจจุบันเท่านั้น (ซึ่งหมายถึงคำสั่งแบบง่ายหรือคำสั่งย่อยเช่นseq 5 | (head -n1; head -n2)
คำสั่งผสมหรือคำสั่งผสม
เมื่อเปลี่ยนเส้นทางจะทำในไฟล์สิ่งที่ต้องการecho "TEST" > file
และecho "TEST" >> file
การใช้งานทั้งopen()
syscall ที่ไฟล์นั้น ( ดู ) dup2()
และได้รับการอธิบายไฟล์จากมันจะผ่านมันไป ท่อ|
ใช้pipe()
และdup2()
syscall เท่านั้น
เท่าที่คำสั่งถูกดำเนินการไปป์และการเปลี่ยนเส้นทางไม่เกิน file descriptors - วัตถุเหมือนไฟล์ที่พวกเขาอาจเขียนสุ่มสี่สุ่มห้าหรือจัดการกับพวกเขาภายใน (ซึ่งอาจก่อให้เกิดพฤติกรรมที่ไม่คาดคิดapt
เช่นมีแนวโน้มที่จะไม่เขียนแม้แต่ถ้ามันรู้ว่ามีการเปลี่ยนเส้นทาง)
บทนำ
เพื่อที่จะเข้าใจว่ากลไกทั้งสองนี้แตกต่างกันอย่างไรจึงจำเป็นต้องเข้าใจคุณสมบัติที่สำคัญประวัติหลังทั้งสองและรากของพวกเขาในภาษาซี ในความเป็นจริงรู้ว่าสิ่งที่อธิบายไฟล์และวิธีdup2()
และสายระบบการทำงานเป็นสิ่งจำเป็นเช่นเดียวกับpipe()
lseek()
เชลล์มีจุดประสงค์เพื่อทำให้กลไกเหล่านี้เป็นนามธรรมแก่ผู้ใช้ แต่การขุดลึกกว่าสิ่งที่เป็นนามธรรมจะช่วยให้เข้าใจถึงลักษณะที่แท้จริงของพฤติกรรมของเชลล์
ต้นกำเนิดของการเปลี่ยนเส้นทางและท่อ
อ้างอิงจากบทความเดนนิส Ritche ของคำทำนาย Petroglyphsท่อมาจากบันทึกภายใน 1964โดยมัลคอล์ดักลาส McIlroy , ในขณะที่พวกเขากำลังทำงานบนระบบปฏิบัติการ Multics อ้างถึง:
เพื่อนำข้อกังวลที่แข็งแกร่งที่สุดมาสรุป:
- เราควรมีวิธีการเชื่อมต่อโปรแกรมเช่นสายสวน - สกรูในส่วนอื่นเมื่อมันกลายเป็นเมื่อมีความจำเป็นในการนวดข้อมูลในอีกทางหนึ่ง นี่เป็นวิธีการของ IO ด้วย
สิ่งที่เห็นได้ชัดคือในเวลานั้นโปรแกรมสามารถเขียนลงดิสก์ได้ แต่ก็ไม่มีประสิทธิภาพหากเอาต์พุตมีขนาดใหญ่ หากต้องการอ้างอิงคำอธิบายของ Brian Kernighan ในวิดีโอUnix Pipeline :
ก่อนอื่นคุณไม่จำเป็นต้องเขียนโปรแกรมขนาดใหญ่อันยิ่งใหญ่ - คุณมีโปรแกรมขนาดเล็กที่มีอยู่แล้วซึ่งอาจทำหน้าที่บางส่วนของงานได้ ... อีกอย่างก็คือเป็นไปได้ว่าปริมาณข้อมูลที่คุณกำลังประมวลผลจะไม่เหมาะสมหาก คุณเก็บไว้ในไฟล์ ... เพราะจำไว้ว่าเราย้อนกลับไปในวันที่ดิสก์ในสิ่งเหล่านี้มีถ้าคุณโชคดีมีเมกะไบต์หรือสองข้อมูล ... ดังนั้นไปป์ไลน์ไม่ต้องยกตัวอย่างผลลัพธ์ทั้งหมด .
ดังนั้นความแตกต่างทางแนวคิดจึงชัดเจน: ไพพ์เป็นกลไกในการทำให้โปรแกรมพูดคุยกัน การเปลี่ยนเส้นทาง - เป็นวิธีการเขียนไฟล์ในระดับพื้นฐาน ในทั้งสองกรณีเปลือกทำให้ทั้งสองสิ่งนี้ง่าย แต่ใต้ฝากระโปรงมีอะไรเกิดขึ้นมากมาย
จะลึกลงไป: syscalls และการทำงานภายในของเชลล์
เราเริ่มต้นด้วยความคิดของไฟล์อธิบาย อธิบายไฟล์โดยทั่วไปไฟล์ที่เปิด (ไม่ว่าจะเป็นไฟล์บนดิสก์หรือในหน่วยความจำหรือไฟล์ที่ไม่ระบุชื่อ) ซึ่งจะถูกแสดงด้วยจำนวนเต็ม สตรีมข้อมูลมาตรฐานสองรายการ (stdin, stdout, stderr) คือตัวอธิบายไฟล์ 0,1 และ 2 ตามลำดับ พวกเขามาจากที่ไหน ? ในคำสั่งเชลล์ตัวอธิบายไฟล์นั้นสืบทอดมาจาก parent - shell และโดยทั่วไปแล้วสำหรับกระบวนการทั้งหมด - กระบวนการลูกสืบทอดตัวบ่งชี้ไฟล์ของผู้ปกครอง สำหรับdaemonsเป็นเรื่องปกติที่จะปิด descriptor ไฟล์ที่สืบทอดทั้งหมดและ / หรือเปลี่ยนเส้นทางไปยังที่อื่น
กลับสู่การเปลี่ยนเส้นทาง มันคืออะไรจริงๆ? มันเป็นกลไกที่บอกให้เชลล์เตรียมไฟล์ descriptors สำหรับคำสั่ง (เนื่องจากการเปลี่ยนเส้นทางทำโดยเชลล์ก่อนที่คำสั่งจะรัน) และชี้ไปที่ตำแหน่งที่ผู้ใช้แนะนำ ความคมชัดมาตรฐานของการเปลี่ยนเส้นทางการส่งออกเป็น
[n]>word
นั่น[n]
คือหมายเลขตัวอธิบายไฟล์ เมื่อคุณทำเช่นecho "Something" > /dev/null
หมายเลข 1 echo 2> /dev/null
เป็นนัยที่นั่นและ
ภายใต้ประทุนทำโดยการทำซ้ำ descriptor ไฟล์ผ่านการdup2()
เรียกของระบบ df > /dev/null
ลองมา เชลล์จะสร้างกระบวนการลูกที่df
ทำงาน แต่ก่อนหน้านั้นจะเปิด/dev/null
เป็น file descriptor # 3 และdup2(3,1)
จะออกซึ่งจะทำสำเนาของ file descriptor 3 และสำเนาจะเป็น 1 คุณรู้ว่าคุณมีสองไฟล์file1.txt
และอย่างไรfile2.txt
และเมื่อคุณทำcp file1.txt file2.txt
คุณจะมีไฟล์สองไฟล์เหมือนกัน แต่คุณสามารถจัดการได้อย่างอิสระ นั่นเป็นสิ่งเดียวกันที่เกิดขึ้นที่นี่ บ่อยครั้งที่คุณจะเห็นว่าก่อนที่จะใช้งานสิ่งที่bash
ต้องทำdup(1,10)
เพื่อทำสำเนาไฟล์ descriptor # 1 ซึ่งคือstdout
(และสำเนานั้นจะเป็น fd # 10) เพื่อเรียกคืนในภายหลัง สิ่งสำคัญคือให้สังเกตว่าเมื่อคุณพิจารณาคำสั่งในตัว(ซึ่งเป็นส่วนหนึ่งของเชลล์เองและไม่มีไฟล์ใน/bin
หรือที่อื่น ๆ ) หรือคำสั่งง่าย ๆ ในเชลล์ที่ไม่มีการโต้ตอบเชลล์จะไม่สร้างกระบวนการลูก
และจากนั้นเรามีสิ่งที่ต้องการและ[n]>&[m]
[n]&<[m]
นี่คือการทำสำเนาไฟล์ descriptors ซึ่งกลไกเดียวกับdup2()
ตอนนี้มันอยู่ในรูปแบบเชลล์ซึ่งพร้อมใช้งานสำหรับผู้ใช้
หนึ่งในสิ่งสำคัญที่ควรทราบเกี่ยวกับการเปลี่ยนเส้นทางคือลำดับของพวกเขาไม่ได้รับการแก้ไข แต่มีความสำคัญต่อวิธีที่เชลล์ตีความสิ่งที่ผู้ใช้ต้องการ เปรียบเทียบสิ่งต่อไปนี้:
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
การใช้ประโยชน์เหล่านี้ในการเขียนสคริปต์เชลล์สามารถใช้งานได้หลากหลาย:
และอื่น ๆ อีกมากมาย
ท่อประปาด้วยpipe()
และdup2()
ดังนั้นท่อจะถูกสร้างขึ้นได้อย่างไร? ผ่านpipe()
syscallซึ่งจะใช้เป็นอินพุตอาร์เรย์ (รายการ aka) ที่เรียกว่าประเภทpipefd
สองรายการint
(จำนวนเต็ม) จำนวนเต็มสองตัวนั้นเป็นตัวอธิบายไฟล์ pipefd[0]
จะสิ้นสุดการอ่านของท่อและpipefd[1]
จะเป็นจุดสิ้นสุดการเขียน ดังนั้นในdf | grep 'foo'
, grep
จะได้รับสำเนาของpipefd[0]
และ จะได้รับสำเนาของdf
pipefd[1]
แต่อย่างไร แน่นอนว่าด้วยความมหัศจรรย์ของdup2()
syscall สำหรับdf
ในตัวอย่างสมมติว่าpipefd[1]
มี # 4 เพื่อให้เปลือกจะทำให้เด็กทำdup2(4,1)
(จำของฉัน cp
เช่น?) แล้วทำเพื่อให้ทำงานได้จริงexecve()
df
ธรรมชาติdf
จะสืบทอดไฟล์ descriptor # 1 แต่จะไม่รู้ว่ามันไม่ได้ชี้ไปที่เทอร์มินัลอีกต่อไป แต่จริงๆแล้ว fd # 4 ซึ่งจริงๆแล้วเป็นจุดสิ้นสุดการเขียนของไพพ์ โดยธรรมชาติแล้วสิ่งเดียวกันจะเกิดขึ้นพร้อมgrep 'foo'
ยกเว้นจำนวนตัวอธิบายไฟล์ที่แตกต่างกัน
ตอนนี้คำถามที่น่าสนใจ: เราสามารถสร้างท่อที่เปลี่ยนเส้นทาง fd # 2 ได้หรือไม่ไม่ใช่แค่ fd # 1? ใช่ในความเป็นจริงนั่นคือสิ่งที่|&
เกิดขึ้นในการทุบตี มาตรฐาน POSIX ต้องการภาษาคำสั่งเชลล์เพื่อสนับสนุนdf 2>&1 | grep 'foo'
ไวยากรณ์สำหรับวัตถุประสงค์นั้น แต่bash
ทำได้|&
เช่นกัน
สิ่งสำคัญที่ควรทราบคือไพพ์จะจัดการกับ file descriptors เสมอ มีไพพ์ที่มีอยู่FIFO
หรือมีชื่อซึ่งมีชื่อไฟล์บนดิสก์และให้คุณใช้มันเป็นไฟล์ แต่ทำงานเหมือนไพพ์ แต่|
ประเภทของท่อเป็นสิ่งที่รู้จักกันในชื่อท่อไม่ระบุชื่อ - พวกเขาไม่มีชื่อไฟล์เพราะพวกเขาเพียงสองวัตถุเชื่อมต่อกัน ความจริงที่ว่าเราไม่ได้จัดการกับไฟล์ก็มีความหมายที่สำคัญเช่นกัน: ท่อไม่lseek()
สามารถทำได้ ไฟล์ทั้งในหน่วยความจำหรือบนดิสก์เป็นแบบสแตติก - โปรแกรมสามารถใช้lseek()
syscall เพื่อข้ามไปยังไบต์ 120 จากนั้นกลับสู่ไบต์ 10 จากนั้นส่งต่อไปจนสุด ท่อไม่คงที่ - เป็นแบบต่อเนื่องดังนั้นคุณจึงไม่สามารถย้อนกลับข้อมูลที่ได้จากพวกเขาด้วยlseek()
. นี่คือสิ่งที่ทำให้บางโปรแกรมทราบว่าพวกเขากำลังอ่านจากไฟล์หรือจากไปป์และทำให้พวกเขาสามารถทำการปรับเปลี่ยนที่จำเป็นสำหรับประสิทธิภาพที่มีประสิทธิภาพ; ในคำอื่น ๆprog
สามารถตรวจสอบได้หากฉันทำหรือcat file.txt | prog
prog < input.txt
ตัวอย่างเช่นการทำงานจริงที่เป็นหาง
คุณสมบัติที่น่าสนใจอีกสองอย่างของไพพ์คือพวกเขามีบัฟเฟอร์ซึ่งบน Linux คือ 4096 ไบต์และพวกเขามีระบบไฟล์ตามที่กำหนดไว้ในซอร์สโค้ด Linux ! พวกเขาไม่ได้เป็นเพียงวัตถุสำหรับส่งข้อมูลไปรอบ ๆ พวกมันเป็นโครงสร้างข้อมูลเอง! ในความเป็นจริงเนื่องจากมีระบบไฟล์ pipefs ซึ่งจัดการทั้งไพพ์และ FIFOs ไพพ์ จึงมีหมายเลขไอโหนดบนระบบไฟล์ที่เกี่ยวข้อง:
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
บนท่อ Linux เป็นแบบทิศทางเดียวเช่นเดียวกับการเปลี่ยนเส้นทาง ในการใช้งานที่คล้าย Unix บางอย่าง - มีท่อสองทิศทาง ถึงแม้ว่าจะมีความมหัศจรรย์ของการเขียนสคริปต์เชลล์คุณสามารถสร้างท่อสองทิศทางบน Linux ได้เช่นกัน
ดูสิ่งนี้ด้วย:
thing1 > temp_file && thing2 < temp_file
การทำท่อได้ง่ายขึ้น แต่ทำไมไม่ใช้ตัว>
ดำเนินการอีกครั้งเพื่อทำสิ่งนี้เช่นthing1 > thing2
สำหรับคำสั่งthing1
และthing2
? ทำไมผู้ประกอบการพิเศษ|
?