สร้างเชลล์ย่อยด้วยเครื่องหมายปีกกา


31

ตามนี้ , วางรายการของคำสั่งระหว่างวงเล็บปีกกาทำให้เกิดรายการที่จะดำเนินการในบริบทเปลือกปัจจุบัน ไม่มี subshell จะถูกสร้างขึ้น

ใช้psเพื่อดูสิ่งนี้ในการทำงาน

นี่คือลำดับชั้นกระบวนการสำหรับไพพ์ไลน์กระบวนการดำเนินการโดยตรงบนบรรทัดคำสั่ง 4398 เป็น PID สำหรับเชลล์ล็อกอิน:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

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

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

ตอนนี้นี่คือลำดับชั้นกระบวนการเมื่อsleepวางท่อไว้ในวงเล็บปีกกา (ดังนั้นจะมีสองระดับของวงเล็บทั้งหมด)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

เหตุใดจึงbashต้องสร้าง subshell ให้ทำงานsleepในกรณีที่ 3 เมื่อเอกสารระบุว่าคำสั่งระหว่างวงเล็บปีกกาจะถูกดำเนินการในบริบทเชลล์ปัจจุบัน?


น่าสนใจฉันจะเดาว่าเป็นเพราะในกรณีที่ 3 กลุ่มภายในเป็นส่วนหนึ่งของไพพ์ไลน์และด้วยเหตุนี้มันจึงถูกเรียกใช้ใน sub-shell เช่นการเรียกใช้ฟังก์ชันอื่นซึ่งเป็นส่วนหนึ่งของไพพ์ไลน์ มันสมเหตุสมผลหรือไม่
Miroslav Koškár

2
ฉันจะไม่พูดว่า "หอยต้อง" เพียงเพราะมัน ... ท่อไม่ได้ดำเนินการในบริบทเชลล์ หากไพพ์ไลน์ประกอบด้วยอะไรนอกจากคำสั่งภายนอกดังนั้นการสร้างกระบวนการย่อยก็เพียงพอแล้ว { sleep 2 | command ps -H; }
Hauke ​​Laging

คำตอบ:


26

ในไปป์ไลน์คำสั่งทั้งหมดจะทำงานพร้อมกัน (ด้วย stdout / stdin ที่เชื่อมต่อโดยไพพ์) ดังนั้นในกระบวนการต่าง ๆ

ใน

cmd1 | cmd2 | cmd3

คำสั่งทั้งสามนั้นทำงานในกระบวนการที่แตกต่างกันดังนั้นอย่างน้อยสองคำสั่งจึงต้องทำงานในกระบวนการลูก เชลล์บางตัวเรียกใช้หนึ่งในนั้นในกระบวนการเชลล์ปัจจุบัน (หาก builtin like readหรือถ้าไพพ์ไลน์เป็นคำสั่งสุดท้ายของสคริปต์) แต่bashรันพวกมันทั้งหมดในกระบวนการแยกต่างหากของตัวเอง (ยกเว้นlastpipeตัวเลือกในbashเวอร์ชันล่าสุดและภายใต้เงื่อนไขบางอย่าง )

{...}คำสั่งกลุ่ม หากกลุ่มนั้นเป็นส่วนหนึ่งของไปป์ไลน์มันจะต้องทำงานในกระบวนการที่แยกต่างหากเช่นเดียวกับคำสั่งง่ายๆ

ใน:

{ a; b "$?"; } | c

เราต้องการเชลล์เพื่อประเมินว่าa; b "$?"เป็นกระบวนการที่แยกจากกันดังนั้นเราจึงต้องการเชลล์ย่อย เชลล์สามารถปรับให้เหมาะสมโดยไม่ Forking bเพราะมันเป็นคำสั่งสุดท้ายที่จะทำงานในกลุ่มนั้น เปลือกหอยบางคนทำ bashแต่เห็นได้ชัดว่าไม่


" ทั้งสามคำสั่งทำงานในกระบวนการที่แตกต่างกันดังนั้นอย่างน้อยสองคำสั่งจึงต้องทำงานใน subshell " ทำไม subshell จึงจำเป็นในกรณีนี้ เชลล์พาเรนต์ไม่สามารถวางไข่กระบวนการต้นไม้ได้หรือไม่? หรือเมื่อคุณพูดว่า " ต้องทำงานใน subshell " คุณหมายถึงว่าเชลล์จะแยกตัวเองแล้วประมวลผลสำหรับแต่ละ cmd1 cmd2 และ cmd3? หากฉันดำเนินการนี้bash -c "sleep 112345 | cat | cat "ฉันจะเห็นเพียงหนึ่งทุบตีสร้างขึ้นแล้วเด็ก 3 คนไปโดยไม่ต้อง bash ย่อยอื่น interleaving
Hakan Baba

" เราต้องการเชลล์เพื่อประเมินว่า a; b" $? "เป็นกระบวนการแยกจากกันดังนั้นเราจึงต้องการ subshell " คุณสามารถขยายเหตุผลได้หรือไม่ ทำไมเราต้องมีกลุ่มย่อยเพื่อที่จะเข้าใจสิ่งนั้น? ต้องเข้าใจอะไรบ้าง ฉันคิดว่าจำเป็นต้องมีการแยกวิเคราะห์ แต่มีอะไรอีกบ้าง . เชลล์พาเรนต์ไม่สามารถแยกวิเคราะห์ได้a; b "$?"หรือไม่? มีความต้องการขั้นพื้นฐานสำหรับ subsheel หรืออาจเป็นการตัดสินใจออกแบบ / ติดตั้งบน bash หรือไม่?
Hakan Baba

@HakanBaba ฉันเปลี่ยนไปเป็น "กระบวนการลูก" เพื่อหลีกเลี่ยงความสับสนที่อาจเกิดขึ้น
Stéphane Chazelas

1
@HakanBaba การแยกวิเคราะห์เสร็จสิ้นใน parent (กระบวนการที่อ่านรหัสหนึ่งที่ดำเนินการล่ามยกเว้นว่ารหัสนั้นถูกส่งไปยังeval) แต่การประเมินผล (เรียกใช้คำสั่งแรกรอมันรันที่สอง) คือ ทำในเด็กคนที่มี stdout เชื่อมต่อกับท่อ
Stéphane Chazelas

ใน{ sleep 2 | ps -H; }parent bash เห็นsleep 2ว่าต้องใช้ fork / exec แต่ใน{ { sleep 2; } | ps -H; }bash หลักเห็น{ sleep 2; }ในคำอื่น ๆ บางรหัสทุบตี ดูเหมือนว่าพาเรนต์จะจัดการกับ fork / exec สำหรับsleep 2แต่จะสร้าง bash ใหม่แบบเรียกซ้ำเพื่อจัดการกับรหัส bash ที่พบ นั่นคือความเข้าใจของฉันมันทำให้รู้สึก?
Hakan Baba

19

การทำเครื่องหมายวงเล็บปีกกาจะดูเหมือนว่าคุณกำลังสร้างขอบเขตเพิ่มเติมในระดับที่ต้องการให้เรียกใช้ sub-shell ใหม่ คุณสามารถเห็นเอฟเฟกต์นี้ด้วยสำเนา Bash ที่ 2 ในps -Hผลลัพธ์ของคุณ

เฉพาะกระบวนการที่ระบุไว้ในระดับแรกของเครื่องหมายปีกกาหยิกเท่านั้นที่จะทำงานภายในขอบเขตของเปลือก Bash ดั้งเดิม วงเล็บปีกกาแบบซ้อนใด ๆ จะทำงานในเปลือก Bash ที่กำหนดขอบเขต

ตัวอย่าง

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

เอา| ps -Hออกจากการผสมเพียงเพื่อให้เราสามารถเห็นวงเล็บปีกกาที่ซ้อนกันเราสามารถทำงานps auxf | lessในเปลือกอื่น

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

แต่รอยังมีอีก!

หากคุณนำท่อออกมาและใช้คำสั่งในรูปแบบนี้เราจะเห็นสิ่งที่คุณคาดหวัง:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

ตอนนี้ในหน้าต่างดูผลลัพธ์เราจะได้รับการอัปเดตทุก ๆ 2 วินาทีของสิ่งที่เกิดขึ้น:

นี่เป็นครั้งแรกsleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

นี่คือที่สองsleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

นี่คือที่สามsleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

สังเกตุทั้งสาม sleeps ที่เรียกใช้ในการจัดฟันในระดับที่แตกต่างกันของเหล็กค้ำยันจะอยู่ภายใน PID 5676 ของ Bash | ps -Hดังนั้นผมเชื่อว่าปัญหาของคุณได้เป็นตัวเองลือที่มีการใช้

สรุปผลการวิจัย

การใช้| ps -H(เช่นไพพ์) ทำให้เกิด sub-shell เพิ่มเติมดังนั้นอย่าใช้วิธีนั้นเมื่อพยายามที่จะซักถามว่าเกิดอะไรขึ้น


ดังนั้น "เฉพาะกระบวนการที่ระบุไว้ในระดับแรกของเครื่องหมายปีกกาหยิกเท่านั้นที่จะทำงานภายในขอบเขตของเปลือก Bash ดั้งเดิม"
xealits

@xealits - เป็นคำถามที่ตามมาหรือเปล่าที่คุณถามฉัน
slm

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

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

@ xealits - ถูกต้อง
slm

7

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

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1

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