웹사이트 검색

Bash 스크립트에서 Linux 신호를 사용하는 방법


Linux 커널은 반응해야 하는 이벤트에 대한 신호를 프로세스에 보냅니다. 제대로 작동하는 스크립트는 신호를 우아하고 견고하게 처리하며 Ctrl+C를 눌러도 스스로 정리할 수 있습니다. 방법은 다음과 같습니다.

신호 및 프로세스

신호는 스크립트, 프로그램 및 데몬과 같은 프로세스로 전송되는 짧고 빠른 단방향 메시지입니다. 그들은 발생한 일에 대해 프로세스에 알립니다. 사용자가 Ctrl+C를 누르거나 응용 프로그램이 액세스할 수 없는 메모리에 쓰려고 시도했을 수 있습니다.

프로세스 작성자가 특정 신호가 전송될 수 있다고 예상한 경우 해당 신호를 처리하기 위해 프로그램이나 스크립트에 루틴을 작성할 수 있습니다. 이러한 루틴을 신호 처리기라고 합니다. 신호를 잡거나 트랩하고 이에 대한 응답으로 몇 가지 작업을 수행합니다.

앞으로 보게 되겠지만 Linux는 많은 신호를 사용하지만 스크립팅 관점에서 볼 때 사용자가 관심을 가질만한 신호의 작은 하위 집합만 있습니다. 특히 사소하지 않은 스크립트에서 종료할 스크립트를 트랩하고(가능한 경우) 정상적인 종료를 수행해야 합니다.

예를 들어 임시 파일을 만들거나 방화벽 포트를 여는 스크립트는 임시 파일을 삭제하거나 포트가 종료되기 전에 닫을 수 있는 기회를 제공받을 수 있습니다. 스크립트가 신호를 받는 즉시 죽으면 컴퓨터가 예측할 수 없는 상태에 놓일 수 있습니다.

자신의 스크립트에서 신호를 처리하는 방법은 다음과 같습니다.

신호를 만나다

일부 Linux 명령에는 암호 이름이 있습니다. 신호를 가두는 명령은 그렇지 않습니다. 트랩이라고 합니다. -l(목록) 옵션과 함께 trap을 사용하여 Linux에서 사용하는 전체 신호 목록을 표시할 수도 있습니다.

trap -l

번호가 매겨진 목록은 64개로 끝나지만 실제로는 62개의 신호가 있습니다. 신호 32와 33이 누락되었습니다. Linux에서는 구현되지 않습니다. 실시간 스레드를 처리하기 위해 gcc 컴파일러의 기능으로 대체되었습니다. 신호 34, SIGRTMIN에서 신호 64, SIGRTMAX까지 모든 것이 실시간 신호입니다.

다른 유닉스 계열 운영 체제에서 다른 목록을 볼 수 있습니다. 예를 들어 OpenIndiana에서는 신호 32와 33이 존재하며 총 수를 73으로 만드는 여러 추가 신호가 있습니다.

신호는 이름, 번호 또는 단축 이름으로 참조할 수 있습니다. 단축 이름은 단순히 SIG가 제거된 이름입니다.

여러 가지 이유로 신호가 발생합니다. 당신이 그것들을 해독할 수 있다면, 그것들의 목적은 그것들의 이름에 포함되어 있습니다. 신호의 영향은 다음 몇 가지 범주 중 하나에 속합니다.

  • 종료: 프로세스가 종료됩니다.
  • 무시: 신호가 프로세스에 영향을 미치지 않습니다. 이것은 정보 전용 신호입니다.
  • 코어: 덤프 코어 파일이 생성됩니다. 이것은 일반적으로 프로세스가 메모리 위반과 같은 어떤 방식으로 위반했기 때문에 수행됩니다.
  • 중지: 프로세스가 중지됩니다. 즉, 종료되지 않고 일시중지됩니다.
  • 계속: 실행을 계속하도록 중지된 프로세스에 지시합니다.

가장 자주 접하게 될 신호입니다.

  • SIGHUP: 신호 1. SSH 서버와 같은 원격 호스트에 대한 연결이 예기치 않게 끊어졌거나 사용자가 로그아웃했습니다. 이 신호를 받는 스크립트는 정상적으로 종료되거나 원격 호스트에 다시 연결을 시도하도록 선택할 수 있습니다.
  • SIGINT: 신호 2. 사용자가 Ctrl+C 조합을 눌러 프로세스를 강제로 닫았거나 kill 명령이 신호 2와 함께 사용되었습니다. 기술적으로 , 이것은 종료 신호가 아닌 인터럽트 신호이지만 신호 처리기가 없는 인터럽트된 스크립트는 일반적으로 종료됩니다.
  • SIGQUIT: 신호 3. 사용자가 Ctrl+D 조합을 눌러 프로세스를 강제로 종료했거나 kill 명령이 신호 3과 함께 사용되었습니다.< /리>
  • SIGFPE: 신호 8. 프로세스가 0으로 나누기와 같은 잘못된(불가능한) 수학 연산을 수행하려고 시도했습니다.
  • SIGKILL: 신호 9. 단두대에 해당하는 신호입니다. 당신은 그것을 잡거나 무시할 수 없으며 즉시 발생합니다. 프로세스가 즉시 종료됩니다.
  • SIGTERM: 신호 15. 이것은 SIGKILL의 보다 사려 깊은 버전입니다. SIGTERM은 또한 프로세스에 종료하라고 지시하지만 프로세스가 종료될 수 있으며 프로세스가 종료되기 전에 정리 프로세스를 실행할 수 있습니다. 이를 통해 정상적인 종료가 가능합니다. 이것은 kill 명령에 의해 제기되는 기본 신호입니다.

명령줄의 신호

신호를 트랩하는 한 가지 방법은 신호의 번호 또는 이름 및 신호가 수신된 경우 발생하려는 응답과 함께 trap을 사용하는 것입니다. 터미널 창에서 이를 시연할 수 있습니다.

이 명령은 SIGINT 신호를 트랩합니다. 응답은 터미널 창에 한 줄의 텍스트를 인쇄하는 것입니다. \n 형식 지정자를 사용할 수 있도록 echo와 함께 -e(이스케이프 활성화) 옵션을 사용하고 있습니다.

trap 'echo -e "\nCtrl+c Detected."' SIGINT

Ctrl+C 조합을 누를 때마다 텍스트 줄이 인쇄됩니다.

신호에 트랩이 설정되어 있는지 확인하려면 -p(트랩 인쇄) 옵션을 사용하십시오.

trap -p SIGINT

옵션 없이 trap을 사용하면 동일한 작업이 수행됩니다.

신호를 트랩되지 않은 정상 상태로 재설정하려면 하이픈 -와 트랩된 신호의 이름을 사용하십시오.

trap - SIGINT
trap -p SIGINT

trap -p 명령의 출력이 없으면 해당 신호에 설정된 트랩이 없음을 나타냅니다.

스크립트에서 신호 트래핑

스크립트 내에서 동일한 일반 형식 trap 명령을 사용할 수 있습니다. 이 스크립트는 SIGINT, SIGQUITSIGTERM의 세 가지 신호를 트랩합니다.

#!/bin/bash

trap "echo I was SIGINT terminated; exit" SIGINT
trap "echo I was SIGQUIT terminated; exit" SIGQUIT
trap "echo I was SIGTERM terminated; exit" SIGTERM

echo $$
counter=0

while true
do 
  echo "Loop number:" $((++counter))
  sleep 1
done

세 개의 trap 문은 스크립트의 맨 위에 있습니다. 각 신호에 대한 응답 안에 exit 명령이 포함되어 있습니다. 이는 스크립트가 신호에 반응한 다음 종료됨을 의미합니다.

텍스트를 편집기에 복사하고 simple-loop.sh라는 파일에 저장하고 chmod 명령을 사용하여 실행 가능하게 만듭니다. 자신의 컴퓨터에서 따라하고 싶다면 이 문서의 모든 스크립트에 이 작업을 수행해야 합니다. 각각의 경우에 적절한 스크립트의 이름을 사용하십시오.

chmod +x simple-loop.sh

나머지 스크립트는 매우 간단합니다. 우리는 스크립트의 프로세스 ID를 알아야 하므로 스크립트가 우리에게 알려주도록 합니다. $$ 변수는 스크립트의 프로세스 ID를 보유합니다.

counter라는 변수를 만들고 0으로 설정합니다.

while 루프는 강제로 중지하지 않는 한 영원히 실행됩니다. counter 변수를 증가시키고 화면에 반영한 다음 잠시 대기합니다.

스크립트를 실행하고 다른 신호를 보냅니다.

./simple-loop.sh

Ctrl+C를 누르면 메시지가 터미널 창에 출력되고 스크립트가 종료됩니다.

다시 실행하고 kill 명령을 사용하여 SIGQUIT 신호를 보냅니다. 다른 터미널 창에서 이 작업을 수행해야 합니다. 자신의 스크립트에서 보고한 프로세스 ID를 사용해야 합니다.

./simple-loop.sh
kill -SIGQUIT 4575

예상대로 스크립트는 도착한 신호를 보고한 다음 종료합니다. 그리고 마지막으로 요점을 증명하기 위해 SIGTERM 신호로 다시 해보겠습니다.

./simple-loop.sh
kill -SIGTERM 4584

스크립트에서 여러 신호를 포착하고 각 신호에 독립적으로 반응할 수 있음을 확인했습니다. 이 모든 것을 흥미로운 것에서 유용한 것으로 승격시키는 단계는 신호 처리기를 추가하는 것입니다.

스크립트에서 신호 처리

응답 문자열을 스크립트의 함수 이름으로 바꿀 수 있습니다. trap 명령은 신호가 감지되면 해당 함수를 호출합니다.

이 텍스트를 편집기에 복사하고 “grace.sh”라는 파일로 저장하고 chmod로 실행 가능하게 만듭니다.

#!/bin/bash

trap graceful_shutdown SIGINT SIGQUIT SIGTERM

graceful_shutdown()
{
  echo -e "\nRemoving temporary file:" $temp_file
  rm -rf "$temp_file"
  exit
}

temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX)
echo "Created temp file:" $temp_file

counter=0

while true
do 
  echo "Loop number:" $((++counter))
  sleep 1
done

스크립트는 단일 trap 문을 사용하여 SIGHUP, SIGINTSIGTERM의 세 가지 신호에 대한 트랩을 설정합니다. . 응답은 graceful_shutdown() 함수의 이름입니다. 세 개의 트랩된 신호 중 하나가 수신될 때마다 함수가 호출됩니다.

스크립트는 mktemp를 사용하여 /tmp 디렉토리에 임시 파일을 생성합니다. 파일 이름 템플릿은 tmp.XXXXXXXXXX이므로 파일 이름은 tmp가 됩니다. 10개의 임의의 영숫자 문자가 뒤따릅니다. 파일 이름이 화면에 표시됩니다.

나머지 스크립트는 counter 변수와 무한 while 루프가 있는 이전 스크립트와 동일합니다.

./grace.sh

파일이 닫히도록 하는 신호를 받으면 graceful_shutdown() 함수가 호출됩니다. 이렇게 하면 단일 임시 파일이 삭제됩니다. 실제 상황에서는 스크립트에 필요한 모든 정리 작업을 수행할 수 있습니다.

또한 트랩된 모든 신호를 함께 묶어 단일 함수로 처리했습니다. 신호를 개별적으로 트래핑하고 전용 핸들러 함수로 보낼 수 있습니다.

이 텍스트를 복사하여 triple.sh라는 파일에 저장하고 chmod 명령을 사용하여 실행 가능하게 만듭니다.

#!/bin/bash

trap sigint_handler SIGINT
trap sigusr1_handler SIGUSR1
trap exit_handler EXIT

function sigint_handler() {
  ((++sigint_count))

  echo -e "\nSIGINT received $sigint_count time(s)."

  if [[ "$sigint_count" -eq 3 ]]; then
    echo "Starting close-down."
    loop_flag=1
  fi
}

function sigusr1_handler() {
  echo "SIGUSR1 sent and received $((++sigusr1_count)) time(s)."
}

function exit_handler() { 
  echo "Exit handler: Script is closing down..."
}

echo $$
sigusr1_count=0
sigint_count=0
loop_flag=0

while [[ $loop_flag -eq 0 ]]; do
  kill -SIGUSR1 $$
  sleep 1
done

스크립트 상단에 3개의 트랩을 정의합니다.

  • 하나는 SIGINT를 트랩하고 sigint_handler()라는 핸들러가 있습니다.
  • 두 번째는 SIGUSR1이라는 신호를 트랩하고 sigusr1_handler()라는 핸들러를 사용합니다.
  • 트랩 3번은 EXIT 신호를 트랩합니다. 이 신호는 스크립트가 닫힐 때 스크립트 자체에서 발생합니다. EXIT에 대한 신호 처리기를 설정하면 스크립트가 종료될 때 항상 호출되는 함수를 설정할 수 있습니다(SIGKILL 신호로 종료되지 않는 한). 핸들러는 exit_handler() 입니다.

SIGUSR1SIGUSR2는 사용자 지정 신호를 스크립트에 보낼 수 있도록 제공되는 신호입니다. 해석하고 반응하는 방법은 전적으로 귀하에게 달려 있습니다.

지금은 신호 처리기를 제쳐두고 스크립트 본문이 익숙할 것입니다. 프로세스 ID를 터미널 창에 표시하고 일부 변수를 생성합니다. 변수 sigusr1_countSIGUSR1이 처리된 횟수를 기록하고 sigint_countSIGINT가 처리된 횟수를 기록합니다. loop_flag 변수는 0으로 설정됩니다.

while 루프는 무한 루프가 아닙니다. loop_flag 변수가 0이 아닌 값으로 설정되면 루핑이 중지됩니다. while 루프의 각 회전은 kill을 사용하여 SIGUSR1 신호를 스크립트의 프로세스 ID로 전송함으로써 이 스크립트에 신호를 보냅니다. 스크립트는 자신에게 신호를 보낼 수 있습니다!

sigusr1_handler() 함수는 sigusr1_count 변수를 증가시키고 터미널 창에 메시지를 보냅니다.

SIGINT 신호가 수신될 때마다 siguint_handler() 함수는 sigint_count 변수를 증가시키고 해당 값을 터미널 창에 표시합니다.

sigint_count 변수가 3이면 loop_flag 변수는 1로 설정되고 종료 프로세스가 시작되었음을 사용자에게 알리는 메시지가 터미널 창으로 전송됩니다.

loop_flag가 더 이상 0이 아니므로 while 루프가 종료되고 스크립트가 완료됩니다. 하지만 이 작업은 EXIT 신호를 자동으로 발생시키고 exit_handler() 함수가 호출됩니다.

./triple.sh

Ctrl+C를 세 번 누르면 스크립트가 종료되고 exit_handler() 함수가 자동으로 호출됩니다.

신호 읽기

신호를 트래핑하고 간단한 핸들러 함수로 처리함으로써 예기치 않게 종료되더라도 Bash 스크립트를 깔끔하게 정리할 수 있습니다. 그것은 당신에게 깨끗한 파일 시스템을 제공합니다. 또한 다음에 스크립트를 실행할 때 불안정성을 방지하고 스크립트의 목적에 따라 보안 허점을 방지할 수도 있습니다.