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

게시 됨: 2022-08-09
bash 프롬프트를 표시하는 Linux 노트북
Fatmawati achmad zaenuri/Shutterstock.com

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

신호 및 프로세스

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

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

Linux 터미널에서 프로세스를 관리하는 방법: 알아야 할 10가지 명령
관련 Linux 터미널에서 프로세스를 관리하는 방법: 알아야 할 10가지 명령

리눅스는 우리가 보게 되겠지만 많은 신호를 사용하지만 스크립팅의 관점에서 보면 당신이 관심을 가질 만한 신호의 작은 부분집합만 있습니다. 특히, 중요하지 않은 스크립트에서 종료할 스크립트는 트랩되어야 하고(가능한 경우) 정상적인 종료가 수행되어야 합니다.

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

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

신호를 만나다

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

 트랩 -l 

trap -l을 사용하여 Ubuntu의 신호 나열

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

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

trap -l을 사용하여 OpenIndiana의 신호 나열

신호는 이름, 번호 또는 단축 이름으로 참조할 수 있습니다. 그들의 단축 이름은 단순히 앞의 "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 (이스케이프 활성화) 옵션을 사용하고 있습니다.

 트랩 'echo -e "\nCtrl+c가 감지되었습니다."' SIGINT 

명령줄에서 Ctrl+C 트래핑

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

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

 트랩 -p SIGINT 

신호에 트랩이 설정되어 있는지 확인

옵션이 없는 trap 을 사용하는 것도 동일한 작업을 수행합니다.

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

 트랩 - SIGINT
 트랩 -p SIGINT 

신호에서 트랩 제거

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

스크립트에서 신호 트래핑

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

 #!/bin/bash

트랩 "에코 I was SIGINT 종료됨; 종료" SIGINT
트랩 "에코 SIGQUIT 종료됨; 종료" SIGQUIT
트랩 "에코 I was SIGTERM 종료됨, 종료" SIGTERM

에코 $$
카운터=0

사실 동안
하다 
  echo "루프 번호:" $((++counter))
  수면 1
완료

세 개의 trap 문은 스크립트 상단에 있습니다. 각 신호에 대한 응답 안에 exit 명령을 포함시켰습니다. 이것은 스크립트가 신호에 반응한 다음 종료됨을 의미합니다.

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

 chmod +x 단순 루프.sh 

chmod로 스크립트를 실행 가능하게 만들기

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

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

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

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

 ./단순 루프.sh 

이를 식별하는 스크립트가 Ctrl+C로 종료되었습니다.

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

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

 ./단순 루프.sh
 죽이기 -SIGQUIT 4575 

이를 식별하는 스크립트가 SIGQUIT으로 종료되었습니다.

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

 ./단순 루프.sh
 죽이기 -SIGTERM 4584 

이를 식별하는 스크립트가 SIGTERM으로 종료되었습니다.

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

스크립트에서 신호 처리

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

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

 #!/bin/bash

트랩 graceful_shutdown SIGINT SIGQUIT SIGTERM

우아한_종료()
{
  echo -e "\n임시 파일 제거:" $temp_file
  rm -rf "$temp_file"
  출구
}

temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX)
echo "생성된 임시 파일:" $temp_file

카운터=0

사실 동안
하다 
  echo "루프 번호:" $((++counter))
  수면 1
완료

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

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

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

 ./은혜.sh 

임시 파일을 삭제하여 정상 종료를 수행하는 스크립트

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

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

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

 #!/bin/bash

트랩 signint_handler SIGINT
트랩 sigusr1_handler SIGUSR1
트랩 exit_handler EXIT

함수 서명_핸들러() {
  ((++signint_count))

  echo -e "\nSIGINT가 $sigint_count 시간을 받았습니다."

  if [[ "$sigint_count" -eq 3 ]]; 그 다음에
    echo "닫기 시작합니다."
    loop_flag=1
  파이
}

함수 sigusr1_handler() {
  echo "SIGUSR1이 $((++sigusr1_count))번을 보내고 받았습니다."
}

함수 exit_handler() { 
  echo "종료 핸들러: 스크립트가 종료되고 있습니다..."
}

에코 $$
sigusr1_count=0
서명_카운트=0
loop_flag=0

동안 [[ $loop_flag -eq 0 ]]; 하다
  죽이기 -SIGUSR1 $$
  수면 1
완료

스크립트 상단에 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 신호를 포착해야 하는 SIGUSR1을 사용하는 스크립트

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

신호 읽기

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

관련: Lynis를 사용하여 Linux 시스템의 보안을 감사하는 방법