นี่เป็นข้อบกพร่องในการทุบตีหรือไม่? `return 'ไม่ออกจากฟังก์ชั่นถ้าเรียกจากไพพ์


16

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

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

returnควรออกจากฟังก์ชั่นโดยไม่พิมพ์$?ใช่มั้ย ถ้าอย่างนั้นฉันจะตรวจสอบว่าฉันสามารถกลับจากท่อเพียงอย่างเดียว:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

สิ่งเดียวกันนี้เกิดขึ้นโดยไม่มีการwhileวนซ้ำ:

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

มีบางอย่างที่ฉันหายไปที่นี่หรือไม่? การค้นหาของ Google ไม่ได้สนใจอะไรเลย! เวอร์ชั่นทุบตีของฉันคือ4.2.37 (1) - ปล่อยให้ Debian Wheezy


มีอะไรผิดปกติกับการตั้งค่าที่ฉันแนะนำในคำตอบของฉันที่อนุญาตให้สคริปต์ของคุณทำงานอย่างที่คุณคาดหวังไว้
jlliagre

@jlliagre มันเป็นสคริปต์ที่ค่อนข้างซับซ้อนในหลายพันบรรทัด ด้วยความกังวลว่าจะทำอย่างอื่นฉันอยากหลีกเลี่ยงการใช้ไพพ์ภายในฟังก์ชั่นดังนั้นฉันจึงแทนที่ด้วยการทดแทนโปรเซส ขอบคุณ!
Teresa e Junior

ทำไมไม่ลองลบสองตัวอย่างแรกถ้าwhileไม่ต้องการทำซ้ำ มันเบี่ยงเบนจากจุด
การแข่งขัน Lightness กับโมนิก้า

@LightnessRacesinOrbit ห่วงคือการใช้งานทั่วไปมากสำหรับท่อด้วยwhile returnตัวอย่างที่สองตรงไปตรงกว่าจุด แต่มันเป็นสิ่งที่ฉันไม่เชื่อว่าใครจะเคยใช้ ...
Teresa e Junior

1
น่าเสียดายที่คำตอบที่ถูกต้องของฉันถูกลบ ... คุณอยู่ในโซนสีเทาเนื่องจากคุณทำสิ่งที่ไม่ระบุ พฤติกรรมขึ้นอยู่กับวิธีที่เชลล์ตีความไปป์และสิ่งนี้แตกต่างกันระหว่าง Bourne Shell และ Korn Shell แม้ว่า ksh จะได้มาจากแหล่งที่มาของ sh ใน Bourne Shell ห่วงในขณะที่อยู่ใน subshell ดังนั้นคุณจะเห็นเสียงสะท้อนเช่นเดียวกับทุบตีใน ksh ห่วงในขณะที่เป็นกระบวนการเบื้องหน้าและ ksh ไม่ได้เรียกเสียงสะท้อนกับตัวอย่างของคุณ
schily

คำตอบ:


10

ที่เกี่ยวข้อง: /programming//a/7804208/4937930

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

นอกจากนั้นฉันคิดว่าคุณเห็นพฤติกรรมที่ไม่มีเอกสารของการทุบตีข้อมูลจำเพาะ (อาจ) ที่ไม่ได้กำหนด ในฟังก์ชั่นไม่มีข้อผิดพลาดถูกยืนยันสำหรับreturnที่ระดับสูงสุดของคำสั่ง subshell และมันก็ทำตัวเหมือนexitและมันก็จะทำงานเช่น

IMHO มันเป็นข้อผิดพลาดทุบตีสำหรับพฤติกรรมที่ไม่สอดคล้องกันreturnขึ้นอยู่กับว่าคำสั่งหลักอยู่ในฟังก์ชั่นหรือไม่

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

เอาท์พุท:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23

การไม่มีข้อผิดพลาด verbosity อาจไม่มีเอกสาร แต่ความจริงที่returnไม่ทำงานจากลำดับคำสั่งระดับบนสุดใน subshell และโดยเฉพาะอย่างยิ่งไม่ออกจาก subshell เป็นสิ่งที่เอกสารที่มีอยู่ทำให้ฉันคาดหวัง OP สามารถใช้exit 1 || return 1ตำแหน่งที่พวกเขาพยายามใช้returnและควรได้รับพฤติกรรมที่คาดหวัง แก้ไข: @ คำตอบของเฮอร์เบิร์ตบ่งชี้ว่าระดับบนสุดของreturnsubshell ทำงานเป็นexit(แต่มาจาก subshell เท่านั้น)
dubiousjim

1
@dubiousjim อัปเดตสคริปต์ของฉัน ฉันหมายถึงreturnในลำดับ subshell ง่ายควรได้รับการยืนยันว่าเป็นข้อผิดพลาด runtime ในกรณีใด ๆแต่จริงๆแล้วมันไม่ได้เมื่อมันเกิดขึ้นใน fucntion ปัญหานี้ได้รับการพูดถึงในgnu.bash.bugแล้ว แต่ก็ยังไม่มีข้อสรุป
yaegashi

1
คำตอบของคุณไม่ถูกต้องเนื่องจากไม่ได้ระบุว่าห่วง while อยู่ใน subshell หรือเป็นกระบวนการพื้นหน้า โดยไม่คำนึงถึงการใช้งานเชลล์จริงreturnคำสั่งอยู่ในฟังก์ชันและถูกต้องตามกฎหมาย พฤติกรรมที่เกิดขึ้นไม่ได้ระบุ แต่
schily

คุณไม่ควรเขียนว่ามันเป็นพฤติกรรมที่ไม่มีเอกสารในขณะที่ส่วนประกอบของข้อเท็จจริงไปป์อยู่ใน subshell มีเอกสารในหน้าคู่มือทุบตี คุณไม่ควรเขียนลักษณะการทำงานนั้นอาจเป็นไปตามข้อกำหนดที่ไม่ได้กำหนดไว้ขณะที่ POSIX ระบุพฤติกรรมที่อนุญาต คุณไม่ควรสงสัยว่า bash bug ในขณะที่ bash นั้นเป็นไปตามมาตรฐาน POSIX โดยอนุญาตให้ส่งคืนในฟังก์ชัน แต่ไม่ได้อยู่นอก
jlliagre

17

มันไม่ได้เป็นข้อบกพร่องในbashแต่พฤติกรรมของเอกสาร :

แต่ละคำสั่งในไปป์ไลน์จะถูกดำเนินการใน subshell ของตัวเอง

returnคำแนะนำที่ถูกต้องจะถูกนิยามฟังก์ชันภายใน แต่อยู่ใน subshell เช่นกันก็ไม่ได้ส่งผลกระทบต่อผู้ปกครองของเปลือกเพื่อให้การเรียนการสอนต่อไปechoจะดำเนินการโดยไม่คำนึงถึง อย่างไรก็ตามมันเป็นโครงสร้างแบบพกพาที่ไม่ใช่แบบพกพาเนื่องจากมาตรฐาน POSIXอนุญาตให้คำสั่งในการเขียนไพพ์ไลน์สามารถดำเนินการได้ใน subshell (ค่าเริ่มต้น) หรือส่วนบนสุด (ส่วนขยายที่อนุญาต)

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

หวังว่าคุณจะบอกได้ bashให้ทำตัวเหมือนที่คุณคาดหวังด้วยตัวเลือกสองทาง:

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here

1
เนื่องจากreturnจะไม่ออกจากฟังก์ชันมันจะไม่เหมาะสมกว่าหรือถ้าเชลล์เพิ่งพิมพ์bash: return: can only `return' from a function or sourced scriptแทนที่จะให้ผู้ใช้เข้าใจผิดว่าฟังก์ชันอาจส่งคืนหรือไม่
Teresa e Junior

2
ฉันไม่เห็นที่ใดในเอกสารบอกว่าการส่งคืนภายใน subshell นั้นถูกต้อง ฉันเดิมพันที่คุณลักษณะนี้คัดลอกมาจาก ksh ฟังก์ชั่นการกลับคำสั่งภายนอกหรือมาประพฤติสคริปต์เช่นทางออก ฉันไม่แน่ใจเกี่ยวกับเชลล์เป้าหมายเดิม
cuonglm

1
@jlliagre: บางที Teresa อาจสับสนเกี่ยวกับคำศัพท์ของสิ่งที่เธอขอ แต่ฉันไม่เห็นว่าทำไมมันจึงเป็น "หากิน" เพื่อทุบตีเพื่อออกการวินิจฉัยถ้าคุณเรียกใช้returnจาก subshell ท้าย$BASH_SUBSHELLที่สุดก็รู้ว่ามันอยู่ใน subshell ตามหลักฐานโดยตัวแปร ปัญหาที่ใหญ่ที่สุดคือสิ่งนี้อาจนำไปสู่การบวกเท็จ; ผู้ใช้ที่เข้าใจวิธีการทำงานของ subshells อาจมีสคริปต์ที่เขียนขึ้นซึ่งใช้returnแทนexitการยกเลิก subshell (และแน่นอนมีกรณีที่ถูกต้องที่หนึ่งอาจต้องการตัวแปรชุดหรือทำcdใน subshell ได้.)
สกอตต์

1
@Scott ฉันคิดว่าฉันเข้าใจสถานการณ์เป็นอย่างดี ไปป์สร้าง subshell และreturnกลับจาก subshell แทนที่จะล้มเหลวเนื่องจากมันอยู่ในฟังก์ชั่นจริง ปัญหาคือว่าhelp returnโดยเฉพาะ: Causes a function or sourced script to exit with the return value specified by N.จากการอ่านเอกสารผู้ใช้ใด ๆ คาดว่าจะล้มเหลวอย่างน้อยหรือพิมพ์คำเตือน แต่ไม่เคยทำเช่นexitนั้น
Teresa e Junior

1
สำหรับฉันแล้วดูเหมือนว่าทุกคนที่คาดหวังว่า a return ใน subshellในฟังก์ชั่นจะกลับมาจากฟังก์ชั่น (ในกระบวนการเปลือกหลัก) ไม่เข้าใจ subshell ดีมาก ในทางกลับกันฉันคาดว่าผู้อ่านที่เข้าใจ subshell จะคาดหวังreturn ใน subshellในฟังก์ชั่นเพื่อยุติ subshell เช่นเดียวกับที่exitจะ
สกอตต์

6

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

SystemV เปลือกจะรายงานข้อผิดพลาดในขณะที่ในksh, ด้านนอกของฟังก์ชั่นหรือมาประพฤติสคริปต์เช่นreturn exitPOSIX เชลล์ส่วนใหญ่และschosh's oshก็มีพฤติกรรมเช่นนั้น:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

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

ในเซสชันแบบโต้ตอบbashรายงานข้อผิดพลาดเท่านั้น แต่ไม่ได้ยกเลิกเชลล์schily's oshรายงานข้อผิดพลาดและยกเลิกเชลล์:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

( zshในช่วงการโต้ตอบและการส่งออกเป็นขั้วที่ต้องทำไม่ยกเลิกbash, yashและschily's oshรายงานข้อผิดพลาด แต่ไม่ได้บอกเลิกเปลือก)


1
มันสามารถโต้เถียงreturnใช้ภายในฟังก์ชันที่นี่
jlliagre

1
@jlliagre: ไม่แน่ใจว่าสิ่งที่คุณหมายถึงreturnคือการใช้ภายในsubshellภายในฟังก์ชั่นยกเว้นและksh zsh
cuonglm

2
ฉันหมายถึงการอยู่ใน subshell ซึ่งอยู่ภายในฟังก์ชั่นไม่จำเป็นต้องหมายความว่าอยู่นอกฟังก์ชั่นนั่นคือไม่มีสิ่งใดในส่วนประกอบไปป์ไลน์มาตรฐานฯ ที่จะต้องพิจารณานอกฟังก์ชั่นที่ตั้งอยู่ สิ่งนี้สมควรที่จะได้รับการชี้แจงจาก Open Group
jlliagre

3
ฉันคิดว่าไม่ นั่นคือนอกฟังก์ชั่น เชลล์ที่เรียกว่าฟังก์ชันและเชลล์ย่อยที่ดำเนินการส่งคืนจะแตกต่างกัน
cuonglm

ฉันเข้าใจเหตุผลของคุณซึ่งอธิบายปัญหาได้อย่างถูกต้องประเด็นของฉันเป็นไปตามไวยากรณ์ของเชลล์ที่อธิบายไว้ในมาตรฐาน POSIX ท่อเป็นส่วนหนึ่งของรายการผสมซึ่งเป็นส่วนหนึ่งของคำสั่งผสมซึ่งเป็นส่วนหนึ่งของฟังก์ชั่น จะไม่มีการระบุส่วนประกอบของไปป์ไลน์ที่จะนำมาพิจารณานอกฟังก์ชั่น เช่นเดียวกับถ้าฉันอยู่ในรถและมีรถคันนั้นจอดอยู่ในโรงรถฉันสามารถสันนิษฐานได้ว่าฉันอยู่ในโรงรถนั้นด้วย ;-)
jlliagre

4

ฉันคิดว่าคุณมีพฤติกรรมที่คาดหวังในทุบตีแต่ละคำสั่งในไปป์ไลน์จะดำเนินการใน subshell คุณสามารถโน้มน้าวตัวเองด้วยการลองปรับเปลี่ยนตัวแปรส่วนกลางของฟังก์ชั่นของคุณ:

foo(){ x=42; : | x=3; echo "x==$x";}

โดยวิธีการกลับมาทำงาน แต่มันกลับมาจาก subshell คุณสามารถตรวจสอบอีกครั้งว่า:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

จะส่งออกต่อไปนี้:

1
This should not be printed.

ดังนั้นคำสั่ง return จะออกจาก subshell อย่างถูกต้อง

.


2
ดังนั้นเพื่อออกจากฟังก์ชั่นการใช้งานและคุณจะได้รับผลมาจากfoo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $? 2แต่เพื่อความชัดเจนผมจะทำให้BEreturn 1 exit 1
dubiousjim

โดยวิธีการที่มีการยืนยันบางส่วนสำหรับความจริงที่ว่าสมาชิกทั้งหมดของไปป์ไลน์ (ไม่ใช่ทั้งหมด แต่หนึ่ง) จะถูกดำเนินการใน subshells?
Incnis Mrsi

ดู: @IncnisMrsi คำตอบ jlliagre ของ
สกอตต์

1

คำตอบทั่วไปคือ bash และ shell อื่น ๆ โดยปกติจะใส่องค์ประกอบทั้งหมดของไปป์ไลน์ในกระบวนการแยกต่างหาก นี่สมเหตุสมผลเมื่อบรรทัดคำสั่งคือ

โปรแกรม1 | โปรแกรม2 | โปรแกรม3

เนื่องจากปกติโปรแกรมจะทำงานในกระบวนการแยกต่างหากอยู่ดี (เว้นแต่คุณจะพูด) แต่มันอาจมาเป็นเซอร์ไพรสได้exec program

คำสั่ง1 | คำสั่ง2 | คำสั่ง3

โดยที่คำสั่งบางส่วนหรือทั้งหมดเป็นคำสั่งในตัว ตัวอย่างเล็กน้อย ได้แก่ :

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

ตัวอย่างที่เหมือนจริงมากขึ้นเล็กน้อยคือ

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

ที่ทั้งwhile... do... doneห่วงจะใส่ลงในกระบวนการย่อยและดังนั้นการเปลี่ยนแปลงในการtจะมองไม่เห็นเปลือกหลักหลังจากที่วงปลาย และนั่นคือสิ่งที่คุณกำลังทำอยู่ - ส่งไปยังwhileลูปทำให้ลูปทำงานเป็น subshell แล้วลองกลับจาก subshell

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