xác nhận thoát bằng bẫy


9

Tôi đang cố gắng để bẫy Ctrl+Ctín hiệu yêu cầu xác nhận từ người dùng. Phần bẫy hoạt động tốt. Nhưng một khi tín hiệu bị kẹt, nó không trở lại hoạt động bình thường. Thay vào đó, nó thoát khỏi kịch bản. Làm thế nào để làm cho nó tiếp tục thực hiện khi người dùng nhấn không.

đây là mã của tôi

hell()
{
echo "Do you want to quit? Press 1 for yes and 0 for no";
read n;
if [ $n == 1 ]; then
exit 1;
fi
}

trap "hell" SIGINT

find /

Câu trả lời:


12

Chuyện gì đang xảy ra

Khi bạn nhấn Ctrl+ C, SIGINTtín hiệu được gửi đến toàn bộ nhóm quy trình nền trước . Ở đây, nó được gửi đến cả findquy trình và quy trình gọi vỏ. findphản ứng bằng cách thoát ra ngay lập tức và vỏ phản ứng bằng cách gọi bẫy.

Nếu mã trong bẫy trả về (tức là không gọi exit), thực thi sẽ tiến hành lệnh sau khi mã bị ngắt bởi tín hiệu. Ở đây, sau khi findlệnh kết thúc tập lệnh, vì vậy tập lệnh sẽ thoát ngay lập tức. Nhưng bạn có thể thấy sự khác biệt giữa nhập 0 và 1 bằng cách thêm một lệnh khác:

find /
echo "find returned $?"

Một cách để làm những gì bạn muốn (nhưng có lẽ không nên làm)

Bạn có thể làm những gì bạn muốn; nhưng phần này trong câu trả lời của tôi là về khám phá lập trình shell hơn là giải quyết một vấn đề thực tế.

  • Là một vấn đề thiết kế, tín hiệu có thể khởi động lại không phải là những gì bạn thường mong đợi trong các loại chương trình tương đối đơn giản mà shell script thường có. Kỳ vọng là Ctrl+ Csẽ giết tập lệnh.
  • Như bạn sẽ thấy bên dưới, dù sao nó cũng mở rộng khả năng của vỏ một chút.

Nếu bạn muốn tránh bị giết find, bạn cần khởi động nó trong nền : find / &. Sau đó sử dụng waitnội dung để chờ cho nó thoát ra bình thường. Một tín hiệu sẽ làm gián đoạn tích waithợp, mà bạn có thể chạy trong một vòng lặp cho đến khi bạn nhận được tín hiệu bạn muốn truyền đi. Sau đó sử dụng killđể giết công việc.

hell () {
  echo "Do you want to quit? Press 1 for yes and 0 for no"
  read n
  if [ "$n" = 1 ]; then
    # Kill the job if it's running, then exit
    if [ -n "$job_pid" ]; then kill $job_pid; fi
    exit 1
  fi
}

job_pid=
trap "hell" SIGINT

# Start a demo job in the background
for i in 1 2 3 4 5; do date; sleep 1; done &
job_pid=$!
# Call wait in a loop; wait will return 0 if the job exits, and 128+$signum if interrupted by a signal.
while ! wait; do
  echo "resuming wait"
done
job_pid=
echo last exit code: $?

Có những hạn chế đối với phương pháp này trong trình bao:

  • Có một điều kiện cuộc đua: nếu bạn nhấn Ctrl+ Cngay sau khi công việc kết thúc nhưng trước khi kết thúc job_pid=, trình xử lý tín hiệu sẽ cố gắng giết $jobpid, nhưng quá trình không còn tồn tại (ngay cả khi là zombie, vì waitđã gặt hái được nó) và quá trình ID có thể đã được sử dụng lại bởi một quy trình khác. Điều này không dễ dàng sửa chữa trong shell (có thể bằng cách đặt một trình xử lý cho SIGCHLD?).
  • Nếu bạn cần trạng thái trở lại từ công việc, bạn cần sử dụng wait $job_pidbiểu mẫu. Nhưng sau đó, bạn không thể phân biệt waitđược bị gián đoạn bởi một tín hiệu từ người Hồi giáo, công việc đã bị giết bởi một tín hiệu khác (cũng không phải là từ công việc bị chấm dứt theo thỏa thuận của chính nó với trạng thái hoàn trả ≥128, nhưng đó là một thực tế chung lập trình).
  • Điều này sẽ không dễ dàng mở rộng nếu có nhiều subjobs. Lưu ý rằng hành vi của bẫy và tín hiệu thường gây ngạc nhiên khi bạn vượt ra khỏi những điều cơ bản trong hầu hết các triển khai hệ vỏ (chỉ ksh thực hiện tốt điều này).

Để khắc phục những hạn chế này, hãy sử dụng ngôn ngữ fancier như Perl hoặc Python.

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.