Shell Script배우고 싶어질 줄이야...

apriljade·2022년 9월 5일
0

쉘 스크립트

목록 보기
1/1

쉘 스크립트...?

쉘 스크립트는 쉽게 생각하면 코드인데, 쉘에서 작동하는 코드입니다. 쉘에는 zsh, bash, powershell, cmd... 등등 많은 종류가 있는데, 저는 우분투에서 사용하는 zsh에서 쉘 스크립트를 작성하고 싶어졌습니다. daemon프로세스의 실행과 종료를 시키고 싶었는데, 프로그램을 새로하나 만들자니 너무 무겁고 간단하게 스크립트로 적당히 구현해보자하는 마음이었습니다.

필요해?

아직까지 판단하기로는 "필요한가?"라는 질문에는 즉답으로 "아니오."가 나옵니다. 분명 유용하긴 한데 반드시 알아야한다까지는 모르겠습니다. 어차피 쉘에서 이것저것 만지다보면 자연스럽게 익히게됩니다. (피눈물을 흘리며 말이죠) 저도 간단한 명령어만 사용하다가 (grep이라던지..) 이번에 스크립트를 작성할 일이 생겨 구글의 지대한 도움하에 약간의 스크립트를 작성했습니다.

경량화 쉘...

스크립트를 작성하면서 배열을 활용해야지~ 하며 배열문법을 찾아봤는데 안되더라구요.(?)

array=(elem0 elem1 elem2)

이런 방식의 문법인데 저는 안되길래 (wsl2의 ubuntu 20.04의 zsh 쉘) 왜 그런가 봤더니 경량화 쉘이면 안된다네요...

필요하게된 계기? 상황

저는 한 가지 프로젝트를 진행 중인데, 그 프로젝트에서 만든 프로그램은 daemon으로 동작하고, 그 daemon프로그램을 실행시키거나 종료시키는 등의 동작을 쉘 스크립트로 만들어야 했습니다. 일단 실행시키는 것은 간단하니 넘어갔고... 종료시키는 것이 문제였는데, 종료 시키기 위해서는 kill명령어로 특정 시그널을 보내어 처리해야합니다. 즉, 종료시키고자하는 프로세스의 PID를 알아내어 명령어를 실행시켜야하죠.

PID 추출하여 종료시키기

pid는 ps 명령어에 적당한 옵션을 주어 찾아낼 수 있습니다. 하지만 ps명령어를 이용하면 모든 프로세스에 대한 정보가 나와버리기 때문에 이 정보들을 적당히 가공하여 원하는 정보만 뽑아내어야 합니다.
일단 우리는 종료시켜야할 프로세스의 이름은 알고있으므로 grep 명령어를 이용해 대상 프로세스의 정보가 나와있는 라인만 뽑아내봅시다.

ps -aux | grep [process name]

전 agent라는 이름의 프로그램을 찾아야하며, daemon을 기동시키는 스크립트 상에서 실행하는 프로세스의 이름은 ./bin/agent라 해당 문자로 찾아보면

ps -aux | grep ./bin/agent
aprilja+  4845  0.0  0.0   8172   720 pts/5    S+   02:20   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox ./bin/agent

1개만 나온 이유는 실행 중인 agent가 없기 때문에 grep 프로세스에 대한 정보만 나온 것 입니다.

sh run-agent.sh
SMS: Start SMS agent.
ps -aux | grep ./bin/agent
aprilja+  4888  0.0  0.0 445228  1632 ?        Ssl  02:21   0:00 ./bin/agent ./agent.conf
aprilja+  4917  0.0  0.0   8172   652 pts/5    S+   02:21   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox ./bin/agent

실행 후 같은 명령어를 입력해보면 2줄이 나오는 것을 알 수 있습니다.
여기서 우리는 항상 첫번째 줄의 정보만 필요하기 때문에 head명령어를 조합해서 맨 윗줄만 뽑아냅니다.

ps -aux | grep ./bin/agent | head -1

결과값의 2번째 열이 우리가 필요로하는 PID이므로 저친구를 뽑아내기 위해 cut명령어를 써보겠습니다. (awk나 sed 명령어도 있습니다)

ps -aux | grep ./bin/agent | head -1 | cut -d ' ' -f 2

cut 명령어의 -f 옵션 뒤의 숫자가 열 번호(필드 번호)인데 2로 했을 때 값이 나오기도, 3으로 했을 떄 나오기도, 4로했을 때 나오기도합니다. 이는 -d 옵션 뒤 ' '으로 문자열을 짤랐을 때, 공백이 여러개 있을 때 생각처럼 잘라주지 않아서 그렇습니다.
아무튼 그래서 저 cut명령어가 잘 되도록 문자열을 한번 손질해줘야합니다.

ps -aux | grep ./bin/agent | head -1 | tr -s ' ' | cut -d ' ' -f 2

이렇게 해주면 ./bin/agent라는 이름의 프로세스 정보를 리스트하고, 거기서 맨 윗줄을 가져와, 그 줄의 단어들을 공백 하나로 구분짓개하고, 그 문자들을 공백을 기준으로 split하여 두번째 단어를 가져옵니다! (...)
우리의 스크립트에서는 이 결과 값을 변수로 담아두고 kill명령어의 파라미터로 입력하면 됩니다.

PID=$(command ps -aux | grep ./bin/agent | head -1 | tr -s ' ' | cut -d ' ' -f 2)
kill -15 $PID	

이렇게 스크립트로 작성해두고 실행만 시켜주면 간단한 daemon 종료 스크립트 완성입니다...!

kill 명령어로 종료시킬 때는 시그널에 대한 별다른 구현을 하지 않으셨으면 9(SIGKILL)나 11(SIGSEGV)을 보내는게 좋습니다! 저는 15(SIGTERM)에 대한 처리 로직이 따로있어서 15를 보냈습니다! 시그널에 대한 자세한 설명은 요기로!!

종료되는 것을 확인하며 기다리기...

위의 과정까지는 원래 할 수 있던 것들이라 크게 어렵지 않았습니다. (42서울 덕분인가...?)
제 agent는 종료되는데 시간이 소요되기 때문에 그 시간동안 기다렸다가 종료 안내를 해주고 싶었습니다. 그러면서 기다리는 동안 종료 중...이라는 메시지를 출력하며 .의 개수를 바꾸기로합니다!

종료의 기준

종료의 기준은 ps -aux | grep ./bin/agent | wc -l이 2면 실행 중인 것이고 1이면 종료된 것으로 판단했습니다. 이 값을 변수에 담아두고 while 루프를 돌면서 체크해주면 됩니다. 그러다가 종료되었으면 안내 문구를 출력해주면 끝이죠!

echo SMS: Terminating agent...
while :
do
    LINE_CNT=$(command ps -aux | grep ./bin/agent | wc -l)
    if [ ${LINE_CNT} -eq 1 ]
    then
        break
    fi
    sleep 1
done
echo SMS: Agent is terminated successfully.

점 찍기

제 프로그램이 실행 옵션에 따라 종료되는데 오래걸리는 경우가 있습니다. 종료시키는데 5초가량이 걸리기도합니다. (스레드가 종료되길 기다리는 것인데 오래걸림...개선중!) 그래서 종료를 기다리는 동안 쉘이 멈추지 않았다는 것을 알리기 위한 장치를 마련합니다.
1초에 한번씩 "종료 중." "종료중..", "종료중..." ... 이 반복되게 해봅시다.
변수와 반복문과 if문을 적절히 사용하면됩니다.

LINE_CNT=$(command ps -aux | grep ./bin/agent | wc -l)
VALUE=0
MAX_POINT_CNT=1

if [ ${LINE_CNT} -eq 1 ]
then
    echo SMS: Agent is not working.
else
    PID=$(command ps -aux | grep ./bin/agent | head -1 | tr -s ' ' | cut -d ' ' -f 2)
    kill -15 $PID
    while :
    do
        LINE_CNT=$(command ps -aux | grep ./bin/agent | wc -l)
        if [ ${LINE_CNT} -eq 1 ]
        then
            break
        fi
        printf "\033[1A"
        printf "\033[K"  
        printf "SMS: Terminating agent"
        while :
        do
            if  [ ${VALUE} -eq ${MAX_POINT_CNT} ]
            then
                break
            else
                VALUE=$((VALUE+1))  
                printf "."
            fi
        done
        VALUE=0
        MAX_POINT_CNT=$((MAX_POINT_CNT%5))
        MAX_POINT_CNT=$((MAX_POINT_CNT+1))
        printf "\n"
        sleep 1
    done
    echo SMS: Terminate SMS agent successfully.
fi

생각보다 유용하다

원래 저는 쉘 스크립트는 쳐다도 안봤습니다. 필요성을 못느껴서 그렇겠죠. 42서울에서 쉘 과제할 때도 아주 죽을 맛이었습니다. 그래도 막상 써보고 이렇게 활용 또한 하다보니 나름 잘배웠다 싶기도 하고 더 공부해볼까 싶은 마음도 있습니다. 실은 sed 명령어를 좋아하는데 regix expression이 익숙치 않아 잘 못쓰고 있는데, 공부하는게 좋겠다 싶기도 합니다.

어차피 사실 이 프로젝트가 고도화되면 될 수록 쉘 스크립트는 필요없어질 예정이긴합니다. agent를 관리해주는 setinel를 구현할 예정이기 때문인데, 이 sentinel에서 agent의 상태를 조회하거나 실행시키거나 종료시키거나 복구시키는 등의 작업이 가능하도록 할 예정입니다. 그래도 그전까지는 아주 유용하게 쓰일 것 같아 기쁘네요 ㅎㅎ

0개의 댓글