쉘 스크립트 프로그래밍 – 1

쉘 스크립트 프로그래밍 - 1

목차

쉘 스크립트는 셸이 해석하는 명령을 모아 둔 실행 파일로, 반복 작업을 자동화할 때 가장 빠르게 손이 가는 도구다. 변수·조건문·반복문 같은 프로그래밍 요소를 그대로 쓸 수 있어, 명령 몇 줄을 파일로 묶어 두면 매번 타이핑하던 작업을 한 번에 처리한다. 이 글은 쉘 스크립트 입문 1편으로, 셸의 역할부터 스크립트 작성·실행, 변수, 사용자 입력과 파라미터, 산술 연산, 조건문(if·case), 반복문(for·while·until)까지 기초 문법을 예제 중심으로 정리한다.

쉘 스크립트란?

셸(shell)은 사용자가 입력한 명령을 해석해 커널로 전달하고, 커널이 돌려준 결과를 다시 사용자에게 보여 주는 명령 해석기다. 우리가 터미널에 친 ls·cd 같은 명령을 받아 실제 동작으로 바꿔 주는 중간 다리인 셈이다. 리눅스의 기본 셸은 대부분 bash(Bourne Again SHell)다.

쉘 스크립트는 이런 명령들을 한 파일에 모아 순서대로 실행하게 만든 것이다. 리눅스가 C 언어 기반 OS인 만큼 셸 스크립트도 C와 비슷한 방식으로 변수, 조건문, 반복문을 사용할 수 있다. 스크립트 작성은 주로 vigedit 같은 편집기로 한다.

스크립트 작성과 실행

shebang과 첫 스크립트

스크립트 첫 줄에는 shebang을 적는다. #! 뒤에 이 스크립트를 해석할 셸의 경로를 지정하는 것으로, #!/bin/sh 또는 #!/bin/bash를 쓴다. vi name.sh로 파일을 열어 다음과 같이 작성해 보자. $USER·$HOME은 셸이 미리 제공하는 환경 변수다.

#!/bin/sh
echo "사용자 이름: " $USER
echo "홈 디렉토리: " $HOME
exit 0

sh name.sh처럼 셸에 파일을 인자로 넘기면 실행 권한이 없어도 바로 동작한다.

[centos@localhost ~]$ sh name.sh
사용자 이름:  centos
홈 디렉토리:  /home/centos

실행 권한 부여 후 직접 실행

스크립트 파일 자체에 실행 권한을 주면 ./파일명 형태로 곧장 실행할 수 있다. 권한은 chmod +x로 부여하고, ls -l로 확인하면 권한 문자열에 x가 붙은 것을 볼 수 있다. 현재 디렉터리에 있는 파일을 실행할 때 ./를 붙이는 이유는, 보안상 셸이 현재 디렉터리(.)를 자동으로 명령 검색 경로에 포함하지 않기 때문이다.

[centos@localhost ~]$ chmod +x name.sh
[centos@localhost ~]$ ls -l *.sh
-rwxrwxr-x 1 centos centos 81  3월 19 15:50 name.sh
[centos@localhost ~]$ ./name.sh
사용자 이름:  centos
홈 디렉토리:  /home/centos

변수와 따옴표

셸 스크립트에서 변수를 다루는 규칙은 다음과 같다.

  • 변수를 미리 선언하지 않는다. 처음 값이 할당되는 순간 변수가 생성된다.
  • 모든 값은 기본적으로 문자열로 취급한다.
  • 대소문자를 구분한다.
  • = 좌우에 공백이 없어야 한다.

마지막 규칙이 입문자가 가장 자주 걸리는 부분이다. = 양쪽에 공백을 넣으면 셸은 변수 할당이 아니라 testval이라는 명령을 실행하려다 실패한다.

[centos@localhost ~]$ testval = hello
bash: testval: 명령을 찾을 수 없습니다...
[centos@localhost ~]$ testval=hello
[centos@localhost ~]$ echo $testval
hello

따옴표는 변수 확장 동작을 좌우한다. 큰따옴표(") 안에서는 $변수가 값으로 치환되지만, 작은따옴표(') 안에서는 모든 문자가 글자 그대로 출력된다. 다음 var1.sh로 차이를 확인해 보자.

#!/bin/sh
myvar="Hi Woo"
echo $myvar
echo "$myvar"
echo '$myvar'
echo $myvar
echo 값 입력:
read myvar
echo '$myvar'=myvar
exit 0

실행하면 큰따옴표·따옴표 없는 경우는 값(Hi Woo)이, 작은따옴표는 글자 그대로 $myvar가 출력된다. read로 받은 입력은 다음 절에서 다룬다.

[centos@localhost ~]$ sh var1.sh
Hi Woo
Hi Woo
$myvar
Hi Woo
값 입력:
jungsoonho
$myvar=myvar

사용자 입력과 파라미터 변수

read로 입력받기

실행 중 사용자에게 값을 입력받을 때는 read 명령을 쓴다. read 변수명 형태로 쓰면 사용자가 입력한 한 줄이 해당 변수에 저장된다. 앞의 var1.sh에서 read myvar가 그 예다.

파라미터(위치 매개변수)

스크립트를 실행할 때 함께 넘긴 인자는 파라미터 변수로 받는다. $0은 실행한 스크립트 이름, $1·$2…는 순서대로 전달된 인자, $*(또는 $@)는 전체 인자, $#은 인자의 개수다.

# paravar.sh
#!/bin/sh
echo "실행파일 이름은 <$0>이다"
echo "첫 번째 파라미터는 <$1>이고, 두 번째 파라미터는 <$2>다."
echo "전체 파라미터는 <$*>다"
echo "파라미터 개수는 <$#>개다"
exit 0

인자 없이 그냥 실행하면 $1·$2·$*가 모두 비어 있고 $#은 0이다.

[centos@localhost ~]$ sh paravar.sh
실행파일 이름은 <paravar.sh>이다
첫 번째 파라미터는 <>이고, 두 번째 파라미터는 <>다.
전체 파라미터는 <>다
파라미터 개수는 <0>개다

인자를 붙여 실행하면 각 위치 변수에 값이 채워진다.

[centos@localhost ~]$ sh paravar.sh apple banana
실행파일 이름은 <paravar.sh>이다
첫 번째 파라미터는 <apple>이고, 두 번째 파라미터는 <banana>다.
전체 파라미터는 <apple banana>다
파라미터 개수는 <2>개다

산술 연산

셸에서 변수의 값은 기본적으로 문자열이다. 따라서 num2=$num1+200처럼 단순히 더하면 계산이 아니라 100+200이라는 문자열이 그대로 들어간다. 숫자 계산을 하려면 산술 연산 구문을 따로 써야 한다.

전통적으로는 expr 명령을 백틱(`...`)으로 감싸 계산한다. 이때 곱셈 *나 괄호 ( )는 셸이 먼저 해석하지 못하도록 로 이스케이프해야 한다. 또한 expr의 각 항과 연산자 사이에는 반드시 공백을 둬야 한다.

#!/bin/sh
num1=100

# 문자열로 이어 붙은 결과 (계산 아님)
num2=$num1+200
echo $num2              # 출력: 100+200

# expr는 백틱으로 감싸야 결과가 변수에 담긴다
num3=`expr $num1 + 200`
echo $num3              # 출력: 300

# 괄호와 * 는 백슬래시로 이스케이프
num4=`expr ( $num1 + 200 ) / 10 * 2`
echo $num4              # 출력: 60
exit 0
[centos@localhost ~]$ sh numcalc.sh
100+200
300
60

요즘 bash에서는 expr보다 내장 산술 확장 $(( ))를 권장한다. 별도 명령 호출 없이 빠르고, 괄호·곱셈을 이스케이프할 필요도 없어 가독성이 좋다.

num1=100
num4=$(( (num1 + 200) / 10 * 2 ))
echo $num4              # 출력: 60

조건문: if와 case

if · if-else

조건 판단에는 if를 쓴다. 조건은 대괄호 [ ](test 명령) 안에 적는데, 대괄호 안쪽 양옆에 공백을 반드시 둬야 한다.[ "woo" = "woo" ]처럼 띄어 써야 하며, ["woo"="woo"]는 오류다.

if [ 조건 ]
then
  참일 경우 실행
fi

if1.sh — 조건이 참이면 then 블록을 실행한다.

#!/bin/sh
if [ "woo" = "woo" ]
then
        echo "참입니다"
fi
exit 0
[centos@localhost ~]$ sh if1.sh
참입니다

else를 붙이면 거짓일 때 실행할 블록을 따로 지정할 수 있다.

#!/bin/sh
if [ "woo" = "woo" ]
then
        echo "참입니다"
else
        echo "거짓입니다"
fi
exit 0

비교 연산자

문자열 비교와 숫자 비교에 쓰는 연산자가 다르다. 문자열은 =·!=, 숫자는 -eq·-lt 같은 영문 약자를 쓴다.

연산자의미
문자1 = 문자2두 문자열이 일치
문자1 != 문자2두 문자열이 일치하지 않음
-z 문자문자가 비어 있으면(null) 참
-n 문자문자가 비어 있지 않으면 참
값1 -eq 값2두 값이 같음 (equal)
값1 -ne 값2두 값이 같지 않음 (not equal)
값1 -lt 값2값1이 값2보다 작음 (less than)
값1 -le 값2값1이 값2보다 작거나 같음 (less or equal)
값1 -gt 값2값1이 값2보다 큼 (greater than)
값1 -ge 값2값1이 값2보다 크거나 같음 (greater or equal)

if3.sh — 숫자 비교 예제. [ 100 -eq 200 ]은 거짓이므로 else 블록이 실행된다.

#!/bin/sh
if [ 100 -eq 200 ]
then
        echo "100은 200과 같다"
else
        echo "100은 200과 다르다"
fi
exit 0

AND · OR 결합

여러 조건을 함께 보려면 AND는 &&, OR는 ||로 연결한다. 다음 andor.sh는 입력한 파일이 존재(-f)하고 크기가 0이 아닐(-s) 때만 앞 5줄을 출력한다.

#!/bin/sh
echo "보고 싶은 파일명을 입력하세요"
read fname
if [ -f $fname ] && [ -s $fname ] ; then
        head -5 $fname
else
        echo "파일이 없거나, 크기가 0 입니다."
fi
exit 0

case ~ esac

값이 여러 경우 중 하나와 일치하는지 비교할 때는 case가 깔끔하다. C나 Java의 switch ~ case와 비슷하다. 각 분기는 패턴)으로 시작하고 ;;로 끝내며, *)는 어디에도 해당하지 않을 때의 기본 분기다.

#!/bin/sh
case "restart" in
    start)
         echo "시작~~";;
    stop)
         echo "중지~~";;
    restart)
         echo "재시작~~";;
    *)
         echo "출력 불가"
esac
exit 0
[centos@localhost ~]$ sh case1.sh
재시작~~

반복문: for · while · until

for-in

for-in은 나열한 목록의 항목을 하나씩 변수에 담아 반복한다. 다음 forin1.sh는 1부터 10까지 더한다. 누적합은 매 반복마다 expr로 계산한다.

#!/bin/sh
hap=0
for i in 1 2 3 4 5 6 7 8 9 10
do
  hap=`expr $hap + $i`
done
echo "1부터 10까지의 합 : $hap"
exit 0
[centos@localhost ~]$ sh forin1.sh
1부터 10까지의 합 : 55

목록 자리에 명령 치환 $(...)을 쓰면 명령 결과를 반복 대상으로 삼을 수 있다. forin2.sh는 현재 디렉터리의 *.sh 파일마다 이름과 앞 3줄을 출력한다.

#!/bin/sh
for fname in $(ls *.sh)
do
  echo "-----$fname-----"
  head -3 $fname
done
exit 0
[centos@localhost ~]$ sh forin2.sh
-----andor.sh-----
#!/bin/sh
echo "보고 싶은 파일명을 입력하세요"
read fname
-----case1.sh-----
#!/bin/sh
case "restart" in
    start)
-----forin1.sh-----
#!/bin/sh
hap=0
for i in 1 2 3 4 5 6 7 8 9 10

while

while은 조건이 참인 동안 반복한다. 다음은 같은 1~10 합산을 while로 구현한 것으로, 조건이 거짓($i가 10을 초과)이 되면 멈춘다.

#!/bin/sh
hap=0
i=0
while [ $i -le 10 ]
do
  hap=`expr $hap + $i`
  i=`expr $i + 1`
done
echo "1부터 10까지의 합 : $hap"
exit 0
[centos@localhost ~]$ sh while.sh
1부터 10까지의 합 : 55

read와 결합하면 원하는 입력이 들어올 때까지 반복하게 만들 수 있다. while2.sh는 비밀번호가 1234가 아니면 계속 다시 묻는다.

#!/bin/sh
echo "비밀번호를  입력하세요"
read mypass
while [ $mypass != "1234" ]
do
  echo "틀렸음. 다시 입력하세요."
  read mypass
done
echo "통과~~"
exit 0
[centos@localhost ~]$ sh while2.sh
비밀번호를  입력하세요
1234
통과~~

until

untilwhile과 용도가 거의 같지만 조건의 의미가 반대다. 조건이 거짓인 동안 반복하고, 참이 되면 멈춘다.until [ 조건 ]while [ ! 조건 ]과 같다. 위 합산 while문을 until로 바꾸려면 조건 줄만 다음처럼 뒤집으면 된다. $i가 10을 초과하면(참) 반복을 끝낸다.

#!/bin/sh
hap=0
i=0
until [ $i -gt 10 ]      # while [ $i -le 10 ] 과 동일한 동작
do
  hap=`expr $hap + $i`
  i=`expr $i + 1`
done
echo "1부터 10까지의 합 : $hap"
exit 0

흐름 제어: break · continue · exit · return

  • break : 반복문을 즉시 빠져나간다.
  • continue : 남은 본문을 건너뛰고 반복문의 조건식으로 돌아간다.
  • exit : 스크립트(프로그램)를 완전히 종료한다.
  • return : 함수 안에서 쓰며, 함수를 호출한 곳으로 돌아간다.

다음은 입력에 따라 세 가지를 분기하는 무한 반복 예제다. b는 break, c는 continue, e는 exit로 동작한다.

#!/bin/sh
echo "무한 반복 입력을 시작합니다. (b: break, c: continue, e: exit)"
while [ 1 ] ; do
  read input
  case $input in
    b | B)
        break;;
    c | C)
        echo "continue는 while문의 조건으로 돌아감"
        continue;;
    e | E)
        echo "exit를 누르면 프로그램(함수)를 완전히 종료함"
        exit 1;;
  esac;
done
echo "break를 누르면 이 문장이 출력된다"
exit 0
[centos@localhost ~]$ sh bce.sh
무한 반복 입력을 시작합니다. (b: break, c: continue, e: exit)
c
continue는 while문의 조건으로 돌아감
b
break를 누르면 이 문장이 출력된다

종료 코드 확인: $?

모든 명령은 끝나면서 종료 코드를 남긴다. 0은 성공, 0이 아닌 값은 실패나 특정 상태를 뜻한다. exit 0으로 스크립트를 정상 종료하는 것도 이 약속을 따르는 것이다. 직전 명령의 종료 코드는 특수 변수 $?로 확인한다.

[centos@localhost ~]$ ls /tmp
[centos@localhost ~]$ echo $?
0
[centos@localhost ~]$ ls /없는경로
ls: '/없는경로'에 접근할 수 없습니다: 그런 파일이나 디렉터리가 없습니다
[centos@localhost ~]$ echo $?
2

정리

1편에서는 쉘 스크립트의 기초 문법을 훑었다. shebang과 실행 권한으로 스크립트를 실행하는 법, 변수와 따옴표 규칙, read 입력과 위치 매개변수($1·$*·$#), 산술 연산(expr·$(( ))), 조건문(if·case), 반복문(for·while·until), 흐름 제어와 종료 코드($?)까지 다뤘다. 각 예제를 직접 작성·실행해 보면 문법이 훨씬 빨리 손에 익는다. 이어지는 2편에서는 사용자 정의 함수와 eval·export 등 함수·변수 활용을 다룬다.

💬 댓글 0

💬 댓글을 남기려면?
첫 댓글을 남겨보세요 ✨

이 콘텐츠가 도움이 됐나요?

누스쿨 커뮤니티에서 더 많은 커리어 전략을 나눠요