Hoàn trả lại trong các hàm bash?


11

Nói rằng tôi có một hàm bash như vậy:

gmx(){
  echo "foo";
}

Hàm này sẽ hoàn toàn trả về giá trị thoát của echolệnh hoặc sử dụng return có cần thiết không?

gmx(){
  echo "foo";
  return $?
}

Tôi giả sử rằng cách bash hoạt động, trạng thái thoát của lệnh cuối cùng của hàm bash là trạng thái được "trả về", nhưng không chắc chắn 100%.

Câu trả lời:


10

returnthực hiện trả về rõ ràng từ hàm shell hoặc "dot script" (tập lệnh có nguồn gốc). Nếu returnkhông được thực thi, một trả về ngầm định được thực hiện ở cuối hàm shell hoặc script script.

Nếu returnđược thực thi mà không có tham số, nó tương đương với việc trả về trạng thái thoát của lệnh được thực hiện gần đây nhất.

Đó là cách returnhoạt động trong tất cả các vỏ POSIX.

Ví dụ,

gmx () {
  echo 'foo'
  return "$?"
}

do đó tương đương với

gmx () {
  echo 'foo'
  return
}

giống như

gmx () {
  echo 'foo'
}

Nói chung, rất hiếm khi bạn cần sử dụng $?tất cả. Nó thực sự chỉ cần thiết nếu bạn cần lưu nó để sử dụng trong tương lai, ví dụ nếu bạn cần điều tra giá trị của nó nhiều lần (trong trường hợp đó bạn sẽ gán giá trị của nó cho một biến và thực hiện một loạt các thử nghiệm trên biến đó).


2
Một nhược điểm của việc sử dụng returnlà đối với các hàm được định nghĩa như thế f() (...; cmd; return), nó ngăn chặn việc tối ưu hóa mà một số shell thực hiện cmdtrong quá trình tương tự như lớp con. Với nhiều shell, điều đó cũng có nghĩa là trạng thái thoát của hàm không mang thông tin cmdđã bị giết khi có (hầu hết các shell không thể truy xuất thông tin đó bằng mọi cách).
Stéphane Chazelas

1
Lưu ý rằng với pdksh và một số dẫn xuất của nó (như OpenBSD shhoặc posh), bạn sẽ cần return -- "$?"nếu có khả năng lệnh cuối cùng là một hàm trả về số âm. mksh(cũng dựa trên pdksh) cấm các hàm trả về giá trị âm.
Stéphane Chazelas

cảm ơn vì điều này có ích, tôi đoán tôi không hiểu các return xchức năng khác với exit x... điều duy nhất tôi biết là return xsẽ không thoát khỏi quy trình hiện tại.
Alexander Mills

@AlexanderMills Vâng, đó là những gì tôi đã nói: returnđược sử dụng để trả về từ một hàm hoặc tập lệnh dấu chấm. exitlàm một cái gì đó hoàn toàn khác nhau (chấm dứt một quá trình).
Kusalananda

vâng điều đó hợp lý Tôi nghĩ rằng tôi đang bắt đầu hiểu rõ hơn về vấn đề này
Alexander Mills

7

Từ bash(1)trang người đàn ông:

Khi được thực thi, trạng thái thoát của hàm là trạng thái thoát của lệnh cuối cùng được thực thi trong phần thân.


đúng, và một hệ quả có thể là tuyên bố trả lại không có gì nhiều hơn trạng thái thoát?
Alexander Mills

Tôi đoán returnlà một lệnh dựng sẵn - mặc dù return 1khác với exit 1, v.v.
Alexander Mills

1
"return [n]: Làm cho hàm dừng thực thi và trả về giá trị được chỉ định bởi n cho người gọi của nó. Nếu n bị bỏ qua, trạng thái trả về là trạng thái của lệnh cuối cùng được thực thi trong thân hàm." (ibid) Vì vậy, returnbuộc trạng thái thoát của hàm thành một giá trị cụ thể nếu được chỉ định.
Ignacio Vazquez-Abrams

1
@AlexandwrMills Có, returnexitcả hai đều được tích hợp, ngoại trừ returnchỉ có thể được sử dụng trong chức năng. Bạn không thể chấm dứt một tập lệnh với return. Trạng thái thoát là giá trị mà lệnh trả về. returnlà lệnh trả về giá trị đó. Vì vậy, "tuyên bố trả lại không có gì nhiều hơn trạng thái thoát" chỉ là không hoàn toàn chính xác. Một là một giá trị, một là giá trị cộng với lệnh.
Sergiy Kolodyazhnyy

1
@AlexanderMills, returntrả về từ hàm, exitthoát khỏi toàn bộ shell. Nó giống hệt như trong, nói C với returnvs. exit(n)hoặc returnso với sys.exit()Python.
ilkkachu

2

Tôi sẽ chỉ thêm một vài lưu ý cho các câu trả lời đã được cung cấp:

  • Mặc dù returncó một ý nghĩa rất đặc biệt đối với shell, nhưng theo quan điểm cú pháp, nó là một lệnh dựng sẵn shell và một câu lệnh return được phân tích cú pháp như bất kỳ lệnh đơn giản nào khác. Vì vậy, điều đó có nghĩa là giống như trong đối số của bất kỳ lệnh nào khác, $?khi không được trích dẫn, sẽ bị phân tách + global

    Vì vậy, bạn cần trích dẫn điều đó $?để tránh nó:

    return "$?"
  • returnthường không chấp nhận bất kỳ tùy chọn ( ksh93's chấp nhận thông thường --help, --man, --author... mặc dù). Đối số duy nhất mà nó mong đợi (tùy chọn) là mã trả về. Phạm vi của các mã trả lại được chấp nhận thay đổi từ shell sang shell và cho dù bất kỳ giá trị nào bên ngoài 0..255 được phản ánh đúng $?cũng thay đổi từ shell sang shell. Xem Mã thoát mặc định khi quá trình kết thúc? để biết chi tiết về điều đó.

    Hầu hết vỏ chấp nhận số âm (sau khi tất cả, đối số truyền cho _exit()/ exitgroup()cuộc gọi hệ thống là một int, vì vậy với giá trị bao gồm ít nhất là -2 31 đến 2 31 -1, vì vậy nó chỉ có ý nghĩa rằng vỏ chấp nhận cùng một phạm vi cho các chức năng của nó) .

    Hầu hết các vỏ sử dụng waitpid()và đồng. API để lấy rằng trạng thái thoát tuy nhiên trong trường hợp này, nó cắt ngắn để một số từ 0 đến 255 khi lưu trữ trong $?. Mặc dù việc gọi một hàm không liên quan đến việc sinh ra một tiến trình và được sử dụng waitpid()để truy xuất trạng thái thoát của nó vì tất cả được thực hiện trong cùng một tiến trình, nhiều shell cũng bắt chước waitpid()hành vi đó khi gọi các hàm. Điều đó có nghĩa là ngay cả khi bạn gọi returnvới giá trị âm, $?sẽ chứa số dương.

    Bây giờ, trong số những vỏ mà returnchấp nhận số âm (ksh88, ksh93, bash, zsh, pdksh và các dẫn xuất khác ngoài mksh, Yash), có một vài (pdksh và Yash) mà không cần nó viết như return -- -123nếu không có -123được lấy như ba -1, -2, -3tùy chọn không hợp lệ.

    Vì pdksh và các dẫn xuất của nó (như OpenBSD shhoặc posh) bảo toàn số âm trong $?đó, điều đó có nghĩa là việc đó return "$?"sẽ thất bại khi $?chứa số âm (sẽ xảy ra khi lệnh chạy cuối cùng là hàm trả về số âm).

    Vì vậy, return -- "$?"sẽ tốt hơn trong những vỏ. Tuy nhiên lưu ý rằng mặc dù được hỗ trợ bởi hầu hết các shell, cú pháp đó không phải là POSIX và trong thực tế không được hỗ trợ bởi mkshcác dẫn xuất tro.

    Vì vậy, để tổng hợp, với các shell dựa trên pdksh, bạn có thể sử dụng các số âm trong các đối số cho các hàm, nhưng nếu bạn làm như vậy, return "$@"sẽ không hoạt động. Trong các shell khác, return "$@"sẽ hoạt động và bạn nên tránh sử dụng các số âm (hoặc các số nằm ngoài 0..255) làm đối số return.

  • Trong tất cả các shell mà tôi biết, việc gọi returntừ bên trong một lớp con chạy bên trong một hàm sẽ khiến cho lớp con thoát ra (với trạng thái thoát được cung cấp nếu có hoặc của lệnh cuối cùng chạy), nhưng sẽ không gây ra sự quay trở lại của hàm ( Đối với tôi, không rõ liệu POSIX có cung cấp cho bạn bảo hành đó hay không, một số ý kiến ​​cho rằng exitnên sử dụng thay vì thoát các lớp con bên trong các chức năng). Ví dụ

    f() {
      (return 3)
      echo "still inside f. Exit status: $?"
    }
    f
    echo "f exit status: $?"

    sẽ xuất ra:

    still inside f. Exit status: 3
    f exit status: 0

0

Có, giá trị trả về ngầm định của hàm là trạng thái thoát của lệnh được thực thi cuối cùng . Điều đó cũng đúng ở bất kỳ điểm nào của bất kỳ tập lệnh shell nào. Tại bất kỳ điểm nào trong chuỗi thực thi tập lệnh, trạng thái thoát hiện tại là trạng thái thoát của lệnh cuối cùng được thực thi. Ngay cả lệnh được thực thi như là một phần của phép gán biến : var=$(exit 34). Sự khác biệt với các hàm là một hàm có thể thay đổi trạng thái thoát khi kết thúc thực thi hàm.

Cách khác để thay đổi "trạng thái thoát hiện tại" là bắt đầu một lớp vỏ phụ và thoát nó với bất kỳ trạng thái thoát cần thiết nào:

$ $(exit 34)
$ echo "$?"
34

Và có, việc mở rộng trạng thái thoát cần phải được trích dẫn:

$ IFS='123'
$ $(exit 34)
$ echo $?
4

A (exit 34)cũng làm việc.
Một số người có thể lập luận rằng một cấu trúc mạnh mẽ hơn nên $(return 34)và một lối thoát sẽ "thoát" tập lệnh đang được thực thi. Nhưng $(return 34)không hoạt động với bất kỳ phiên bản bash nào. Vì vậy, nó không phải là di động.

Cách an toàn nhất để đặt trạng thái thoát là sử dụng nó vì nó được thiết kế để hoạt động, xác định và returntừ một chức năng:

exitstatus(){ return "${1:-"$?"}"; }

Vì vậy, vào cuối của một chức năng. nó chính xác tương đương với không có gì returnhoặc return "$?". Sự kết thúc của một hàm không cần có nghĩa là "dòng mã cuối cùng của hàm".

#!/bin/sh
exitstatus(){ a="${1:-"$?"}"; return "$a"; }
gmx(){
    if     [ "$1" = "one" ]; then
           printf 'foo ';
           exitstatus 78
           return "$?"
    elif   [ "$1" = "two" ]; then
           printf 'baz ';
           exitstatus 89
           return
    else
           printf 'baz ';
           exitstatus 90
    fi
}  

Sẽ in:

$ ./script
foo 78
baz 89
baz 90

Cách sử dụng thực tế duy nhất "$?"là in giá trị của nó: echo "$?"hoặc lưu trữ nó trong một biến (vì nó là giá trị phù du và thay đổi với mỗi lệnh được thực thi): exitstatus=$?(nhớ trích dẫn biến trong các lệnh như export EXITSTATUS="$?".

Trong returnlệnh, phạm vi giá trị hợp lệ thường là 0 đến 255, nhưng hãy hiểu rằng các giá trị 126 + nđược sử dụng bởi một số shell để báo hiệu trạng thái thoát đặc biệt, vì vậy, khuyến nghị chung là sử dụng 0-125.

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.