กฎสำหรับเรียกใช้ subshell ใน Bash?


24

ฉันดูเหมือนจะเข้าใจผิดกฎของ Bash ในการสร้าง subshell ฉันคิดว่าวงเล็บจะสร้าง subshell เสมอซึ่งจะทำงานเหมือนกระบวนการของตัวเอง

อย่างไรก็ตามนี่ไม่ใช่กรณี ใน Code Snippet A (ด้านล่าง) sleepคำสั่งที่สองไม่ได้ทำงานในเชลล์แยกต่างหาก (ตามที่กำหนดโดยpstreeเทอร์มินัลอื่น) อย่างไรก็ตามใน Code Snippet B sleepคำสั่งที่สองจะทำงานในเชลล์แยกต่างหาก ความแตกต่างเพียงอย่างเดียวระหว่างตัวอย่างก็คือตัวอย่างที่สองมีสองคำสั่งภายในวงเล็บ

มีใครบ้างที่โปรดอธิบายกฎสำหรับตอนที่สร้าง subshells

รหัส SNIPET A:

sleep 5
(
sleep 5
)

รหัส SNIPET B:

sleep 5
(
x=1
sleep 5
)

คำตอบ:


20

วงเล็บจะเริ่ม subshell เสมอ สิ่งที่เกิดขึ้นเป็นที่ตรวจพบทุบตีว่าsleep 5เป็นคำสั่งสุดท้ายดำเนินการโดย subshell นั้นจึงเรียกexecแทน+fork คำสั่งแทนที่ subshell ในกระบวนการเดียวกันexecsleep

กล่าวอีกนัยหนึ่งกรณีฐานคือ:

  1. ( … )สร้าง subshell เดิมโทรกระบวนการและfork waitในกระบวนการย่อยซึ่งเป็น subshell:
    1. sleepเป็นคำสั่งภายนอกที่ต้องการกระบวนการย่อยของกระบวนการย่อย subshell สายและfork waitในกระบวนการย่อย:
      1. subsubprocess execรันคำสั่งภายนอก→
      2. exitในที่สุดก็สิ้นสุดคำสั่ง→
    2. wait เสร็จสิ้นใน subshell
  2. wait เสร็จสิ้นในกระบวนการต้นฉบับ

การเพิ่มประสิทธิภาพคือ:

  1. ( … )สร้าง subshell เดิมโทรกระบวนการและfork waitใน subprocess ซึ่งเป็น subshell จนกว่าจะเรียกใช้exec:
    1. sleep เป็นคำสั่งภายนอกและเป็นสิ่งสุดท้ายที่กระบวนการนี้ต้องทำ
    2. execกระบวนการย่อยรันคำสั่งภายนอก→
    3. exitในที่สุดก็สิ้นสุดคำสั่ง→
  2. wait เสร็จสิ้นในกระบวนการต้นฉบับ

เมื่อคุณเพิ่มอย่างอื่นหลังจากการเรียกใช้sleepคุณจำเป็นต้องเก็บ subshell ไว้รอบ ๆ เพื่อให้การเพิ่มประสิทธิภาพนี้ไม่เกิดขึ้น

เมื่อคุณเพิ่มอย่างอื่นก่อนที่จะเรียกsleepการเพิ่มประสิทธิภาพสามารถทำได้ (และ ksh ทำมัน) แต่ทุบตีไม่ได้ทำมัน (มันค่อนข้างอนุรักษ์นิยมกับการเพิ่มประสิทธิภาพนี้)


subshell จะถูกสร้างขึ้นโดยการเรียกforkและกระบวนการเด็กจะถูกสร้างขึ้น (เพื่อรันคำสั่งภายนอก) fork + execโดยการเรียก แต่ย่อหน้าแรกของคุณแสดงให้เห็นว่าfork + execถูกเรียกสำหรับ subshell ด้วย ฉันกำลังทำอะไรผิดที่นี่?
haccks

1
@haccks fork+ execไม่ได้ถูกเรียกใช้สำหรับ subshell แต่จะถูกเรียกสำหรับคำสั่งภายนอก หากไม่มีการปรับให้เหมาะสมจะมีการforkเรียก subshell และอีกอันสำหรับคำสั่งภายนอก ฉันได้เพิ่มคำอธิบายการไหลอย่างละเอียดในคำตอบของฉัน
Gilles 'ดังนั้นหยุดความชั่วร้าย'

ขอบคุณสำหรับการอัปเดต ตอนนี้มันอธิบายได้ดีขึ้น ฉันสามารถอนุมานจากมันว่าในกรณีของ(...)(ในกรณีฐาน) มีอาจจะหรืออาจจะไม่เรียกร้องให้execขึ้นอยู่กับว่า subshell มีคำสั่งภายนอกใด ๆ ที่จะดำเนินการในขณะที่ในกรณีของการดำเนินการคำสั่งภายนอกใด ๆ fork + execต้องมี
haccks

อีกหนึ่งคำถาม: การเพิ่มประสิทธิภาพนี้ใช้ได้กับ subshell เท่านั้นหรือสามารถทำได้สำหรับคำสั่งเช่นdateใน shell หรือไม่?
haccks

@haccks ฉันไม่เข้าใจคำถาม การปรับให้เหมาะสมนี้เกี่ยวข้องกับการเรียกใช้คำสั่งภายนอกเช่นเดียวกับสิ่งสุดท้ายที่กระบวนการเชลล์ทำ ไม่ จำกัด เฉพาะ subshells: เปรียบเทียบstrace -f -e clone,execve,write bash -c 'date'และstrace -f -e clone,execve,write bash -c 'date; true'
Gilles 'หยุดความชั่วร้าย'

4

จากคู่มือการเขียนโปรแกรม Bash ขั้นสูง :

"โดยทั่วไปแล้วคำสั่งภายนอกในสคริปต์จะระงับการประมวลผลย่อยในขณะที่ Bash builtin ไม่ได้ด้วยเหตุนี้ builtins จะทำงานได้เร็วขึ้นและใช้ทรัพยากรระบบน้อยกว่าคำสั่งภายนอกที่เทียบเท่า"

และไกลออกไปเล็กน้อย:

"รายการคำสั่งที่ฝังอยู่ในวงเล็บจะเรียกใช้เป็นเชลล์ย่อย"

ตัวอย่าง:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

ตัวอย่างการใช้รหัส OPs (โดยมี sleeps สั้นกว่าเนื่องจากฉันใจร้อน):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

ผลลัพธ์:

[root@talara test]# bash sub_bash
6606
6608
6608

2
ขอบคุณสำหรับคำตอบทิม ฉันไม่แน่ใจว่ามันตอบคำถามของฉันอย่างเต็มที่ ตั้งแต่ "รายการคำสั่งที่ฝังอยู่ระหว่างวงเล็บจะทำงานเป็น subshell" ฉันคาดหวังว่ารายการที่สองsleepจะทำงานใน subshell (อาจเป็นในกระบวนการของ subshell เนื่องจากเป็นแบบ built-in แทนที่จะเป็น subprocess ของ subshell) อย่างไรก็ตามในกรณีใด ๆ ฉันคาดว่าจะมี subshell อยู่เช่นกระบวนการย่อย Bash ภายใต้กระบวนการ Bash หลัก สำหรับตัวอย่าง B ข้างต้นดูเหมือนจะไม่เป็นเช่นนั้น
อาย

การแก้ไข: เนื่องจากsleepดูเหมือนว่าจะไม่ได้ติดตั้งในตัวฉันจึงคาดว่าการsleepเรียกครั้งที่สองในตัวอย่างข้อมูลทั้งสองจะทำงานในกระบวนการย่อยของกระบวนการ subshell
อาย

@bashful ฉันใช้เสรีภาพในการแฮ็กโค้ดของคุณด้วย$BASHPIDตัวแปรของฉัน น่าเศร้าที่คุณทำมันไม่ได้ให้เรื่องราวทั้งหมดที่ฉันเชื่อ ดูผลลัพธ์ของฉันเพิ่มในคำตอบ
ทิม

4

หมายเหตุเพิ่มเติมสำหรับคำตอบ @Gilles

ตามที่กล่าวโดย Gilles: The parentheses always start a subshell.

อย่างไรก็ตามตัวเลขที่ sub-shell นั้นอาจทำซ้ำ:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

อย่างที่คุณเห็น $$ ยังคงทำซ้ำและเป็นไปตามที่คาดไว้เพราะ (ดำเนินการคำสั่งนี้เพื่อค้นหาman bashบรรทัดที่ถูกต้อง):

$ LESS=+/'^ *BASHPID' man bash

BASHPID
ขยายเป็น ID กระบวนการของกระบวนการทุบตีปัจจุบัน สิ่งนี้แตกต่างจาก $$ ในบางกรณีเช่น subshells ที่ไม่ต้องการ bash เพื่อเริ่มต้นใหม่

นั่นคือ: หากเชลล์ไม่ได้เริ่มต้นใหม่ $$ จะเหมือนกัน

หรือด้วยสิ่งนี้:

$ LESS=+/'^ *Special Parameters' man bash

พารามิเตอร์พิเศษ
$ ขยายไปยัง ID กระบวนการของเชลล์ ในเชลล์ย่อย () จะขยายเป็น ID กระบวนการของเชลล์ปัจจุบันไม่ใช่เชลล์ย่อย

$$คือรหัสของเปลือกปัจจุบัน (ไม่ใช่ subshell) ที่


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