Giữ mã thoát khi bẫy SIGINT và tương tự?


14

Nếu tôi sử dụng trapnhư được mô tả, ví dụ như trên http://linuxcommand.org/wss0160.php#trap để bắt ctrl-c (hoặc tương tự) và dọn dẹp trước khi thoát thì tôi sẽ thay đổi mã thoát.

Bây giờ điều này có lẽ sẽ không tạo ra sự khác biệt trong thế giới thực (ví dụ: vì các mã thoát không phải là di động và trên hết không phải lúc nào cũng rõ ràng như được thảo luận trong Mã thoát mặc định khi quá trình kết thúc? ) Nhưng tôi vẫn tự hỏi liệu có tồn tại không thực sự không có cách nào để ngăn chặn điều đó và trả lại mã lỗi mặc định cho các tập lệnh bị gián đoạn?

Ví dụ (trong bash, nhưng câu hỏi của tôi không nên được coi là bash cụ thể):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Đầu ra:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Đã chỉnh sửa để loại bỏ để phù hợp hơn với POSIX.)

(Đã chỉnh sửa một lần nữa để biến nó thành tập lệnh bash, thay vào đó, câu hỏi của tôi không cụ thể.)

Đã chỉnh sửa để sử dụng "INT" di động cho bẫy có lợi cho "SIGINT" không di động.

Chỉnh sửa để loại bỏ các dấu ngoặc nhọn vô dụng và thêm giải pháp tiềm năng.

Cập nhật:

Tôi đã giải quyết nó ngay bây giờ bằng cách thoát ra với một số mã lỗi được mã hóa cứng và bẫy EXIT. Điều này có thể có vấn đề trên một số hệ thống nhất định vì mã lỗi có thể khác nhau hoặc bẫy EXIT không thể thực hiện được nhưng trong trường hợp của tôi thì nó đủ ổn.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

Kịch bản của bạn trông hơi lạ: bạn bảo readđọc từ bộ đồng xử lý hiện tại và trap cmd SIGINTsẽ không hoạt động như tiêu chuẩn nói rằng bạn nên sử dụng trap cmd INT.
schily

À đúng, theo POSIX, tất nhiên không có tiền tố SIG.
phk

Rất tiếc, nhưng sau đó "đọc -p" cũng sẽ không được hỗ trợ, vì vậy tôi sẽ điều chỉnh nó cho bash.
phk

@schily: Tôi không biết ý của bạn với "coprocess" là gì.
phk

Chà, trang người đàn ông Korn Shell cho biết read -pđọc đầu vào từ bộ đồng xử lý hiện tại.
schily

Câu trả lời:


4

Tất cả những gì bạn cần làm là thay đổi trình xử lý EXIT bên trong trình xử lý dọn dẹp của bạn. Đây là một ví dụ:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '

ý bạn là trap cleanup INTthay vì trap cleanup EXIT?
Jeff Schaller

Tôi nghĩ rằng tôi có nghĩa là EXIT. Tuy nhiên, khi lối ra cuối cùng được gọi, tuy nhiên đã xong, chúng ta thay đổi bẫy thành lối thoát bằng return 0. Tôi không tin rằng trình xử lý tín hiệu được đệ quy.
đá

OK, trong ví dụ của bạn, tôi không bẫy (SIG) INT, đây thực sự là điều tôi muốn để dọn dẹp ngay cả khi người dùng thoát khỏi tập lệnh tương tác của tôi thông qua ctrl-c.
phk

@phk Ok. Tôi nghĩ mặc dù bạn có ý tưởng làm thế nào để buộc thoát ra để trả về 0 hoặc một số giá trị khác mà những gì tôi thu thập được là vấn đề. Tôi giả sử bạn sẽ có thể điều chỉnh mã để đặt cài đặt EXIT bẫy bên trong trình xử lý dọn dẹp thực sự của bạn được gọi bởi SIGINT.
đá

@rocky: Mục tiêu của tôi là có một trình xử lý bẫy trong khi không thay đổi mã thoát mà tôi thường có nếu không có trình xử lý. Bây giờ tôi chỉ có thể trả lại mã thoát mà bạn thường nhận được nhưng vấn đề là tôi không biết mã thoát chính xác trong những trường hợp này (như đã thấy trong luồng tôi liên kết đến và bài đăng từ meuh khá phức tạp). Bài viết của bạn vẫn hữu ích, tôi không nghĩ đến việc xác định lại trình xử lý bẫy một cách linh hoạt.
phk

9

Trên thực tế, việc ngắt nội bộ của bash readdường như hơi khác so với việc ngắt một lệnh chạy bằng bash. Thông thường, khi bạn nhập trap, $?được đặt và bạn có thể giữ nó và thoát với cùng một giá trị:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Nếu tập lệnh của bạn bị gián đoạn khi thực hiện lệnh như sleep hoặc thậm chí là nội dung dựng sẵn wait, bạn sẽ thấy

130 SIGINT
130 EXIT

và mã thoát là 130. Tuy nhiên, read -pdường như $?là 0 (trên phiên bản bash 4.3.42 của tôi).


Việc xử lý các tín hiệu trong quá trình readcó thể đang được tiến hành, theo tệp thay đổi trong bản phát hành của tôi ... (/ usr / share / doc / bash / THAY ĐỔI)

thay đổi giữa phiên bản này, bash-4.3-alpha và phiên bản trước đó, bash-4.2-phát hành.

  1. Các tính năng mới trong Bash

    r. Khi ở chế độ Posix, 'read' bị gián đoạn bởi tín hiệu bị kẹt. Sau khi chạy trình xử lý bẫy, đọc trả về tín hiệu 128 + và loại bỏ mọi đầu vào đọc một phần.


OK, điều đó thực sự kỳ lạ. Đó là 130 trên shell tương tác trên bash 4.3.42 (dưới cygwin), 0 dưới cùng một shell khi ở chế độ tập lệnh và POSIX hoặc không có sự khác biệt. Nhưng sau đó, dưới dấu gạch ngang và
bận rộn,

Tôi đã thử chế độ POSIX với một cái bẫy không thoát ra và nó khởi động lại read, như đã lưu ý trong tệp THAY ĐỔI (thêm vào câu trả lời của tôi). Vì vậy, nó có thể đang được tiến hành.
meuh

Mã thoát 130 là 100% không di động. Trong khi Bourne Shell sử dụng 128 + signolàm mã thoát cho tín hiệu, ksh93 sử dụng 256 + signo. POSIX nói: một cái gì đó trên 128 ....
schily

@schily Đúng, như đã lưu ý trong chủ đề tôi liên kết đến trong bài viết gốc ( unix.stackexchange.com/questions/99112 ).
phk

6

Bất kỳ mã thoát tín hiệu thông thường nào cũng sẽ có sẵn $?khi vào trình xử lý bẫy:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

Nếu có bẫy EXIT riêng, bạn có thể sử dụng cùng một phương pháp: ngay lập tức giữ trạng thái thoát được truyền từ bộ xử lý tín hiệu (nếu có), sau đó trả về trạng thái thoát đã lưu.


Điều này áp dụng cho hầu hết các vỏ? Rất đẹp. localkhông phải là POSIX.
phk

1
Đã chỉnh sửa. Tôi đã không được thử nghiệm khác {ba,z}sh. AFAIK, đó là điều tốt nhất có thể được thực hiện, bất kể.
Tom Hale

Điều này không hoạt động trong dấu gạch ngang ( $?là 1 cho dù tín hiệu nhận được là gì).
MoonSweep

2

Chỉ cần trả về một số mã lỗi không đủ để mô phỏng lối ra bằng SIGINT. Tôi ngạc nhiên khi không ai đề cập đến điều này cho đến nay. Đọc thêm: https://www.cons.org/cracauer/sigint.html

Cách thích hợp là:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Điều này hoạt động với Bash, Dash và zsh. Để có tính di động cao hơn, bạn cần sử dụng thông số tín hiệu số (mặt khác, zsh mong đợi một tham số chuỗi cho killlệnh ...)

Cũng lưu ý điều trị đặc biệt của EXITtín hiệu. Điều này là do một số shell (cụ thể là Bash) thực hiện bẫy EXITcho bất kỳ tín hiệu nào (sau khi có thể xác định bẫy trên tín hiệu đó). Việc thiết lập lại EXITbẫy ngăn chặn điều đó.

Chỉ thực hiện mã khi thoát "bình thường"

Việc kiểm tra chỉ [ $sig = EXIT ]cho phép thực thi mã khi thoát bình thường (không có tín hiệu). Tuy nhiên, tất cả các tín hiệu phải có bẫy mà cuối cùng đặt lại bẫy vào EXITlúc đó; normal_exit_only_cleanupcũng sẽ được gọi cho các tín hiệu không. Nó cũng sẽ được thực hiện bởi một lối thoát thông qua set -e. Điều này có thể được khắc phục bằng cách đặt bẫy ERR(không được Dash hỗ trợ) và thêm một kiểm tra [ $sig = ERR ]trước kill.

Phiên bản chỉ đơn giản hóa Bash

Mặt khác, hành vi này có nghĩa là trong Bash bạn chỉ có thể làm

trap cleanup EXIT

để chỉ thực thi một số mã dọn dẹp và giữ lại trạng thái thoát.

đã chỉnh sửa

  • Xây dựng hành vi "EXIT bẫy mọi thứ" của Bash

  • Xóa tín hiệu KILL, không thể bị mắc kẹt

  • Xóa tiền tố SIG khỏi tên tín hiệu

  • Đừng cố gắng kill -s EXIT

  • Đưa set -e/ ERR vào tài khoản


Không có yêu cầu chung rằng một chương trình bẫy tín hiệu chấm dứt theo cách bảo tồn thực tế rằng nó đã nhận được tín hiệu. Ví dụ, một chương trình có thể tuyên bố rằng gửi SIGINTlà cách để tắt nó và nó có thể quyết định thoát bằng 0 nếu nó được quản lý để chấm dứt mà không có lỗi hoặc mã lỗi khác nếu nó không tắt sạch. Trường hợp tại điểm : top. Chạy top; echo $?và sau đó nhấn Ctrl-C. Trạng thái được đổ trên màn hình sẽ là 0.
Louis

1
Cảm ơn bạn đã chỉ ra rằng. Tuy nhiên, người đăng đặc biệt hỏi về việc giữ mã thoát.
philipp2100
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.