ออกจากเชลล์สคริปต์จากเชลล์ย่อย


30

ลองพิจารณาตัวอย่างนี้:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

โดยปกติเมื่อfuncถูกเรียกมันจะทำให้สคริปต์หยุดทำงานซึ่งเป็นพฤติกรรมที่ตั้งใจไว้ อย่างไรก็ตามถ้ามันถูกดำเนินการใน sub-shell เช่น in

result=`func`

มันจะไม่ออกจากสคริปต์ ซึ่งหมายความว่ารหัสการโทรต้องตรวจสอบสถานะการออกของฟังก์ชั่นทุกครั้ง มีวิธีหลีกเลี่ยงสิ่งนี้หรือไม่? สิ่งนี้set -eมีไว้เพื่ออะไร?


1
ฉันต้องการฟังก์ชั่น "หยุด" ที่พิมพ์ข้อความไปยัง stderr และหยุดสคริปต์ แต่มันไม่หยุดเมื่อฟังก์ชั่นการโทรหยุดถูกดำเนินการในเปลือกย่อยเช่นในตัวอย่าง
Ernest AC

2
แน่นอนเพราะมันออกจาก subshell ไม่ใช่อันปัจจุบัน funcเพียงแค่เรียกใช้ฟังก์ชันโดยตรง:

1
ฉันไม่สามารถเรียกมันได้โดยตรงเพราะมันจะส่งคืนสตริงที่ต้องถูกเก็บไว้ในตัวแปร
Ernest AC

1
@ErnestAC โปรดระบุรายละเอียดทั้งหมดในคำถามเดิม ฟังก์ชั่นด้านบนจะไม่ส่งคืนสตริง

1
@htor ฉันเปลี่ยนตัวอย่าง
Ernest AC

คำตอบ:


10

คุณสามารถฆ่าเชลล์เดิม ( kill $$) ก่อนการโทรexitและนั่นอาจจะใช้ได้ แต่:

  • ดูเหมือนจะค่อนข้างน่าเกลียดสำหรับฉัน
  • มันจะแตกถ้าคุณมี subshell ที่สองในนั้นนั่นคือใช้ subshell ภายใน subshell

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


5
ขอบคุณ ฉันควรเปลี่ยนไปใช้ Python แทน
เออร์เนสต์ AC

2
อย่างที่ฉันเขียนมันเป็นปี 2019 การบอกให้ใครสักคน "เปลี่ยนไปใช้ Perl" นั้นไร้สาระ ขออภัยเป็นที่ถกเถียงกัน แต่คุณจะบอกใครบางคนที่มีความผิดหวังกับ 'C' เพื่อเปลี่ยนเป็น Cobol ซึ่งเทียบเท่า IMO? ในขณะที่เออร์เนสต์ชี้ให้เห็น Python เป็นตัวเลือกที่ดีกว่ามาก การตั้งค่าของฉันจะเป็นทับทิม ไม่ว่าจะด้วยวิธีใดก็ตามยกเว้น Perl
เกรแฮมนิโคลส์

38

คุณสามารถตัดสินใจได้ว่าสถานะการออก 77 เช่นหมายถึงออกจากทุกระดับของ subshell และทำ

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -Eเมื่อใช้ร่วมกับกับERRดักก็เหมือนกับรุ่นปรับปรุงset -eที่ช่วยให้คุณกำหนดการจัดการข้อผิดพลาดของคุณเองได้

ใน zsh, กับดัก ERR จะถูกสืบทอดโดยอัตโนมัติดังนั้นคุณไม่จำเป็นต้องใช้set -Eคุณยังสามารถกำหนดกับดักเป็นTRAPERR()ฟังก์ชั่นและปรับเปลี่ยนได้$functions[TRAPERR]เช่นfunctions[TRAPERR]="echo was here; $functions[TRAPERR]"


1
ทางออกที่น่าสนใจ! kill $$ชัดเจนมากขึ้นสง่างามกว่า

3
มีสิ่งหนึ่งที่ต้องระวังกับดักนี้จะไม่จัดการคำสั่งที่สอดแทรกเช่นecho "$(exit 77)"; สคริปต์จะดำเนินต่อไปราวกับว่าเราได้เขียนecho ""
Warbo

interresting! มีโชคในการทุบตี (ค่อนข้างเก่า) ที่ไม่มี -E หรือไม่? บางทีเราต้องหันไปใช้กับดักกำหนดสัญญาณผู้ใช้และใช้การฆ่าสัญญาณ ฉันจะทำการค้นหาอีกครั้งเช่นกัน ...
Olivier Dulac

จะรู้ได้อย่างไรว่าเมื่อไม่อยู่ในกับดักย่อยของเชลล์เพื่อส่งคืน 1 แทน 77
ceving

7

ในฐานะที่เป็นทางเลือกให้กับkill $$คุณยังอาจพยายามที่kill 0จะทำงานในกรณีของ subshells ซ้อนกัน (ทุกสายที่และด้านกระบวนการจะได้รับสัญญาณ) ... แต่ก็ยังคงโหดร้ายและน่าเกลียด


2
รหัสกระบวนการนี้จะไม่ฆ่า 0 หรือไม่
เออร์เนสต์ AC

5
นั่นจะฆ่ากลุ่มกระบวนการทั้งหมด คุณอาจตีสิ่งที่คุณไม่ต้องการ (ถ้าเช่นคุณเริ่มบางสิ่งในพื้นหลัง)
Derobert

2
@ErnestAC ดู manpage kill (2) pids ≤0มีความหมายพิเศษ
Derobert

0

ลองสิ่งนี้ ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

ผลลัพธ์ที่ฉันได้รับคือ ...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

หมายเหตุ

  • พารามิเตอร์เพื่อให้การifทดสอบเป็นtrueหรือfalse(ดู 2 การทดสอบ)
  • เมื่อการifทดสอบคือfalseเราไม่เคยไปถึง subshell

นี่คล้ายกับแนวคิดดั้งเดิมที่ผู้ใช้โพสต์เกี่ยวกับและกล่าวว่าไม่ได้ผล ฉันไม่คิดว่ามันจะใช้กับกรณี subshell ได้ การทดสอบของคุณโดยใช้การออกที่ผิดหลังจากกรณี "เชลล์" และไม่เคยไปถึงกรณีทดสอบ "subshell" ฉันเชื่อว่ามันจะล้มเหลวสำหรับกรณีดังกล่าวเนื่องจาก subshell จะออกจากการเรียก "exit 1" แต่ไม่เผยแพร่ข้อผิดพลาดไปยังเปลือกนอก
stickj

0

(คำตอบเฉพาะของ Bash) Bash ไม่มีแนวคิดของข้อยกเว้น อย่างไรก็ตามด้วย set -o errexit (หรือเทียบเท่า: set -e) ที่ระดับนอกสุดคำสั่งที่ล้มเหลวจะส่งผลให้มีการออกจาก subshell ด้วยสถานะทางออกที่ไม่ใช่ศูนย์ หากนี่เป็นชุดของ subshells ที่ซ้อนกันโดยไม่มีเงื่อนไขเกี่ยวกับการทำงานของ subshells เหล่านั้นมันจะ 'ม้วน' สคริปต์ทั้งหมดและออกอย่างมีประสิทธิภาพ

สิ่งนี้อาจเป็นเรื่องยากเมื่อพยายามรวมบิตของรหัสทุบตีต่างๆไว้ในสคริปต์ที่มีขนาดใหญ่ขึ้น หนึ่งชิ้นทุบตีอาจทำงานได้ดีด้วยตัวเอง แต่เมื่อดำเนินการภายใต้ errexit (หรือไม่มี errexit) ทำงานในลักษณะที่ไม่คาดคิด

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
บางอย่างผิดพลาด
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
บางอย่างผิดพลาด
แต่เรามาถึงที่นี่ต่อไป
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / bin / ทุบตี
หยุด () {
    echo "$ {1}"
    ทางออก 1
}

ถ้าเป็นเท็จ แล้วก็
    echo "foo"
อื่น
    (
        หยุด "มีบางอย่างผิดปกติ"
    )
    echo "แต่เรามาถึงที่นี่อีกแล้ว"
Fi
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #

-2

ตัวอย่างของฉันที่จะออกในหนึ่งซับ:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit

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