Thoát ra an toàn trong khi các vòng lặp trong bash


7

Nói rằng tôi có một tập lệnh bash nào:

while :
do
    foo
done

Tôi muốn có thể chạy tập lệnh này từ bảng điều khiển và có thể thoát tập lệnh này vào một thời điểm tùy ý miễn là nó xảy ra ở giữa hai lần chạy foo. Vì vậy, nếu, giả sử, tôi nhấn Ctrl+ C(nó có thể là một hành động khác khiến tập lệnh thoát ra, Ctrl+ Cchỉ là một ví dụ) sẽ thoát tại điểm khả dụng tiếp theo sau khi foo được thực thi:

while :
do
    foo
    if [pressed_ctrl_c]:
        break
done

1
Bạn có thể nhìn vào bẫy - tutorialspoint.com/unix/unix-signals-traps.htm
Rahul

Điều "bẫy <kbd> ctrl </ kbd> + c" có đảm bảo rằng tôi không đột nhập vào bên trong foo không?
Henry Henrinson

Câu trả lời:


5

Bạn có thể thử loại cấu trúc này:

#!/bin/bash
#
INTR=
trap 'INTR=yes; echo "** INTR **" >&2' INT

while :
do
    (
        # Protect the subshell block
        trap '' INT

        # Protected code here
        echo -n "The date/time is: "
        sleep 2
        date
        read -t2 -p 'Continue (y/n)? ' YN || echo
        test n = "$YN" && echo "Asked for BREAK" >&2 && exit 90
    )
    SS=$?
    test 90 -eq $SS && echo "Matched BREAK" >&2 && break

    # Ctrl/C, perhaps?
    test yes = "$INTR" && echo "Matched INTR" >&2 && break
done
exit 0

Một số lưu ý

  • Các readtestcặp chứng minh kiểm soát tương tác để đoạn mã bảo vệ bên trong ( ... )khối.
  • exit 90là tương đương breaknhưng từ bên trong một subshell. Các test 0 != $? ...dòng ngay sau khi khối subshell đầu ở đó để nắm bắt được exit 90tình hình và thực hiện breakrằng mã thực sự muốn.
  • Các subshell có thể sử dụng các giá trị trạng thái thoát khác nhau để chỉ loại khác nhau của dòng điều khiển cần thiết ( break, exit, vv ...)
  • Điều này không ngăn cản một chương trình cài đặt trình xử lý tín hiệu của riêng nó. Ví dụ: gdbcài đặt trình xử lý riêng của nó cho SIGINT( CtrlC). Nếu mục đích là để ngăn người dùng thoát ra khỏi phiên, việc thay đổi phím ngắt có thể giúp làm xáo trộn tình huống (xem mã bên dưới). Không phù hợp nhưng có khả năng hiệu quả.

Thay đổi phím SIGINT trên thiết bị đầu cuối

G=$(stty -g)                    # Save settings
test -n "$G" && stty intr ^A    # That is caret and A, not Ctrl/A

# ... SIGINT generated with Ctrl/A rather than Ctrl/C ...

test -n "$G" && stty "$G"       # Restore original settings

Bạn chưa giải quyết được yêu cầu cho phép gọi hiện tại foođể chạy đến khi hoàn thành, ngay cả khi <Ctrl> + <C> đã được nhập.
Scott

À, vâng. Tôi sẽ cập nhật mã phù hợp, cảm ơn bạn @Scott
roaima

Câu chuyện vui: Trông rất giống một trong những lần thử đầu tiên của tôi. Tôi đã không đăng nó, bởi vì nó không hoạt động - bởi vì tôi đã gõ nó vào vỏ tương tác của mình dưới dạng một lớp lót, thay vì đưa nó vào một tệp. Một cách nhanh chóng để kiểm tra điều này là gọi kịch bản trên sourcechứ không phải là cách thông thường. Tôi nghĩ bạn sẽ thấy rằng <Ctrl> + <C> không có hiệu lực. Tôi không thể giải thích nó (bạn có thể không?) Gợi ý thân thiện: thay đổi exit 0thành (exit 0)(hoặc đơn giản true), hoặc điều này sẽ khiến vỏ tương tác của bạn thoát ra. Khi bạn nói rằng bạn có thể nắm bắt được exit 1tình trạng của bạn exit 90.
Scott

@ Hủy bỏ, nó dự định được chạy như một phần của tập lệnh chứ không phải thông qua source, chủ yếu là vì nếu bạn cố gắng thực hiện traptrực tiếp trên dòng lệnh, bạn hoàn toàn có thể dễ dàng tự buộc mình vào các nút thắt. Cảm ơn bạn vì exit 1- Tôi nghĩ rằng tôi đã bắt được tất cả khi tôi chuyển sang 90(Tôi cảm thấy rằng giá trị mới này "rõ ràng hơn" như là một tiêu chí thoát hiểm).
roaima

Vâng, tôi hiểu rằng nó có nghĩa là một kịch bản. Tôi chỉ nghĩ rằng tôi có thể gỡ lỗi các lệnh script nhanh hơn bằng cách thực hiện nó trên dòng lệnh thay vì liên tục chỉnh sửa, lưu và chạy tệp script. Đối với việc buộc vỏ của tôi trong các nút thắt: tôi đã làm nó trong ngoặc đơn, như thế (trap "pressed_ctrl_c=1" INT; while true …). Tập
Scott

4

Điều này dường như làm việc:

#!/bin/sh
pressed_ctrl_c=
trap "pressed_ctrl_c=1" INT
while true
do
        (trap "" INT; foo)&
        wait  ||  wait
        if [ "$pressed_ctrl_c" ]
        then
                # echo
                break
        fi
done
  • Khởi tạo pressed_ctrl_cthành null. Điều này có lẽ không cần thiết trong một kịch bản.
  • trap command signumbáo cho shell được thiết lập để bắt số tín hiệu signum và thực thi commandkhi bắt được một tín hiệu . Đây là từ viết tắt của cụm từ SIGINT, viết tắt của cụm từ SIGINT, viết tắt của tiếng Anh, đó là thuật ngữ kỹ thuật cho tín hiệu được tạo bởi Ctrl+ C, vì vậy, khi bạn nhập Ctrl+ C, trình bao đặt pressed_ctrl_cthành 1. (SIGINT có giá trị số là 2, vì vậy bạn có thể nói trap "pressed_ctrl_c=1" 2nếu bạn muốn tiết kiệm khi gõ.)
  • Trong vòng lặp, chúng ta có một dòng lệnh trong ngoặc đơn. Điều này tạo ra một vỏ phụ.
  • Chúng tôi sử dụng traplệnh một lần nữa. Lần này commandlà một chuỗi null; Điều này nói với vỏ để bỏ qua số tín hiệu signum. Vì đây là bên trong dấu ngoặc đơn, nó chỉ ảnh hưởng đến lớp con.
  • Chạy đi foo. Vì nó được chạy từ subshell, foosẽ bỏ qua Ctrl+ C, tức là, nó sẽ tiếp tục chạy ngay cả khi bạn gõ nó.
  • Đặt subshell trong nền nền.
  • Càng và chờ nó hoàn thành.
  • Nếu waitlệnh thành công, đi đến ifcâu lệnh. Nếu thất bại, thực hiện một cái khác. (Tôi sẽ quay lại vấn đề này.)
  • Nếu $pressed_ctrl_cđược đặt, thoát ra khỏi vòng lặp. Tùy chọn bỏ ghi echolệnh nếu Ctrl+ Cxuất hiện trên thiết bị đầu cuối của bạn ^C và bạn muốn chuyển sang dòng tiếp theo.

Chúng tôi chạy một lệnh trong nền, và sau đó ngay lập tức waitcho nó. Điều này rất giống với việc chạy lệnh ở nền trước (ít nhất, khi được thực hiện trong một tập lệnh). Các waitlệnh sẽ chấm dứt thành công khi subshell chấm dứt; tức là khi foolệnh kết thúc ( waitLệnh sẽ chấm dứt thành công ngay cả khi footrả về lỗi.) Khi waitlệnh đầu tiên kết thúc thành công, chúng ta bỏ qua lệnh thứ hai và đi đến if.

Lớp vỏ đang chạy vòng lặp đang bị gián đoạn, nhưng lớp vỏ, và do đó, fooquá trình này đang bỏ qua chúng. Vì vậy, khi bạn gõ Ctrl+ C, shell sẽ đặt pressed_ctrl_cthành 1 và hủy bỏ lệnh (đầu tiên) wait. Vì waitlệnh đầu tiên thất bại, chúng tôi chuyển sang lệnh thứ hai. Hãy nhớ rằng, foolớp con vẫn đang chạy, vì vậy điều này waitvẫn còn một việc phải làm (nghĩa là nó sẽ đợi cho foođến khi kết thúc.)

Cuối cùng, nếu biến đã được đặt để chỉ ra rằng dấu Ctrl+ Cđã được nhấn trong khi foođang chạy, ngắt đầu ra vòng lặp.

Nếu bạn nhấn Ctrl+ Chai lần, cái thứ hai sẽ ngắt và hủy bỏ waitlệnh thứ hai . Điều này sẽ khiến tập lệnh của bạn chấm dứt và đưa bạn trở lại dấu nhắc shell của bạn, trong khi vẫn foochạy trong nền. Bạn có thể giảm thiểu điều này bằng cách nói wait  ||  wait  ||  wait  ||  wait; mở rộng nó như xa như bạn muốn. Bạn sẽ cần gõ Ctrl+ Cmột lần cho mỗi lần wait để kết thúc tập lệnh sớm.

Ôi!

Một vấn đề với điều này là các quy trình được đặt vào nền bởi một tập lệnh có đầu vào tiêu chuẩn của chúng được đặt thành /dev/null. Nếu bạn foođọc từ bàn phím, ở trên sẽ cần sửa đổi.

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.