업데이트:

4 분 소요

개요

일전에 shell script의 alias에 대한 글을 여기에 작성했었다.
하지만 이렇게 짠 명령어들은 2% 부족한 기능을 제공했었고 이를 개선시켰다.

타임라인

필자는 알다시피 jekyll 을 이용한 github blog를 이용하는데, 포스트를 올리기 전에 아래와 같은 코드를 통해 로컬호스트로 테스트 서버를 열고, 그곳에서 어떻게 렌더링되는지 확인하며 작성하게 된다.

동기, 비동기, 백그라운드 실행

cd ~/mydirectory/blog/yeoooo.github.io #gem파일이 있는 디렉토리까지
                                    #이동해줘야한다.
bundle exec jekyll serve

나는 로컬서버의 포트를 기본 설정값인 4000을 사용하는데, 이 포트가 사용되고 있거나 충돌을 일으키면 아래와 같은 코드로 포트, 프로세스를 꺼주고는 했다.

lsof -i :4000 #포트를 사용하고있는 프로세스의 정보를 알려준다.
kill -9 _____ #위의 코드를 통해 얻어낸 프로세스의 정보에서 PID를 
              #_____ 칸에 입력해줘서 프로세스를 끈다.

길지 않은 코드들이지만 자주 사용하다보니 불편함을 느꼈고 이를 다음과 같은 코드를 zshell에 입력해 축약했다.

# cd to blog directory and start jekyll local server START
function bs(){
    cd ~/mydirectory/blog/yeoooo.github.io;
    bundle exec jekyll serve;
}
# cd to blog directory and start jekyll local server END

이렇게 작성한 코드는 성공적으로 블로그 디렉토리까지 이동해서 jekyll을 실행시킨다.
하지만 키보드에서 떼기 싫은 내 손은 블로그를 브라우저를 통해 열기 위해 움직여줘야했다.(127.0.0.1:4000을 브라우저 주소창에 입력해줘야한다.) 이를 아래와 같이 변경했지만, 이미 jekyll이 작동되고 있는 상태이기 때문에 별다른 명령어가 먹히질 않았다.

function bs(){
    cd ~/mydirectory/blog/yeoooo.github.io;
    bundle exec jekyll serve;
    #jekyll이 작동되고 있으므로 아래의 명령어는 먹히지 않는다.
    open 127.0.0.1:4000
}

이를 해결하기 위해 검색하던중 {}과 ;, &과 &&의 의미가 궁금해졌고, 여기서 그 의미와 함께 해답을 찾게 되었다.
찾은 해답을 통해 코드를 다음과 같이 바꿨다.

function bs(){
    cd ~/dev/yeoooo.github.io;
    {bundle exec jekyll serve&{sleep 4;open 127.0.0.1:4000;}};
    #jekyll을 작동시키고 바로 브라우저를 키면 아직 서버가 작동되기전에 페이지가 켜져서 
    #아무것도 뜨지 않기 때문에 4초정도 대기 시켰다.
}

이렇게 작성된 코드는 {}를 통해서 명령어를 그룹지어 하나로 실행하기 때문에 jekyll이 실행됨과 같이 브라우저에서 원하는 주소로 접근할 수 있게 된다.
또한 &를 사용해서 jekyll을 백그라운드에서 실행시키고, 그 다음 명령을 입력시켜 성공적으로 원하는 기능이 실행된다.

추가적으로 &&은 이전 작업이 성공한 후에 다음 명령어를 실행하고 ;는 성공 여부와 상관없이 다음 명령어를 실행한다. 동기와 비동기의 차이다.

옵션, default 옵션, 열 행 추출

위에서 PID를 얻어내고, 그를 통해서 포트를 닫아줬다.
문제는 PID를 얻어낸 뒤 눈으로 직접 확인하고 작성해야한다는 불편함이 있었는데, 이 또한 해결할 수 있는 방법이 존재했다.
먼저 이전 코드와 현재 코드를 비교해보자.

function getPID(){
    lsof -i :$1
}
function killPID(){
    kill -9 $1
}

먼저 첫번째 명령어들인 getPIDkillPID는 위에서 언급한 것과 같이 각각 PID를 얻어내고 이를 통해 프로세스를 종료시키는 명령어들이다. 간단하지만 눈으로 확인하고 매번 입력해줘야한다.

#killPort START
function killPort(){
    while getopts "a" opt
    do
    case $opt in
    a) lsof -i :$2|grep kyeong|awk '{print $2}'|while read line;
    do echo $line killed 
    kill -9 $line;
    done
    ;;
    esac
    done
    if [ $OPTIND -eq 1 ]; then 
    target=$(lsof -i :$1|sed -n '2p'|awk '{print $2}')
    kill -9  $(lsof -i :$1|sed -n '2p'|awk '{print $2}');
    echo $target killed;
    fi
}
#killPort END

그와 다르게 해당 코드는 그 두 작업을 포트 입력만으로 해결하고 심지어 killPort 명령어 뒤에 -a 옵션을 붙여 해당 포트를 사용하고 있는 모든 프로세스를 종료시켜줄 수 있다.

getopts

먼저 옵션을 주기위해 getopts 내장 함수를 사용했다. getopts는 뒤의 string을 우리가 흔히 아는 옵션으로 사용할 수 있게 파싱 해준다.

while getopts "abcde" opt #이렇게 작성하면 -a, -b, -c, -d, -e(이 옵션들은 따로따로 사용되어야 한다.)와 같이 사용할 수 있다.

또한 :(콜론)을 통해서 옵션의 옵션 값을 줄 수 있는데, 이는 필요하지 않아서 사용하지는 않았다.

while getopts "a:b" opt #이렇게 작성하면 -a -b와 같이(쉼표가 없음에 주의하자.) 사용할 수 있다.  

이 후로 case 문을 이용해 옵션이 들어왔을 경우에 실행시킬 명령어를 입력해준다.
while 반복문으로 작동하는 getopts이기 때문에 esacdone 키워드를 넣어줘야한다는 점을 잊지말자.

getopts에 대해 더 자세한 내용을 알고 싶다면 참고문서를 확인하자.

default option

초기값, 즉 아무 옵션도 입력되지 않았을 경우 실행할 명령어를 주는 것은 case에서 일어나지 않는다.

killPort 4000

while getopts 구문을 지나간 후에 아무런 인자가 입력되지 않는 다면 getopts와 연관된 전역변수 OPTIND(현재 몇번 째 인자를 처리할 것인지 표시해준다.)가 1인 것을 이용해 default 값을 줄 수 있다.

shell script에서 if 문은 옵션을 가질 수 있다.
여기서는 -eq 옵션(문자열 비교 연산자)를 이용해 1과 일치하는지 비교하여 인자가 들어왔는지 확인 후, 들어오지 않았다면 실행할 명령어를 입력해준다.

if 문을 입력해 줄 때는 주의점이 있는데, 대괄호 [ ]의 양쪽에 공백이 존재해야 한다는 것이다.

if [ $OPTIND -eq 1 ]; #또한 키워드 하나하나 들어갈 때 마다 공백이 필요하다.

grep

grep 명령어는 텍스트에서 해당 문자열이 들어간 행을 찾아서 출력해준다.

lsof.png

lsof 명령어를 입력하게되면 이와 같은 화면을 볼 수 있는데, 이 곳에서 삭제하려는 행을 특정 해내기 위해서 grep을 사용할 수 있다.

해당 포트를 사용하는 모든 프로세스를 종료시키고자 User열에 적혀있는 나의 이름을 키워드로 줬다.

lsof -i :$2|grep 나의이름

이와 같이 입력한다면 종료시키고자 하는 프로세스 행을 특정해낼 수 있다.

sed

sed로도 이를 특정할 수 있다.
grep과는 다르게 파일을 열지 않고 데이터를 수정한다.

$(lsof -i :$2|sed -n '2p');

위와 같이 작성하면 2번 째 줄에 있는 데이터를 반환한다.
자세한 기능은 참고문서에서 확인하자.

awk

PID를 추출해내기 위해서는 위에서 얻어낸 행을 토대로 열을 검색해야한다.
이와 같이 열로 이뤄진 파일에서 적합한 것이 awk이며 이를 사용했다.
아래와 같이 입력하면 PID를 추출할 수 있다.

echo $(lsof -i :$1|sed -n '2p'|awk '{print $2}')

정리

shell script에 대해 짤막하게나마 공부하게되는 계기를 가지게 되었다.
생각보다 많은 기능을 제공했으며 다른 언어와 달리 직관적이지 않은 것들도 많고 그만큼 애를 먹었지만 흥미로운 내용이었다.

참고문서

옵션기능 구현
https://velog.io/@khyup0629/shell-script-옵션-기능-구현하기

연속적으로 명령어 입력하기
https://opentutorials.org/module/2538/15818

ps에서 pid 목록만 가져와서 kill하기
https://sosal.kr/880

default option
https://stackoverflow.com/questions/22058316/bash-getopts-multiple-arguments-or-default-value

shell script if문 옵션
https://etloveguitar.tistory.com/18

sed
https://webstone.tistory.com/83

댓글남기기