Đây có phải là một lỗi trong bash? `return` không thoát khỏi chức năng nếu được gọi từ một đường ống


16

Gần đây tôi có một số vấn đề kỳ lạ với bash. Trong khi cố gắng đơn giản hóa tập lệnh của mình, tôi đã nghĩ ra đoạn mã nhỏ này:

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

returnCó nên thoát khỏi chức năng mà không in $?, không nên? Chà, sau đó tôi đã kiểm tra xem tôi có thể trở về từ một mình không:

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

Điều tương tự xảy ra mà không có whilevòng lặp:

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

Có cái gì tôi đang thiếu ở đây? Một tìm kiếm Google không mang lại điều gì về điều này! Phiên bản bash của tôi là 4.2.37 (1) - phát hành trên Debian Wheezy.


Bất cứ điều gì sai với các cài đặt tôi đã đề xuất trong câu trả lời của mình cho phép tập lệnh của bạn hoạt động theo cách trực quan mà bạn mong đợi nó sẽ làm?
jlliagre

@jlliagre Đây là một kịch bản khá phức tạp trên hàng ngàn dòng. Với mối quan tâm về việc phá vỡ một cái gì đó khác, tôi thích tránh việc chạy một đường ống trong hàm, vì vậy tôi đã thay thế nó bằng một sự thay thế quá trình. Cảm ơn!
Teresa e Junior

Tại sao không loại bỏ hai ví dụ đầu tiên, nếu whilekhông cần thiết cho việc sao chép? Nó phân tâm từ điểm.
Cuộc đua nhẹ nhàng với Monica

@LightnessRacesinOrbit Một whilevòng lặp là cách sử dụng rất phổ biến cho một đường ống với return. Ví dụ thứ hai thì thẳng thắn hơn, nhưng đó là điều mà tôi không tin bất cứ ai sẽ sử dụng ...
Teresa e Junior

1
Thật không may, câu trả lời đúng của tôi đã bị xóa ... Bạn đang ở trong vùng màu xám khi bạn làm điều gì đó không xác định. Hành vi phụ thuộc vào cách shell diễn giải các đường ống và điều này thậm chí khác nhau giữa Bourne Shell và Korn Shell mặc dù ksh được lấy từ các nguồn sh. Trong Bourne Shell, vòng lặp while nằm trong một khung con, do đó bạn thấy tiếng vang như với bash, Trong ksh, vòng lặp while là quá trình tiền cảnh và do đó ksh không gọi echo bằng ví dụ của bạn.
schily

Câu trả lời:


10

Liên quan: /programming//a/7804208/4937930

Đây không phải là lỗi mà bạn không thể thoát khỏi tập lệnh hoặc trả về từ một hàm bằng exithoặc returntrong các lớp con. Chúng được thực hiện trong một quy trình khác và không ảnh hưởng đến quy trình chính.

Bên cạnh đó, tôi cho rằng bạn đang nhìn thấy các hành vi không có giấy tờ của bash trên (có thể) không xác định cụ thể. Trong một hàm, không có lỗi nào được xác nhận returnở cấp cao nhất của các lệnh con và nó chỉ hoạt động như thế nào exit.

IMHO đó là một lỗi bash cho hành vi không nhất quán returntùy thuộc vào câu lệnh chính có trong hàm hay không.

#!/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 $?

Đầu ra:

$ 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

Việc thiếu tính dài hạn có thể không có giấy tờ. Nhưng thực tế là returnnó không hoạt động từ một chuỗi lệnh cấp cao nhất trong một lớp con và đặc biệt là không thoát khỏi lớp con, là những gì các tài liệu hiện có đã khiến tôi mong đợi. OP có thể sử dụng exit 1 || return 1nơi họ đang cố gắng sử dụng returnvà sau đó sẽ có được hành vi mong đợi. EDIT: Câu trả lời của @ herbert chỉ ra rằng toplevel returntrong subshell đang hoạt động như exit(nhưng chỉ từ subshell).
dubiousjim

1
@dubiousjim Cập nhật tập lệnh của tôi. Tôi có nghĩa là returntrong một chuỗi con đơn giản nên được xác nhận là lỗi thời gian chạy trong mọi trường hợp , nhưng thực tế nó không phải là khi nó xảy ra trong một fucntion. Vấn đề này cũng đã được thảo luận trong gnu.bash.orms , nhưng không có kết luận.
yaegashi

1
Câu trả lời của bạn không chính xác vì nó không được chỉ định cho dù vòng lặp while nằm trong một khung con hay là quá trình tiền cảnh. Bất kể việc thực hiện shell thực tế, returntuyên bố là trong một chức năng và do đó hợp pháp. Tuy nhiên, hành vi kết quả là không xác định.
schily

Bạn không nên viết nó là một hành vi không có giấy tờ trong khi các thành phần đường ống thực tế nằm trong một khung con được ghi lại trong trang hướng dẫn bash. Bạn không nên viết hành vi có thể dựa trên thông số kỹ thuật không xác định trong khi POSIX chỉ định các hành vi được phép. Bạn không nên nghi ngờ lỗi bash trong khi bash tuân theo tiêu chuẩn POSIX bằng cách cho phép trả về trong một chức năng nhưng không phải bên ngoài.
jlliagre

17

Nó không phải là một lỗi trong bashnhưng hành vi được ghi lại của nó :

Mỗi lệnh trong một đường ống được thực hiện trong lớp con riêng của nó

Lệnh returnnày có giá trị trong một định nghĩa hàm nhưng cũng nằm trong một lớp con, nó không ảnh hưởng đến lớp vỏ của nó nên lệnh tiếp theo echo, được thực thi bất kể. Tuy nhiên, đây không phải là cấu trúc vỏ không di động vì tiêu chuẩn POSIX cho phép các lệnh tạo đường ống được thực thi trong một lớp con (mặc định) hoặc trên cùng (phần mở rộng được phép).

Ngoài ra, mỗi lệnh của một đường ống đa lệnh nằm trong môi trường mạng con; như một phần mở rộng, tuy nhiên, bất kỳ hoặc tất cả các lệnh trong một đường ống có thể được thực thi trong môi trường hiện tại. Tất cả các lệnh khác sẽ được thực hiện trong môi trường shell hiện tại.

Hy vọng, bạn có thể nói bashđể hành xử theo cách bạn mong đợi với một vài lựa chọn:

$ 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
returnsẽ không thoát khỏi chức năng, nên nó sẽ có ý nghĩa hơn nếu vỏ chỉ được in bash: return: can only `return' from a function or sourced script, thay vì tạo cho người dùng cảm giác sai rằng chức năng có thể đã trở lại?
Teresa e Junior

2
Tôi không thấy bất cứ nơi nào trong tài liệu nói rằng sự trở lại bên trong subshell là hợp lệ. Tôi cá rằng tính năng này đã được sao chép từ ksh, hàm trả về bên ngoài hoặc tập lệnh có nguồn gốc hoạt động giống như thoát . Tôi không chắc chắn về vỏ Bourne ban đầu.
cuonglm

1
@jlliagre: Có thể Teresa bối rối về thuật ngữ của những gì cô ấy yêu cầu, nhưng tôi không hiểu tại sao nó lại "khó khăn" khi bash đưa ra chẩn đoán nếu bạn thực hiện returntừ một mạng con. Rốt cuộc, nó biết rằng nó nằm trong một nhánh con, bằng chứng là $BASH_SUBSHELLbiến. Vấn đề lớn nhất là điều này có thể dẫn đến dương tính giả; một người dùng hiểu cách thức hoạt động của các lớp con có thể có các tập lệnh được viết returnthay cho exitviệc chấm dứt một lớp con. (Và, tất nhiên, có những trường hợp hợp lệ trong đó người ta có thể muốn đặt biến hoặc thực hiện cdtrong một nhánh con.)
Scott

1
@Scott Tôi nghĩ tôi hiểu rõ tình hình. Một đường ống tạo ra một lớp con và returnđang quay trở lại từ lớp con thay vì không thành công, vì nó nằm trong một hàm thực tế. Vấn đề là ở chỗ help returncụ thể: Causes a function or sourced script to exit with the return value specified by N.Từ việc đọc tài liệu, bất kỳ người dùng nào cũng mong đợi nó ít nhất sẽ thất bại hoặc in cảnh báo, nhưng không bao giờ hành xử như thế exit.
Teresa e Junior

1
Dường như với tôi rằng bất cứ ai hy vọng một return trong một subshell trong một chức năng để trở về từ chức năng (trong quá trình shell chính) không hiểu subshells rất tốt. Ngược lại, tôi sẽ mong đợi một người đọc hiểu các subshell sẽ mong đợi return trong một subshell trong một hàm để chấm dứt subshell, giống như exitvậy.
Scott

6

Mỗi tài liệu POSIX, sử dụng returnbên ngoài chức năng hoặc tập lệnh có nguồn gốc là không xác định . Vì vậy, nó phụ thuộc vào vỏ của bạn để xử lý.

Hệ vỏ sẽ báo cáo lỗi, trong khi ở ksh, returnngoài chức năng hoặc có nguồn gốc kịch bản hành xử giống như exit. Hầu hết các vỏ POSIX khác và osh schily cũng hoạt động như vậy:

$ 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

kshzshkhông xuất ra vì phần cuối của ống trong các shell này được thực thi trong shell hiện tại thay vì subshell. Câu lệnh return ảnh hưởng đến môi trường shell hiện tại được gọi là hàm, khiến hàm trả về ngay lập tức mà không in bất cứ thứ gì.

Trong phiên tương tác, bashchỉ báo cáo lỗi nhưng không chấm dứt trình bao, schily's oshbáo cáo lỗi và chấm dứt trình bao:

$ 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

( zshTrong phiên tương tác và đầu ra là làm thiết bị đầu cuối không chấm dứt, bash, yashschily's oshbáo cáo các lỗi nhưng không chấm dứt vỏ)


1
Nó có thể được tranh luận returnđược sử dụng bên trong một chức năng ở đây.
jlliagre

1
@jlliagre: Không chắc chắn những gì bạn có nghĩa là, returnđã sử dụng bên trong subshell bên trong chức năng , ngoại trừ kshzsh.
cuonglm

2
Tôi có nghĩa là ở bên trong một lớp con nằm trong hàm không nhất thiết có nghĩa là nằm ngoài hàm đó, tức là không có gì trong các thành phần đường ống trạng thái tiêu chuẩn được coi là nằm ngoài hàm nơi chúng được đặt. Điều này sẽ xứng đáng được làm rõ bởi Nhóm mở.
jlliagre

3
Tôi nghĩ rằng không có. Đó là ngoài chức năng. Shell được gọi là hàm và lớp con được thực hiện trả về là khác nhau.
cuonglm

Tôi hiểu lý do của bạn giải thích chính xác vấn đề, quan điểm của tôi là theo ngữ pháp shell được mô tả trong tiêu chuẩn POSIX, đường ống là một phần của danh sách ghép là một phần của lệnh ghép là phần thân của hàm. Không nơi nào được nêu các thành phần đường ống sẽ được xem xét bên ngoài chức năng. Giống như nếu tôi ở trong một chiếc ô tô và chiếc xe đó đang đậu trong một nhà để xe, tôi có thể cho rằng tôi cũng đang ở trong nhà để xe đó ;-)
jlliagre

4

Tôi nghĩ rằng bạn đã có hành vi dự kiến, trong bash, mỗi lệnh trong một đường ống được thực thi trong một lớp con. Bạn có thể tự xác nhận bằng cách cố gắng sửa đổi một biến toàn cục của hàm:

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

Nhân tiện, sự trở lại đang hoạt động nhưng nó trở lại từ lớp con. Một lần nữa bạn có thể kiểm tra xem:

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

Sẽ xuất ra như sau:

1
This should not be printed.

Vì vậy, câu lệnh return trả lại chính xác thoát khỏi subshell

.


2
Do đó, để thoát khỏi chức năng, hãy sử dụng foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?và bạn sẽ nhận được kết quả 2. Nhưng để rõ ràng, tôi sẽ làm cho return 1được exit 1.
dubiousjim

Nhân tiện, có một số bằng chứng cho thực tế là tất cả các thành viên của một đường ống (không phải tất cả ngoại trừ một) được thực hiện trong các lớp con?
Incni Mrsi


1

Câu trả lời chung hơn là bash và một số shell khác thường đưa tất cả các yếu tố của một đường ống vào các quy trình riêng biệt. Điều này là hợp lý khi dòng lệnh là

chương trình 1 | chương trình 2 | chương trình 3

vì các chương trình thường được chạy trong các quy trình riêng biệt (trừ khi bạn nói ). Nhưng nó có thể gây ngạc nhiên choexec program

lệnh 1 | lệnh 2 | lệnh 3

trong đó một số hoặc tất cả các lệnh là các lệnh dựng sẵn. Các ví dụ tầm thường bao gồm:

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

Một ví dụ thực tế hơn một chút là

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

nơi toàn bộ while... do... donevòng lặp được đưa vào một tiến trình con, và do đó thay đổi nó để tkhông nhìn thấy được bằng vỏ chính sau khi vòng lặp kết thúc. Và đó chính xác là những gì bạn đang làm - chuyển thành một whilevòng lặp, khiến cho vòng lặp chạy như một lớp con, và sau đó cố gắng quay trở lại từ lớp con.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.