Bourne Shell 스크립트 정리

2022년 5월 20일 수정

이 글은 Bourne Shell 스크립트 문법 및 팁에 대해 전반적인 기준 없이 그냥 정리하는 글이다. 중구난방이고 개인적인 필요에 의해 업데이트 된다는 점에 유의하자.

셸 스크립트 실행하기

일반적으로 셸 스크립트를 만들면 을 이용해 실행하는 것이 보통이다.

sh ./somescript.sh

이 외에도 여러 실행 방법이 있다. 예를 들어

source somescript.sh

이렇게 하거나 혹은

. ./somescript.sh

이렇게 해도 스크립트가 실행된다.

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

만약 스크립트를 별도의 명령 없이 직접 파일 이름으로 실행되게 하고자 한다면 스크립트 제일 상단에 아래와 같이 실행 정보를 표기하는 주석문을 작성하자.

#!/bin/sh

# 으로 시작되는 구문은 원래 주석이기 때문에 실행 자체에 영향을 끼치진 않는다. 다만 유닉스에서 유래한 OS에서 스크립트의 첫 줄의 #! 은 해당 스크립트를 실행시키는 인터프리터나 셸의 경로를 적는 공간이다.

이제 실행 퍼미션을 주자. 예제는 스크립트 파일 이름이 somescript.sh 라고 가정한다.

chmod a+x somescript.sh

이렇게 하면 이 파일은 모든 사용자에게 실행(x) 권한이 주어진다.

이제 아래와 같은 명령으로 실행할 수 있다.

./somescript.sh

이런 식으로 실행시킬 을 명시해서 스크립트가 바로 실행되게 만들 수 있다.

참고로 이런 스크립트를 실행시키는 바이너리 정보 기입 법은 보통은 아래와 같은 스타일을 추천한다.

#!/usr/bin/env sh

env 라는 도구는 매개변수로 넘긴 도구가 어디에 있는지 찾아서 그 경로를 치환 시키는 도구다. 물론 sh가 아닌 bash 같은 셸을 명시해도 잘 동작해야 정상이다.

이후 PATH 환경 변수에 등록된 디렉터리로 옮기거나 해서 좀 더 쉽게 실행시킬 수 있게도 할 수 있다. 필요 없다면 확장자 .sh 를 빼버려도 유닉스나 리눅스, macOS 등에선 실행에 아무런 문제가 없다.

변수

변수를 생성하는 것은 아래와 같이 비교적 흔한(?) 방식으로 가능하다.

VAR_NAME="value"

= 사이에 공백이 있으면 안 된다. 공백이 들어가면 비교문으로 동작하기 때문이다.

환경변수로 뽑고 싶으면 export 를 붙이면 된다.

export VAR_NAME="value"

if문 정리

조건을 통해 분기를 정하는 스크립트에서 가장 중요한 부분이라고 볼 수 있는 문법이다. 대충 아래와 같은 식으로 사용한다.

if [ condition ]
then
    ...
elif [ condition ]
then
    ...
else
    ...
fi

라인 수를 줄이길 원할 때는 세미콜론을 잘 쓰면 된다.

if [ condition ]; then
    foo bar
fi

if [ condition ] ; then foo bar; fi;

조건을 반대로 뒤집을 때(NOT)는 조건 앞에 ! 을 쓰면 된다.

if [ ! condition ]; then foo bar; fi;

AND 혹은 OR 조건은 몇 가지 방법이 있지만 아래의 예 하나만 보자.

if { [ condition1 ] && [ condition2 ] ;} || [ condition3 ] ; then
    ...
fi

위 코드는 AND 체크하는 두 식을 중괄호를 이용해 묶은 형태다. 즉 기성 언어라면 (c1 && c2) || c3 이렇게 표현했을 식이라고 볼 수 있다. 세미콜론의 위치를 잘 보자.

루프(Loop)

for 문을 이용한 현대적인(?) 루프는 아래처럼 쓸 수 있다.

VALUES="a\nb\nc"
for VALUE in $VALUES; do
    echo $VALUE
done

VALUES 라는 변수의 값은 세 줄로 이루어진 문자열 데이터다. \n 은 개행문자다. 그리고 위 예제의 for 문은 각 라인마다 한 줄 씩 동작한다. 즉 출력은 a, b, c가 차례대로 각 한 줄 마다 표시된다.

그리고 C 언어 방식의 for 루프를 비슷하게 구현할 수도 있다.

for (( i=0; i < 10; i++ ))
do
    echo "$i"
done

위의 코드는 0에서 9까지의 숫자를 콘솔에 표시한다.

조건식 오퍼레이터

조건문에 사용하는 몇 가지 논리 오퍼레이터를 정리해보자.

=, !=
문자열이 동일한지, 동일하지 않은지
-eq, -ne, -gt, -ge, -lt, -le
차례대로 equal, not equal, greater than, greater than or equal, less than, less than or equal
-a, -o
AND와 OR
-z, -n
차례대로 문자열의 길이가 0(zero), 반대로 그 이상(not null)
-d, -e, -f
디렉토리가 존재하는지, 파일이 존재하는지, 문자열이 파일인지
-L, -r, -w
링크인지, 읽기 가능한지, 쓰기 가능한지
-s
파일 크기가 0 이상인지
-x
파일이 실행 가능한지
-nt, -ot, -ef
왼쪽 파일이 오른쪽 파일보다 최신인지, 과거인지, 같은 파일인지

디렉토리 존재 유무 체크

위에서도 정리했지만 -d 플래그를 쓸 수 있다.

if [ -d "/foo/bar/" ] ; then
    ...
fi

반대로 파일 여부를 파악하려면 -e 혹은 -f 플래그를 쓰면 될 것 같다.

문자열 비교

if [ "$FOO" = "BAR" ]; then
    ...
fi

= 사이의 공백이 꼭 필요하다. 공백이 없는 코드는 변수를 생성하는 코드이기 때문이다.

셸 스크립트에서 파일 개수 구하기

특정 디렉터리 안의 파일 갯수를 구하는 것은 바로는 안 되고 각 유틸리티를 이용한 결과를 얻어서 사용한다.

COUNT=$(ls /foo/bar | wc -l)

참고로 위 코드는 백쿼트(back-quote) ` 를 사용하는 것과 비슷한 방식의 커맨드다. 즉 ls /foo/bar | wc -l 커맨드의 실행 결과가 위 변수에 주입된다.

위 결과를 이용해 파일이 하나 이상 존재하는지 체크하려면

if [ "$COUNT" -gt 0 ] ; then
    # 파일이 하나 이상 존재하는 경우
fi

이런 식으로 쓸 수 있다.

스크립트 실행 파라미터(Parameter)

실행 파라미터(Parameter), 프로그래밍 언어에서는 매개변수라고 부르지만 여기서는 실행 옵션이라는 의미가 더 와닿는 것 같다. 스크립팅시에는 대충 아래의 키워드가 쓰인다.

$#
파라미터 개수
$@
파라미터 전체
$0
실행 명령 이름, 즉 스크립트 파일 이름
$1
첫 번째 파라미터
$2
두 번째 파라미터 (이런 식으로 계속 숫자를 불리면 된다)

위의 방법과 조합해서 리스트 형태로 받아서 사용하는 방법도 있다.

args=("$@")
for (( i=0; i < $#; i++ ))
do
    param=${args[$i]}
    echo "$i parameter is $param"
done

args 를 처리하는 방법을 잘 보자.

와일드카드 파라미터 처리

셸 스크립트에 옵션으로 *.ext 같은 파일 이름 와일드카드를 넘겼을 때 이를 나열하는 방법을 for 커맨드로 표현하면 이렇다.

for arg in "$@"
do
    echo $arg
    ...
done

앞서 살펴본 모든 파라미터를 받아오는 $@ 와 for 루프를 이용한 트릭이다.

이 경우 arg 에는 각 파일의 경로가 들어있다. 즉 와일드카드에 해당하는 모든 파일의 경로를 for 루프를 통해 하나하나 얻을 수 있다. 각 파일을 가공하고 싶을 때 유용하다.

셸 스크립트 동작에 에러가 나면 멈추게 하기

셸 스크립트 상단에 아래 한 줄을 넣어두면 에러가 났을 때 스크립트가 멈춘다.

set -e

스크립트에서 실행하는 커맨드의 에러 메시지 숨기기

가끔 스크립트에서 여러 명령을 사용하는데 이 명령이 에러를 표시하는 게 보기 싫을 수도 있다. 이럴 때 에러 메시지는 stderr (2) 파이프로 전달되니 이것을 지옥의 쓰레기통(?)에 던지면 된다.

some_shell_command 2> /dev/null

여기서 2> 이 표현이 에러를 어디로 리다이렉트(redirect) 시킬지 의미하는 표현이다.