Có cách nào để viết một hàm bash hủy bỏ toàn bộ quá trình thực thi, bất kể nó được gọi như thế nào?


83

Tôi đang sử dụng câu lệnh "exit 1" trong các hàm bash của mình để chấm dứt toàn bộ tập lệnh và nó hoạt động tốt:

function func()
{
   echo "Goodbye"
   exit 1
}
echo "Function call will abort"
func
echo "This will never be printed"

Nhưng sau đó tôi nhận ra rằng nó không hoạt động khi được gọi như:

res=$(func)

Tôi hiểu rằng tôi đã tạo một vỏ con và "thoát 1" hủy bỏ vỏ con đó chứ không phải là vỏ chính ....

Nhưng có cách nào để viết một hàm hủy bỏ toàn bộ quá trình thực thi, bất kể nó được gọi như thế nào không? Tôi chỉ cần lấy giá trị trả về thực (được lặp lại bởi hàm).

Câu trả lời:


90

Những gì bạn có thể làm là đăng ký trình bao cấp cao nhất để TERMtín hiệu thoát ra, sau đó gửi một TERMđến trình bao cấp cao nhất:

#!/bin/bash
trap "exit 1" TERM
export TOP_PID=$$

function func()
{
   echo "Goodbye"
   kill -s TERM $TOP_PID
}

echo "Function call will abort"
echo $(func)
echo "This will never be printed"

Vì vậy, hàm của bạn sẽ gửi một TERMtín hiệu trở lại trình bao cấp cao nhất, được bắt và xử lý bằng lệnh được cung cấp, trong trường hợp này "exit 1",.


1
Tôi đã nghĩ đến setsid()hàm C , nhưng nó không hoạt động theo cách hoàn toàn giống nhau. Đã cập nhật để không sử dụng setsidlệnh, vì nó sẽ yêu cầu chúng tôi bắt đầu một quy trình mới.
FatalError

3
Làm. Bất kỳ mức nào của chức năng lồng vào nhau, bất kỳ luồng nào ... Chỉ hoạt động.
LiMar

Có thể thực hiện một hàm abort () chung trong số này thoát bằng cách sử dụng mã của đối số đầu tiên, ví dụ: abort 2sẽ thực hiện trap "exit 2" TERMtrước đó killkhông?
Neil C. Obremski

@ NeilC.Obremski: Chắc chắn rồi. Vấn đề chủ yếu là làm thế nào để giá trị truyền trở lại shell cấp cao nhất. Có thể sử dụng một lệnh như tempfileđể chọn một nơi để lưu trữ mã trong trình bao trên cùng, sau đó để trình bao đang thoát ra ghi mã vào tệp đó, sau đó chỉ cần đọc trong trình xử lý của cha mẹ.
FatalError

2
Có vẻ như không hoạt động khi có các phiếu phụ can thiệp. Xem giải pháp thay thế cho bash bằng cách sử dụng set -E tại đây
Ron Burk có

42

Bạn có thể sử dụng set -ethoát nếu một lệnh thoát với một tình trạng khác không :

set -e 
func
set +e

Hoặc lấy giá trị trả về:

(func) || exit $?

3
Thật thú vị, tôi thấy một giải pháp. "set -e" trong tập lệnh chính khiến bất kỳ hàm nào trả về 1 (hoặc thoát cùng với nó) sẽ hủy bỏ toàn bộ quá trình thực thi. Thật không may "set -e" thay đổi đáng kể tất cả các hành vi và tôi không thể sử dụng nó.
LiMar

1
Bạn nên sử dụng (()) để so sánh số. Bên trong của [] -gt, -eq, v.v. nên được sử dụng.
jordanm

16
hoặc thậm chíres=$(func) || exit
glenn Jackman

@glennjackman nhận được cookie, tôi nghĩ vậy. Đó là rác tôi có sạch hơn nhiều!
brice

3
kiểu đó thách thức tinh thần của câu hỏi. nếu mọi lệnh gọi hàm cần tự bắt lỗi, thì không có ích gì khi sử dụng exit ngay từ đầu. hàm chỉ có thể trả về. / Tuy nhiên, nếu set -eđược thiết lập trên toàn cầu, điều này không làm tốt nghĩa
phil294

2

Tôi đoán tốt hơn là

#!/bin/bash
set -e
trap "exit 1" ERR

myfunc() {
     set -x # OPTIONAL TO SHOW ERROR
     echo "Exit with failure"
     set +x # OPTIONAL
     exit 1
}
echo "BEFORE..."
myvar="$(myfunc)"
echo "AFTER..But not shown"

1

Một tiến trình con không thể buộc tiến trình mẹ đóng một cách hoàn toàn. Bạn cần sử dụng một số loại cơ chế báo hiệu. Các tùy chọn có thể bao gồm một giá trị trả về đặc biệt hoặc có thể gửi một số tín hiệu với kill, một cái gì đó như

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}

1

Nhưng có cách nào để viết một hàm hủy bỏ toàn bộ quá trình thực thi, bất kể nó được gọi như thế nào không?

Không.

Tôi chỉ cần lấy giá trị trả về thực (được lặp lại bởi hàm).

Bạn có thể

res=$(func)
echo $?
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.