Ctrl + C로 bash 스크립트를 중지 할 수 없습니다


42

날짜를 인쇄하고 원격 시스템에 핑하기위한 루프가있는 간단한 bash 스크립트를 작성했습니다.

#!/bin/bash
while true; do
    #     *** DATE: Thu Sep 17 10:17:50 CEST 2015  ***
    echo -e "\n*** DATE:" `date` " ***";
    echo "********************************************"
    ping -c5 $1;
done

터미널에서 실행하면을 (를) 중지 할 수 없습니다 Ctrl+C. ^C터미널로 전송하는 것처럼 보이지만 스크립트는 중지되지 않습니다.

MacAir:~ tomas$ ping-tester.bash www.google.com

*** DATE: Thu Sep 17 23:58:42 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C                                                          <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

*** DATE: Thu Sep 17 23:58:48 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms

몇 번 누르거나 얼마나 빨리하더라도 나는 그것을 멈출 수 없다.
직접 테스트하고 실현하십시오.

부작용으로으로 중지하고 Ctrl+Z멈췄습니다 kill %1.

여기서 정확히 무슨 일이 일어나고 ^C있습니까?

답변:


26

무슨 일을 모두한다는 것입니다 bashpingSIGINT 수신 ( bash존재 하지 상호 작용을 모두 ping하고 bash당신이에서 해당 스크립트를 실행 한 대화 형 쉘 터미널의 전경 프로세스 그룹으로 생성하고 설정 한 같은 프로세스 그룹에서 실행).

그러나 bash현재 실행중인 명령이 종료 된 후에 만 ​​해당 SIGINT를 비동기 적으로 처리합니다. bash현재 실행중인 명령이 SIGINT로 종료 된 경우에만 해당 SIGINT를 수신하면 종료됩니다 (즉, 종료 상태는 SIGINT에 의해 종료되었음을 나타냄).

$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere

bash, sh그리고 sleep내가 Ctrl-C를 누를 때 SIGINT를받을 수 있지만 sh출구는 일반적으로 0 종료 코드와, 그래서 bash우리는 "여기"를 참조 이유입니다는 SIGINT를 무시합니다.

ping적어도 iputils의 것은 그렇게 동작합니다. 중단되면 통계를 인쇄하고 핑 응답 여부에 따라 0 또는 1 종료 상태로 종료합니다. 따라서 ping실행 중에 Ctrl-C를 bash누르면 Ctrl-CSIGINT 처리기에서 눌렀 지만 ping정상적으로 종료 되므로 종료 bash되지 않습니다.

SIGINT에 특수 처리기가 없기 때문에 sleep 1해당 루프에 in을 추가하고 실행 Ctrl-C하는 동안 누르면 SIGINT로 사망하고 SIGINT로 사망했다고 보고하고 종료됩니다 (실제로 SIGINT로 자체 종료됩니다) 중단을 부모에게보고).sleepsleepbashbash

왜 그런 식으로 bash행동하는지에 대해서는 확실하지 않으며 행동이 항상 결정적이지는 않습니다. 방금 개발 메일 링리스트 에서 질문을했습니다bash ( 업데이트 : @Jilles는 이제 그의 대답에 이유를 정리했습니다 ).

내가 비슷하게 동작하는 다른 쉘은 ksh93입니다 ( @Jilles가 언급했듯이 FreeBSD도 마찬가지입니다sh ). SIGINT는 무시되는 것 같습니다. 그리고 ksh93SIGINT에 의해 명령이 종료 될 때마다 종료됩니다.

bash위와 같은 동작을 수행 하지만

ksh -c 'sh -c "kill -INT \$\$"; echo test'

"test"를 출력하지 않습니다. 즉, SIGINT를 기다리는 명령이 SIGINT를 수신하지 않더라도 SIGINT의 사망을 명령하면 종료됩니다 (SIGINT로 자체 종료).

해결 방법은 다음을 추가하는 것입니다.

trap 'exit 130' INT

스크립트 상단 bash에서 SIGINT를 수신하면 강제 종료됩니다 (어쨌든 현재 실행중인 명령이 종료 된 후에 만 ​​SIGINT가 동 기적으로 처리되지 않습니다).

이상적으로는 SIGINT로 사망했다고 부모에게보고하려고합니다 ( bash예를 들어 다른 스크립트 인 경우 해당 bash스크립트도 중단됨). 를 수행하는 exit 130것은 SIGINT의 사망과 동일하지 않지만 (일부 쉘은 $?두 경우 모두 동일한 값으로 설정 되지만) SIGINT (SIGINT가 2 인 시스템에서)에 의한 사망을보고하는 데 자주 사용됩니다.

그러나 대한 bash, ksh93또는 FreeBSD의 sh, 그 작동하지 않습니다. 130 개의 종료 상태는 SIGINT에 의해 사망으로 간주되지 않으며 상위 스크립트는 중단 되지 않습니다 .

따라서 SIGINT를 받으면 SIGINT로 우리 자신을 죽이는 것이 더 나은 대안 일 것입니다.

trap '
  trap - INT # restore default INT handler
  kill -s INT "$$"
' INT

1
질의 대답 은“왜”를 설명합니다. AS를 설명 예를 들어, 고려  for f in *.txt; do vi "$f"; cp "$f" newdir; done. 파일 중 하나를 편집하는 동안 사용자가 Ctrl + C를 입력 vi하면 메시지 만 표시됩니다. 사용자가 파일 편집을 마친 후에 루프가 계속되는 것이 합리적입니다. (그리고 네, 말할 수 있음을 알고 있습니다 . 예를 들어 루프를 vi *.txt; cp *.txt newdir제출하는 중 for입니다.)
Scott

@Scott, 좋은 지적. vi( vim적어도) isig편집 할 때 tty를 비활성화 하지만 ( 실행 할 때 분명히하지는 않으며 :!cmd그 경우에는 매우 많이 적용됩니다).
Stéphane Chazelas

@ 팀, 수정 사항을 수정하려면 내 수정 사항을 참조하십시오.
Stéphane Chazelas

@ StéphaneChazelas 감사합니다. pingSIGINT를받은 후 0으로 종료 되기 때문 입니다. bash 스크립트 sudo대신을 포함 할 때 비슷한 동작을 발견 ping했지만 sudoSIGINT를받은 후 1로 종료됩니다. unix.stackexchange.com/questions/479023/…
Tim

13

bash는 http://www.cons.org/cracauer/sigint.html에 따라 SIGINT 및 SIGQUIT에 대해 WCE (대기 및 협업 종료)를 구현한다고 설명 합니다. 즉, bash가 프로세스 종료를 기다리는 동안 SIGINT 또는 SIGQUIT를 수신하면 프로세스가 종료 될 때까지 대기하고 프로세스가 해당 신호에서 종료되면 자체 종료됩니다. 이렇게하면 사용자 인터페이스에서 SIGINT 또는 SIGQUIT를 사용하는 프로그램이 예상대로 작동합니다 (신호로 인해 프로그램이 종료되지 않으면 스크립트는 정상적으로 계속됩니다).

단점은 SIGINT 또는 SIGQUIT를 포착하지만 그로 인해 종료되지만 신호를 자신에게 다시 보내는 대신 정상적인 exit ()를 사용하는 프로그램에서 나타납니다. 이러한 프로그램을 호출하는 스크립트를 중단하지 못할 수 있습니다. 나는 ping과 ping6 같은 프로그램에 실제 수정이 있다고 생각합니다.

비슷한 동작은 ksh93과 FreeBSD의 / bin / sh에 의해 구현되지만 대부분의 다른 쉘에는 없습니다.


고마워요. 나는 FreeBSD의 SH 중 하나를 중단하지 않습니다 때 (mksh가 않는 아이의 SIGINT에 의해 죽음을보고하는 일반적인 방법입니다 중 하나 출구 (130)로 cmd를 종료 exit(130)예를 들어 당신이 중단 경우 mksh -c 'sleep 10;:').
Stéphane Chazelas

5

짐작할 때, 이는 SIGINT가 종속 프로세스로 전송되고 프로세스 종료 후 쉘이 계속 진행되기 때문입니다.

보다 나은 방법으로이를 처리하기 위해 실행중인 명령의 종료 상태를 확인할 수 있습니다. Unix 리턴 코드는 프로세스가 종료 된 방법 (시스템 호출 또는 신호)과 전달 된 값 또는 프로세스를 종료 한 신호를 모두 인코딩합니다 exit(). 이것은 다소 복잡하지만 그것을 사용하는 가장 빠른 방법은 신호로 종료 된 프로세스에 0이 아닌 리턴 코드가 있음을 아는 것입니다. 따라서 스크립트에서 리턴 코드를 확인하면 하위 프로세스가 종료 된 경우 자신을 종료 할 수 있으므로 불필요한 sleep호출 과 같은 부적합이 필요하지 않습니다 . 스크립트 전체에서이 작업을 수행하는 빠른 방법은을 사용하는 set -e것이지만 종료 상태가 0이 아닌 것으로 예상되는 명령에 대해서는 약간의 조정이 필요할 수 있습니다.


1
당신이 bash는-4 사용하지 않는 설정 -e 배쉬에서 제대로 작동하지 않습니다
schily

"정확하게 작동하지 않는다"는 것은 무엇입니까? 나는 그것을 bash 3에서 성공적으로 사용했지만 아마도 일부 경우가있을 것입니다.
Tom Hunt

몇 가지 간단한 경우에는 bash3이 오류로 종료되었습니다. 그러나 이것은 일반적인 경우에는 발생하지 않았습니다. 일반적인 결과로, 대상 작성에 실패했을 때 make가 중지되지 않았으며 이는 서브 디렉토리의 대상 목록에서 작동 한 makefile에서 작성된 것입니다. David Korn과 저는 bash 관리자에게 bash4의 버그를 수정하도록 설득하기 위해 몇 주 동안 우송해야했습니다.
schily

4
여기서 문제는 pingSIGINT를 수신하면 종료 상태가 0으로 돌아가고 bash그 경우 수신 된 SIGINT를 무시한다는 것입니다. "set -e"를 추가하거나 종료 상태를 확인하면 도움이되지 않습니다. SIGINT에 명시 적 트랩을 추가하면 도움이됩니다.
Stéphane Chazelas

4

터미널은 control-c를 확인하고 새로운 포 그라운드 프로세스 그룹을 생성하지 않은 INT것처럼 여기에 쉘을 포함하는 포 그라운드 프로세스 그룹으로 신호를 보냅니다 ping. 트래핑하여 쉽게 확인할 수 INT있습니다.

#! /bin/bash
trap 'echo oh, I am slain; exit' INT
while true; do
  ping -c5 127.0.0.1
done

실행중인 명령이 새 포 그라운드 프로세스 그룹을 작성한 경우 control-c는 쉘이 아닌 해당 프로세스 그룹으로 이동합니다. 이 경우 쉘은 터미널에서 신호를 보내지 않으므로 종료 코드를 검사해야합니다.

( INT껍질에 취급 쉘이 때로는 신호를 무시 필요로 부리, 그런데, 복잡하고, 때로는하지 소스 다이빙 호기심 경우, 또는 숙고 할 수 있습니다. tail -f /etc/passwd; echo foo)


이 경우 문제는 신호 처리가 아니라 스크립트에서 bash가 작업 제어를 수행한다는 사실은 문제가 아닙니다. 자세한 내용은 내 답변을 참조하십시오
schily

SIGINT가 새 프로세스 그룹으로 이동하려면이 명령은 터미널에 대한 ioctl ()을 수행하여 터미널의 포 그라운드 프로세스 그룹으로 만들어야합니다. ping여기에서 새 프로세스 그룹을 시작할 이유가 없으며 OP의 문제를 재현 할 수있는 핑 버전 (데비안의 iputils)은 프로세스 그룹을 만들지 않습니다.
Stéphane Chazelas

1
SIGINT를 전송하는 것은 터미널이 아니며, 이스케이프 처리되지 않은 (보통 ^ V) ^ C 문자를 수신 할 때 tty 장치 (/ dev / ttysomething 장치의 드라이버 (커널 코드))의 라인 규칙입니다. 터미널에서.
Stéphane Chazelas

2

글쎄, 나는 sleep 1bash 스크립트에 a를 추가하려고 시도했다 .
이제 두 가지로 멈출 수 있습니다 Ctrl+C.

를 누르면 현재 실행중인 프로세스 Ctrl+CSIGINT신호가 전송되며이 명령은 루프 내에서 실행되었습니다. 그런 다음 서브 쉘 프로세스는 루프에서 다음 명령을 계속 실행하여 다른 프로세스를 시작합니다. 스크립트를 중지하려면 두 개의 SIGINT신호 를 보내야 합니다. 하나는 실행중인 현재 명령을 중단하고 다른 하나는 서브 쉘 프로세스 를 중단합니다 .

sleep전화가 없는 스크립트에서는 Ctrl+C실제로 빠르게 누르고 여러 번 누르는 것이 작동하지 않는 것처럼 보이며 루프를 종료 할 수 없습니다. 내 생각에는 현재 실행중인 프로세스의 중단과 다음 프로세스의 시작 사이에 적절한 순간에 두 번 누르는 것만으로는 충분하지 않습니다. Ctrl+C누를 때마다 a SIGINT는 루프 내에서 실행되는 프로세스로 보내지 만 subshell 에는 보내지 않습니다 .

with 스크립트 sleep 1에서이 호출은 1 초 동안 실행을 일시 중단하고 첫 번째 Ctrl+C(first SIGINT)에 의해 중단 되면 서브 쉘 이 다음 명령을 실행하는 데 시간이 더 걸립니다. 이제 두 번째 Ctrl+C(second SIGINT)가 subshell 로 이동 하고 스크립트 실행이 종료됩니다.


올바르게 작동하는 쉘에서 하나의 ^ C이면 충분합니다. 배경에 대한 내 대답을 참조하십시오.
schily

글쎄, 당신이 투표권을 잃었고 현재 귀하의 답변 점수가 -1임을 고려할 때, 귀하의 답변을 진지하게 받아 들여야한다고 확신하지는 않습니다.
nephewtom

일부 사람들이 공감하는 것은 항상 회신의 품질과 관련이있는 것은 아닙니다. ^ c를 두 번 입력해야한다면 분명히 bash 버그의 희생자입니다. 다른 껍질을 시도 했습니까? 실제 Bourne Shell을 사용해 보셨습니까?
schily

쉘이 올바르게 작동하면 동일한 프로세스 그룹의 스크립트에서 모든 것을 실행 한 다음 단일 ^ c로 충분합니다.
schily

1
이 답변에서 @nephewtom이 설명하는 동작은 Ctrl-C를받을 때 다르게 동작하는 스크립트의 다른 명령으로 설명 할 수 있습니다. 수면이있는 경우 수면이 실행되는 동안 Ctrl-C가 수신 될 가능성이 압도적입니다 (루프의 다른 모든 것이 빠르다고 가정). 쉘은 수면의 부모가 sigint에 의해 수면이 종료되었음을 알리고 종료됩니다. 그러나 스크립트에 휴면 상태가 없으면 Ctrl-C는 대신 ping으로 이동하여 0으로 종료하여 반응하므로 상위 쉘은 다음 명령을 계속 실행합니다.
Jonathan Hartley

0

이 시도:

#!/bin/bash
while true; do
   echo "Ctrl-c works during sleep 5"
   sleep 5
   echo "But not during ping -c 5"
   ping -c 5 127.0.0.1
done

이제 첫 번째 줄을 다음으로 변경하십시오.

#!/bin/sh

다시 시도하십시오-핑이 중단 가능한지 확인하십시오.


0
pgrep -f process_name > any_file_name
sed -i 's/^/kill /' any_file_name
chmod 777 any_file_name
./any_file_name

예를 들어 pgrep -f firefox, 실행중인 PID를 grep firefox하고이 PID를이라는 파일에 저장합니다 any_file_name. 'sed'명령은 kill'any_file_name'파일의 PID 번호 시작 부분에를 추가 합니다. 세 번째 줄은 any_file_name실행 파일을 제출합니다. 이제 네 번째 줄은 파일에서 사용 가능한 PID를 종료합니다 any_file_name. 파일에 위의 네 줄을 쓰고 해당 파일을 실행하면 Control-를 수행 할 수 있습니다 C. 나를 위해 절대적으로 잘 작동합니다.


0

bash기능 에 대한 수정에 관심이 있고 그 뒤에 있는 철학에 관심이 없다면 다음 과 같은 제안이 있습니다.

직접 문제 명령을 실행하지만,이)가 B를 종료하기 위해) 대기 않습니다) 신호 및 C와 혼란을하지 않는 래퍼에서 마십시오 하지 단순히 WCE 메커니즘 자체를 구현하지만, 수신시 죽는다 SIGINT.

이러한 래퍼는 awk+ system()기능 으로 만들 수 있습니다 .

$ while true; do awk 'BEGIN{system("ping -c5 localhost")}'; done
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.082 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.087 ms
^C
--- localhost ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1022ms
rtt min/avg/max/mdev = 0.082/0.084/0.087/0.009 ms
[3]-  Terminated              ping -c5 localhost

OP와 같은 스크립트를 넣으십시오.

#!/bin/bash
while true; do
        echo -e "\n*** DATE:" `date` " ***";
        echo "********************************************"
        awk 'BEGIN{system(ARGV[1])}' "ping -c5 ${1-localhost}"
done

-3

당신은 잘 알려진 bash 버그의 희생자입니다. Bash는 실수 인 스크립트에 대한 작업 제어를 수행합니다.

bash는 외부 프로그램을 스크립트 자체와 다른 프로세스 그룹에서 실행합니다. TTY 프로세스 그룹이 현재 포 그라운드 프로세스의 프로세스 그룹으로 설정되면이 포 그라운드 프로세스 만 종료되고 쉘 스크립트의 루프는 계속됩니다.

확인하려면 : 내장 프로그램으로 pgrp (1)을 구현하는 최근 Bourne Shell을 가져와 컴파일 한 다음 / bin / sleep 100 (또는 플랫폼에 따라 / usr / bin / sleep)을 스크립트 루프에 추가 한 다음 본 쉘. ps (1)을 사용하여 sleep 명령 및 스크립트를 실행하는 bash의 프로세스 ID를 얻은 후 pgrp <pid>"<pid>"를 호출하여 sleep의 프로세스 ID 및 스크립트를 실행하는 bash로 바꾸십시오. 다른 프로세스 그룹 ID가 표시됩니다. 이제 pgrp < /dev/pts/7현재 tty 프로세스 그룹을 얻기 위해 (스크립트에서 사용하는 tty로 tty 이름을 바꾸십시오) 와 같은 것을 호출 하십시오. TTY 프로세스 그룹은 sleep 명령의 프로세스 그룹과 같습니다.

수정하려면 : 다른 쉘을 사용하십시오.

최근 Bourne Shell 소스는 내 schily 도구 패키지에 있으며 여기에서 찾을 수 있습니다.

http://sourceforge.net/projects/schilytools/files/


어떤 버전입니까 bash? AFAIK bash는 -m 또는 -i 옵션을 전달한 경우에만 해당됩니다.
Stéphane Chazelas

이 더 이상 bash4에 적용 않습니다하지만 영업 이익은 이러한 문제가있을 때, 그는 bash3 사용 보인다 보인다
schily

bash3.2.48 또는 bash 3.0.16 또는 bash-2.05b (로 시도 bash -c 'ps -j; ps -j; ps -j') 로는 재생할 수 없습니다 .
Stéphane Chazelas

이것은 bash를로 호출 할 때 확실히 발생합니다 /bin/sh -ce. 계층화 된 make 호출을 중단 smake하기 위해 현재 실행중인 명령의 프로세스 그룹을 명시 적으로 종료 시키는 추악한 해결 방법을 추가해야했습니다 ^C. bash가 프로세스 그룹을 시작한 프로세스 그룹 ID에서 프로세스 그룹을 변경했는지 확인 했습니까?
schily

ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'모든 3 ps 호출에서 ps 및 bash에 대해 동일한 pgid를보고합니다. (ARGV0 = sh는 zshargv [0]을 전달하는 방법입니다).
Stéphane Chazelas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.