bash에서 루프 while 안전하게 종료

bash에서 루프 while 안전하게 종료

다음을 수행하는 bash 스크립트가 있다고 가정해 보겠습니다.

while :
do
    foo
done

나는 이 스크립트를 콘솔에서 실행할 수 있고, foo를 두 번 실행하는 사이에 발생하는 한 언제든지 종료할 수 있기를 원합니다. 따라서 Ctrl+를 누르면 C(이것은 스크립트를 종료시키는 또 다른 동작일 수 있습니다. Ctrl+ C는 단지 예일 뿐입니다), foo를 실행한 후 사용 가능한 다음 지점에서 종료됩니다.

while :
do
    foo
    if [pressed_ctrl_c]:
        break
done

답변1

다음 구성을 시도해 볼 수 있습니다.

#!/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

몇 가지 메모

  • read쌍은 test블록 내에서 보호된 코드 세그먼트의 대화형 제어를 보여줍니다 ( ... ).
  • 이는 서브쉘 내부 exit 90와 동일하지만 내부에서 발생합니다 . break서브쉘 블록의 끝 바로 뒤에 있는 줄은 test 0 != $? ...상태를 캡처 exit 90하고 break코드가 실제로 원하는 것을 구현하는 데 사용됩니다.
  • 서브셸은 다양한 종료 상태 값을 사용하여 다양한 유형의 원하는 제어 흐름( 등...) break을 나타낼 수 있습니다.exit
  • 이는 프로그램이 자체 신호 처리기를 설치하는 것을 막지는 않습니다. 예를 들어 gdb( )에 대한 자체 처리기를 설치합니다. 사용자가 세션을 중단하는 것을 방지하는 것이 목표인 경우 인터럽트 키를 변경하면 상황을 난독화하는 데 도움이 될 수 있습니다(아래 코드 참조). 우아하지는 않지만 아마도 효과적일 것입니다.SIGINTCtrlC

터미널에서 SIGINT 키 변경

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

답변2

이것은 작동하는 것 같습니다:

#!/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
  • pressed_ctrl_c비어 있도록 초기화되었습니다 . 이는 스크립트에서 필요하지 않을 수도 있습니다.
  • trap command signum캡처 신호 번호를 설정하도록 쉘에 지시signum 그리고 실행command하나를 캡처할 때. "INT"는 Ctrl신호 + 생성에 대한 기술 용어 인 "인터럽트 신호"의 약어인 "SIGINT"의 약어 이므로 + 를 C입력하면 쉘이 이를 1로 설정합니다. (SIGINT의 값은 2이므로 타이핑 시간을 절약하고 싶다면 이렇게 말하면 됩니다.)CtrlCpressed_ctrl_ctrap "pressed_ctrl_c=1" 2
  • 루프 내부에는 괄호 안에 명령줄이 있습니다. 그러면 서브쉘이 생성됩니다.
  • 이 명령을 다시 사용해 보겠습니다 trap. 이 시간command빈 문자열입니다. 이는 쉘이 신호 번호를 무시하도록 지시합니다.signum. 괄호 안에 있으므로 하위 쉘에만 영향을 미칩니다.
  • 달리기 foo. 서브쉘에서 실행되기 때문에 +는 foo무시됩니다 . 즉, 입력해도 계속 실행됩니다.CtrlC
  • 서브쉘을 백그라운드에 두고...
  • ...완료될 때까지 기다립니다.
  • 명령이 성공 하면 wait명령문 실행이 계속됩니다 if. 실패하면 다시 하세요. (나는 이것에 대해 다시 돌아올 것이다.)
  • 설정된 경우 $pressed_ctrl_c루프를 중단합니다. echo+가 터미널에 표시되고 Ctrl다음 줄로 이동하려는 경우 명령의 주석 처리를 제거하도록 선택할 수 있습니다.C^C

백그라운드에서 명령을 실행한 다음 wait즉시 실행합니다. 이는 포그라운드에서 명령을 실행하는 것과 매우 유사합니다(적어도 스크립트 내에서 실행하는 경우). wait명령은 서브쉘이 종료될 때, 즉 foo명령이 종료될 때 성공적으로 종료됩니다. ( 오류가 반환 wait되어도 명령은 성공적으로 종료됩니다.) 첫 번째 명령이 성공적으로 종료되면 두 번째 명령을 건너뛰고 으로 이동합니다 .foowaitif

루프를 실행하는 셸은 인터럽트를 포착하지만 하위 셸과 프로세스는 foo이를 무시합니다. 따라서 Ctrl+ 를 입력하면 C쉘이 pressed_ctrl_c1로 설정되고 (첫 번째) wait명령이 중단됩니다. 첫 번째 wait명령이 실패했으므로 두 번째 명령을 계속 진행합니다. foo서브셸은 여전히 ​​실행 중이므로 아직 수행할 작업이 있다는 점을 기억하세요 wait(즉, 완료될 때까지 기다립니다 foo).

마지막으로, 이 변수가 실행 중에 +를 눌렀음을 나타내도록 설정된 경우 Ctrl출력 루프를 중단합니다.Cfoo

Ctrl+를 두 번 누르면 C두 번째 명령이 중단되고 두 번째 wait명령이 중단됩니다. 이렇게 하면 스크립트가 종료되고 foo백그라운드에서 실행되는 동안 쉘 프롬프트로 돌아갑니다. wait  ||  wait  ||  wait  ||  wait원하는 만큼 늘려서 이 문제를 완화할 수 있습니다 . Ctrl스크립트를 조기에 종료하려면 +를 각각 한 번씩 입력해야 합니다 .Cwait

오!

이에 대한 한 가지 문제는 스크립트에 의해 백그라운드에 배치된 프로세스의 표준 입력이 로 설정되어 있다는 것입니다 /dev/null. foo키보드에서 읽는 경우 위의 내용을 수정해야 합니다.

관련 정보