ctrl+c를 올바르게 처리하기 위해 내 쉘을 얻으려고합니다.

ctrl+c를 올바르게 처리하기 위해 내 쉘을 얻으려고합니다.

Ctrl C를 누를 때 실행 중인 프로그램이 SIGINT를 수신하도록 쉘이 설정되는 방법을 이해하려고 노력하고 있지만 bash를 실행하고 내부에서 다른 프로그램을 실행한 다음 Ctrl + C를 누르면 쉘은 SIGINT를 수신하지 않습니다. 실행 중인 프로그램이 중지되지만 bash는 중지되지 않습니다.

이는 다른 쉘에서 bash를 시작할 때도 적용됩니다.

나는 아무 키나 누르면 서브루틴을 실행하는 나만의 특수 쉘 프로그램을 만들어 보았습니다.

#! /usr/bin/env bash

set -e

while true
do
    echo press any key to run $@
    read -n 1 -r -s key
    code=$(echo $key | cat -v)
    if [[ "$code" == "^D" ]]
    then
        echo bye
        exit 0
    fi
    echo running $@
    $@ || echo oops
done

이것이 하는 일입니다:

$ ./shell.sh sleep 1
press any key to run sleep 1
running sleep 1
press any key to run sleep 1
running sleep 1
press any key to run sleep 1
bye

아무 키나 누르면 인수로 전달한 프로그램이 실행됩니다. 그런 다음 프롬프트에서 Ctrl+D를 눌러 종료합니다.

그러나 Ctrl+C를 사용하여 수면 프로그램을 중지하려고 하면 다음과 같습니다.

$ ./shell.sh sleep 10
press any key to run sleep 10
running sleep 10
^C
$

또한 쉘도 중지합니다. 이는 예상된 현상입니다.

그러나 bash단독으로 실행하면 이와 같이 동작하지 않습니다.

나는 대화형 bash처럼 작동하기를 원합니다. "sleep" 명령을 종료한 다음 프롬프트에서 계속 진행합니다.

나는 또한 -m이것이 성공할 경우를 대비하여 이것을 실행해 보았습니다.

$ bash -m shell.sh sleep 10
press any key to run sleep 10
running sleep 10
^C$

그러나 그것은 또한 종료됩니다.

그래서 두 가지 질문이 있습니다. (아마도 같은 대답이 있을 것입니다.)

  • ctrl+c를 눌렀을 때 bash가 대화형으로 실행되고 종료되지 않도록 하려면 어떻게 해야 합니까? 서브루틴이 종료되고 프롬프트가 계속되도록 허용하면 됩니다.

  • Bash는 내부적으로 무엇을 하고 있나요? 예를 들어, 위의 질문에 답할 수 있는 bash 옵션이 없기 때문에 Python에서 동일한 쉘 프로그램을 만들고 싶을 때(나도 만들었지만 여기에는 포함되지 않음) 어떻게 해야 합니까? 신호를 전달하여 작동하게 할 수 있다는 것을 알고 있지만, 이를 수행하는 방법이 실제 쉘이 수행하는 것과 동일하다는 것을 알고 싶습니다.

나는 내 "쉘"에서 신호를 처리하여 작동하게 할 수 있다고 생각했지만 실제 쉘이 작동하는 방식이 아니라는 것을 알려주고 전경과 그룹에 대한 내용을 계속 듣고 있으므로 할 수 있기를 바라고 있습니다. 내 예에 연결됩니다. - 아니면 적어도 내 예에 연결되지 않는 이유에 대한 설명이 있습니다.

답변1

예비 설명

"특수 쉘 프로그램"에는 주요 문제와 관련이 없는 몇 가지 문제가 있지만, 프로그램을 계속 사용하려면 문제를 해결하는 것이 좋습니다.

  1. 올바른 인용.

  2. 확장 $@( "$@"위 참조)을 명령으로 전달하면 사용자는 쉘 자체의 상태를 해석하는 내장 기능 변경( try ./shell.sh set -x또는 ) 을 포함하여 쉘 내장 기능을 실행할 수 있습니다 ./shell.sh exit. 최소한 "$@"서브셸에서 실행하되(쉘 내장 기능을 허용함) exec -- "$@"서브셸에서 실행하는 것이 바람직합니다(그렇지 않음).

  3. echo고정 문자열을 에코하려는 경우가 아니면 피하십시오 .사용printf.

  4. 당신은 필요하지 않습니다 cat -v.Bash는 EOT 문자( ^D) 를 생성할 수 있습니다.그리고 당신의 $key것과 비교해 보세요.

[[여기도 필요없어 [괜찮아 (그들은 동등하지 않습니다하지만). 문자열 비교의 기본 연산자 [=Bash가 뒤에 오는 내용을 이해할 수 있지만 ==다른 쉘에서는 이를 이해할 수도 있고 이해하지 못할 수도 있다는 것입니다. 사용하기 좋고, 충분한 곳에 사용하지 않는 것이 제 개인적인 취향입니다. 이렇게 하면 이식 불가능한 코드에 익숙해지지 않고 Bash만큼 풍부하지 않은 셸에서 무엇을 할 수 있는지 알 수 있습니다. 분명히 말하면 이것은[[[=[[[내 개인적인 취향, [[코드에는 문제가 없습니다.

다음은 개선된 스크립트입니다(하지만 원래의 (error) 와 마찬가지로 Ctrl+ 를 계속 처리하므로 c나중에 수정될 예정입니다).

#! /usr/bin/env bash
set -e
while true
do
    printf 'press any key to run %s\n' "${*@Q}"
    read -n 1 -r -s key
    if [ "$key" = $'\cD' ]
    then
        echo bye
        exit 0
    fi
    printf 'running %s\n' "${*@Q}"
    (exec -- "$@") || echo oops
done

코드는 또한$*적절한 경우와 함께@Q수정자명령을 명시적으로 인쇄합니다. ./shell.sh echo "a b"원본 스크립트의 동작과 비교해 보세요 .

이제 요점을 살펴보겠습니다.


요점을 말하자면

Ctrl+ c터미널의 라인 규칙은 (보통) 포그라운드 프로세스 그룹의 프로세스에 SIGINT를 보냅니다. 쉘은 그룹에 있을 수도 있고 없을 수도 있으므로 신호를 받을 수도 있고 받지 않을 수도 있습니다. 신호를 중계하지 않습니다.

전경 프로세스 그룹은 현재 제어 터미널에서 "전경에 있는" 프로세스 그룹입니다. 일반적으로 이는 제어 터미널에 어떤 그룹이 전경에 있는지 지속적으로 알려주는 대화형 쉘입니다.

일반적으로 말하면, 하위 프로세스가 실행 중인 향후 상위 프로세스(예: "쉘")는 새 프로세스가 동일한 프로세스 그룹(예: 이전 프로세스, 상위 프로세스)에 있는지 아니면 새 프로세스 그룹에 있는지 결정해야 합니다. 이 답변을 참조하세요:실행 중인 프로세스의 프로세스 그룹을 변경하는 방법이 있습니까?. 새로운 프로세스 그룹이 포그라운드 프로세스 그룹이 되어야 한다면,단말기에 알려야 함; 나중에 하위 프로세스가 종료되면 전경 프로세스 그룹이 상위 프로세스 그룹으로 다시 설정되어야 합니다.

따라서 "쉘"이 프로세스 그룹을 처리하는지 여부와 방법에 따라 Ctrl+ c로 인해 SIGINT가 전경 프로세스 그룹으로 전송되면 신호가 전달됩니다.

  • 하위 프로세스의 (새) 그룹이 포그라운드 프로세스 그룹이고 쉘이 다른(일반적으로 이전) 프로세스 그룹에 있는 경우 하위 프로세스로 보내되 "쉘"로 보내지 마십시오. (일부 또는 전체) 자손 중 일부 또는 모두가 신호를 받게 됩니다.
  • 포그라운드 프로세스 그룹에 있는 경우 "쉘" 및 하위 프로세스(+자손)에 적용됩니다. 이를 달성하는 가장 쉬운 방법은 하위 프로세스 그룹을 변경하지 않는 것입니다. 프로세스 그룹 개념이 존재하지 않습니다);
  • "셸"로 지정하지만, 다른 프로세스 그룹에 있고 전경 프로세스 그룹이 "셸" 중 하나인 경우 하위 프로세스(+자손)로 지정하지 않습니다.

이는 "쉘"을 방지 Ctrl하고 c중단하는 한 가지 방법은 "쉘"이 포그라운드 프로세스 그룹에 있지 않도록 하는 것임을 의미합니다.

또 다른 접근 방식은 "쉘"이 전경 프로세스 그룹에 위치하여 신호를 처리하도록 허용하는 것입니다. 귀하의 옵션은 다음과 같습니다:

  • 귀하의 "쉘"은 아마도 특별한 작업을 수행하지 않을 것입니다. SIGINT는 이를 종료합니다. 이것이 당신이 피하고 싶은 것입니다. 엄밀히 말하면 "아무 것도 하지 않는" "쉘"은 부모로부터 상속된 무시 마스크가 SIGINT를 무시하지만 이는 극단적인 경우입니다.
  • 귀하의 "쉘"은 SIGINT를 무시하도록 명시적으로 선택할 수 있습니다. 스크립트가 에 의해 해석되는 경우 하위 프로세스가 SIGINT를 무시하지 않으려면(상속으로 인해) 하위 쉘 ( ; )에서 원래 구성으로 재설정해야 합니다 bash. 그러나 "원래 구성"은 " in에는 항목에 "shell" 값이 있으므로 극단적인 경우에는 자식이 SIGINT를 무시하지 않도록 하지 못할 수도 있습니다.trap '' INTexec(trap - INT; exec -- "$@")
  • "쉘"은 SIGINT를 포착하고 일부 작업을 수행할 수 있습니다. 스크립트가 해석되는 경우에는 무작동( ) 또는 공백이 있을 수 있지만 빈 문자열은 bash필요 하지 않습니다. 아이는 마치 덫이 존재하지 않는 것처럼 행동할 것입니다. Bash는 셸에 들어갈 때 무시되는 신호를 캡처하거나 재설정할 수 없습니다.trap 'shell code' INTshell code:

트랩이 포함된 스크립트는 다음과 같습니다.

#! /usr/bin/env bash
set -e
trap 'echo "the shell got SIGINT"' INT
while true
do
    printf 'press any key to run %s\n' "${*@Q}"
    read -n 1 -r -s key
    if [ "$key" = $'\cD' ]
    then
        echo bye
        exit 0
    fi
    printf 'running %s\n' "${*@Q}"
    (exec -- "$@") || echo oops
done

다양한 단계에서 +를 ./shell.sh sleep 10눌러 Ctrl실행하고 실험해 보세요. 실행할 때 +의 SIGINT 신호가 이에 도달하여 중단된다는 c것을 알아야 합니다.sleepCtrlc반품해석 쉘에 들어갑니다(트랩이 실행되는 것을 볼 수 있습니다). 이는 동일한 프로세스 그룹에서 실행 중이고 bash두 프로세스 모두 SIGINT를 수신하기 때문입니다.sleep

-m실행하여 강제(작업 제어)를 수행 하면 bash -m shell.sh sleep 10+의 SIGINT는 전송 프로세스 중에 해석 쉘에 도달하지만 전송 프로세스 중에 인터프리터 쉘에는 도달하지 않습니다. 다른 프로세스 그룹에서 실행되기 때문입니다(확인할 수 있음). Bash에 내장된 함수입니다. 전경에 있으면 전경에도 있습니다. 전경에 있을 때는 그렇지 않습니다. SIGINT 도착 시 배송되지 않습니다. SIGINT가 도착하면 트랩은 쓸모가 없지만 SIGINT가 도착하면 트랩은 여전히 ​​유용합니다. 트랩이 아닌 경우 + 마침표가 전체를 중단합니다.Ctrlcreadsleep-m bashsleepps -ao pid,pgrp,cmdreadbashsleepbashsleepbashsleepbashreadCtrlcreadbash

다소 놀랍게도 우리의 것 bash -m(특히 내가 테스트한 Bash 5.2.15)하다sleepSIGINT에 의해 살해되면 종료됩니다. echo oops셸 자체가 SIGINT에 의해 종료된 것처럼 또는 셸이 일반적으로 수행하는 마지막 실행 명령의 종료 상태를 전달하는 것처럼 130의 종료 상태로 실행하지 않고 종료됩니다 sleep. SIGINT에 의해 살해당합니다.

이 행동은 실제로 의미가 있습니다. 이 기사에서는 이에 대해 설명합니다.SIGINT/SIGQUIT를 올바르게 처리. 지금은 읽을 필요가 없습니다. "쉘"을 작성하고 싶다면 언젠가는 읽어야 할 것 같습니다. 우리에게는 Bash 매뉴얼에서 발췌한 다음 내용이 관련되어 있습니다.

SIGINT로 인해 명령이 종료되면 Bash는 사용자가 전체 스크립트를 종료하려고 했다고 결론을 내리고 SIGINT에 대한 조치를 취합니다(예: SIGINT 트랩을 실행하거나 자체 종료).

(원천)

이는 "작업 제어가 활성화되지 않은 경우"입니다(더 넓은 스니펫이 관심을 가질 수 있음). 작업 제어가 활성화되면 어떤 일이 발생하는지에 대한 설명을 찾지 못했지만 우리가 관찰한 동작을 통해 bash -m"Bash는 사용자가 전체 스크립트를 종료하려고 한다고 결론을 내렸습니다."가 여전히 적용된다는 것을 추론했습니다. 내 설명은 다음과 같습니다. bashSIGINT를 받지 못한다 는 사실에도 불구하고 (현재 포그라운드 프로세스 그룹에 있지 않기 때문에) sleepSIGINT로 인해 종료된 것을 볼 수 있으므로 사용자가 전체 스크립트를 종료하려고 했다고 결론을 내립니다. 실제로 신호를 수신하지 않기 때문에 트랩을 실행하지 않습니다. 스스로 종료되지 않고( 로 확인했습니다. strace) 종료 상태만 전달합니다 sleep.

"쉘"을 작성할 때 쉘의 프로세스 그룹에서 명령을 실행할지 여부를 결정하십시오. SIGINT를 처리합니다. 그런 다음 "쉘"이 마지막 명령이 SIGINT에 의해 종료되었는지 아니면 방금 종료되었는지 감지하기를 원할 수도 있고 원하지 않을 수도 있습니다. 일반적으로 실제로 SIGINT를 처리(예: 무시하기보다는 잡기)하고 명령 실행 중에 발생한다는 것을 알아차리는 것은 명령이 종료된 후 수행할 작업을 결정하는 데 도움이 될 수 있습니다.

trap예, 실제 쉘은 사용자가 s를 명시적으로 정의하지 않은 경우에도 신호를 포착합니다. 예를 들어 Linux에서는 grep ^Sig /proc/"$$"/status다음 답변을 기반으로 SigCgt를 실행하고 해석할 수 있습니다.프로세스가 어떤 신호를 듣고 있는지 확인하는 방법은 무엇입니까?

지금은 읽을 시간이다링크된 기사"진짜 껍질"이 무엇을 다루는지 살펴보세요.


기타 참고사항

  • Ctrl"Send SIGINT in + c터미널 회선 규칙(보통)" 이라고 썼습니다 . 프로그램("쉘"에서 실행되는 프로그램 포함)이 이를 구성할 수 있기 때문에 "일반적으로"입니다(그러나모든 프로그램이 보편적인 것은 아닙니다). 예를 들어, ./shell.sh stty intr ^D이 작업을 한 번 이상 수행 하면 +가 SIGINT를 생성하고 읽히지 않기 stty intr ^D때문에 루프를 쉽게 종료할 수 없습니다 .Ctrldread^D

  • 셸에서는 bash"백그라운드에서 실행" 과 같은 것이 &명령 종료자와 연결되어 있지만 &프로세스가 포그라운드 프로세스 그룹에서 시작되지 않는다는 의미는 아닙니다. 포그라운드 프로세스 그룹, SIGTTIN, SIGTTOU 및 전반적인 작업 제어 개념은 제어 터미널에 대한 특정 프로세스 액세스를 거부하는 동시에 요청 시 프로세스를 허용(즉, 포그라운드로 이동)하는 방법입니다 fg. 작업 제어가 없으면 시작된 프로세스 &는 비동기식이며(셸은 프로세스가 완료될 때까지 기다리지 않음) 표준 입력이 /dev/null또는 일부 동등한 파일로 리디렉션되므로 터미널에서 "훔칠" 수 없습니다. 그런 의미에서 " in the background"; 그러나 포그라운드 프로세스 그룹에 있을 수도 있습니다. 예를 들어, bash -c 'sleep 500 & sleep 600'새 프로세스 와 두 개의 프로세스 는 단일 프로세스 그룹(상위 프로세스 설정 방법에 따라 상위 프로세스 그룹 또는 새 프로세스 그룹)에서 실행되고 bash해당 프로세스 그룹은 포그라운드 프로세스가 됩니다. 그룹.sleepbash

  • 이러한 사항을 테스트하기 위해 쉘 스크립트를 계속 사용하려면 주의해서 수행하십시오 set -e. SIGINT로 인해 스크립트가 종료되면 set -eSIGINT로 인해 종료되었다고 가정하기 쉬울 수 있습니다. 우리 스크립트의 경우 || echo oops.

관련 정보