2010. 10. 29. 12:37

Ubuntu Shell 활용 (03) - 간단한 Bash shell script 작성하기

shell script는 반복적인 shell 작업을 자동화 하는데 훌륭한 도구입니다. Bash 뿐만 아니라 다른 shell은 loop, 조건 검사, case문 등과 같은 프로그래밍을 할 수 있는 기본적인 구조를 제공하고 있습니다. 단지 다른점은, string 타입의 변수만 지원된다는 점입니다.


script를 편집하고 실행하기

shell script는 간단한 text 파일입니다. 그래서 당신이 선호하는 text editor(vi)를 이용하여 만들 수 있습니다. 실행을 위해서 shell script 파일은 실행가능해야 합니다. 예를 들어, 만약 당신이 myscript.sh 이름으로 shell script를 생성했다면, 다음을 통해 실행가능하게 만드는 것이 필요합니다.

$ chmod u+x myscript.sh

그리고, bash script의 첫 line은 반드시 다음과 같아야 합니다.

#! /bin/bash

#는 comment의 시작을 알립니다. #! syntax는 이러한 특수한 문법을 적용하지 말라고 shell에게 알려주는 역할을 합니다. /bin/bash 부분은 bash나 다른 shell과 같은 어떤 동작 가능한 shell에게 script를 실행해라는 뜻입니다.

당신이 작성한 실행 가능한 shell script는 PATH 혹은 전체 경로 혹은 상대경로에 있어야 합니다. 다시 말해 script를 실행할 때 다음과 같은 결과를 얻을 수 있습니다.

$ myscript.sh
myscript.sh: command not found
현재 디렉토리에 myscript.sh가 있어도, 해당 PATH에 포함되지 않은 경우입니다. 이러한 문제를 해결하기 위해, PATH에 추가하던가 아니면 전체 경로 혹은 상대경로로 실행합니다.
$ mkdir ~/bin ; cp myscript.sh ~/bin/ ; PATH=$PATH:~/bin
$ cp myscript.sh /user/local/bin
$ ./myscript.sh
$ /home/greenfish/work/myscript.sh


Script에 내용 추가하기

shell script는 명령의 단순 나열이지만, 또한 훌륭한 programming language이기도 합니다. 예를 들어, 주어진 다른 input으로 발생된 다른 output의 different를 추출할 수 있습니다. if/else, case 그리고 for/while loop 같은 명령들을 다루기로 하겠습니다.

다음의 예제 코드는 abc를 MYSTRING 변수에 할당하는 것을 보여줍니다. 그리고, abc인지 테스트하며, 그 결과를 보여줍니다. 테스트는 [ ]에 의해 구성됩니다.

MYSTRING=abc
if [ $MYSTRING = abc ] ; then
echo "The variable is abc"
fi
부정 조건 테스트는 !=를 사용합니다.
if [ $MYSTRING != abc ] ; then
echo "$MYSTRING is not abc";
fi

다음은 숫자에 대한 테스트를 보여줍니다.
MYNUMBER=1
if [ $MYNUMBER -eq 1 ] ; then echo "MYNUMBER equals 1"; fi
if [ $MYNUMBER -lt 2 ] ; then echo "MYNUMBER <2"; fi
if [ $MYNUMBER -le 1 ] ; then echo "MYNUMBER <=1"; fi
if [ $MYNUMBER -gt 0 ] ; then echo "MYNUMBER >0"; fi
if [ $MYNUMBER -ge 1 ] ; then echo "MYNUMBER >=1"; fi

이제 파일 이름에 대한 테스트를 확인해 봅시다. 아래 예는, 파일의 존재 여부 체크 (-e), 정규 파일 체크 (-r), 디렉토리 체크 (-d)와 같습니다. 이러한 체크는 if/then으로 구성됩니다. 만약 일치되지 않는다면 그에 상응되는 결과를 보고합니다.

filename="$HOME"
if [ -e $filename ] ; then echo "$filename exists"; fi
if [ -f "$filename" ] ; then
 echo "$filename is a regular file"
elif [ -d "$filename" ] ; then
 echo "$filename is a directory"
else
 echo "I have no idea what $filename is"
fi

아래 표는 파일, 문자열 그리고 변수에 대한 테스트에 필요한 Operator를 보여줍니다.

Operator 설명
 -a file  파일 존재 여부 체크 (-e와 동일) 
 -b file  special block device 파일인지 체크 
 -c file  special device 파일인지 체크 (예, serial device) 
 -d file  디렉토리 체크 
 -e file  파일 존재 여부 체크 (-a와 동일) 
 -f file  파일이 존재하고 정규 파일인지 체크
 (directory, socket, pipe, link, device 등은 아님) 
 -g file  set-group-id bit가 세팅된 파일인지 체크 
 -h file  심볼릭 링크인지 체크 (-L과 동일) 
 -k file  sticky bit가 세팅된 파일인지 체크
 -L file  심볼릭 링크인지 체크 (-h와  동일)
 -n string  문자열 길이가 0 byte 이상인지 체크 
 -o file  당신이 소유한 파일인지 체크 
 -p file  named pipe 파일인지 체크 
 -r file  당신이 읽을 수 있는 파일인지 체크
 -s file  파일이 존재하고, socket인지 체크
 -t fd  terminal에 연결된 파일 descriptor인지 체크 
 -u file  set-user-id bit가 세팅된 파일인지 체크 
 -w file  당신이 쓸수 있는 파일인지 체크 
 -x file  당신이 실행할 수 있는 파일인지 체크
 -z string  문자열 길이가 0 byte 인지 체크
 expr1 -a expr2  처음과 두번째 expression이 true인지 체크 
 expr1 -o expr2  처음과 두번째 expression중 하나가 true인지 체크 
 file1 -nt file2  처음 파일이 두번째 파일보다 새 파일인지 체크 
 file1 -ot file2  처음 파일이 두번째 파일보다 오래된 파일인지 체크 
 file1 -ef file2  두개의 파일이 하드 혹은 심볼릭 링크로 연결되어 있는지 체크 
 var1 = var2  두개의 값이 동일한지 체크 
 var1 -eq var2  두개의 값이 동일한지 체크 
 var1 -ge var2  첫번째 값이 두번째 값보다 같거나 큰지 체크 
 var1 -gt var2  첫번째 값이 두번째 값보다 큰지 체크 
 var1 -le var2  첫번째 값이 두번째 값보다 같거나 작은지 체크 
 var1 -lt var2  첫번째 값이 두번째 값보다 작은지 체크 
 var != var2  두개의 값이 다른지 체크 
 var -ne var2  두개의 값이 다른지 체크 

그다음으로 자주 사용되는 명령은 case 입니다. case를 이용하여 각각 다른 값의 테스트를 할 수 있습니다. 프로그래밍언어의 보통 switch 구문과 유사합니다. case 구문은 몇개 단계의 if 구문으로 구성될 수 있습니다.

case “$VAR” in
 string1)
  { action1 };;
 string2)
  { action2 };;
 *)
  { default action } ;;
esac

그리고, case의 예제를 /etc/init.d/ 경로에 start-up script를 통해 찾을 수 있습니다. 각각의 initscript는 어떤 파라미터(start, stop, ...)가 들어왔는지 체크하고 선택합니다.

/etc/init.d$ cat networking
#!/bin/sh -e
### BEGIN INIT INFO
# Provides:          networking
# Required-Start:
# Required-Stop:     $local_fs
# Default-Start:
# Default-Stop:      0 6
# Short-Description: Raise network interfaces.
### END INIT INFO

PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"

[ -x /sbin/ifup ] || exit 0

. /lib/lsb/init-functions

# helper function to set the usplash timeout. https://launchpad.net/bugs/21617
usplash_timeout () {
        TIMEOUT=$1
        if [ -x /sbin/usplash_write ]; then
            /sbin/usplash_write "TIMEOUT $TIMEOUT" || true
        fi
}

process_options() {
    [ -e /etc/network/options ] || return 0
    log_warning_msg "/etc/network/options still exists and it will be IGNORED! Read README.Debian of netbase."
}

check_network_file_systems() {
    [ -e /proc/mounts ] || return 0

    if [ -e /etc/iscsi/iscsi.initramfs ]; then
        # probably root on iSCSI
        log_warning_msg "not deconfiguring network interfaces: root filesystem appears to be on iSCSI."
        exit 0
    fi

    exec 9<&0 < /proc/mounts
    while read DEV MTPT FSTYPE REST; do
        case $DEV in
        /dev/nbd*|/dev/nd[a-z]*|/dev/etherd/e*)
            log_warning_msg "not deconfiguring network interfaces: network devices still mounted."
            exit 0
            ;;
        esac
        case $FSTYPE in
        nfs|nfs4|smbfs|ncp|ncpfs|cifs|coda|ocfs2|gfs|pvfs|pvfs2|fuse.httpfs|fuse.curlftpfs)
            log_warning_msg "not deconfiguring network interfaces: network file systems still mounted."
            exit 0
            ;;
        esac
    done
    exec 0<&9 9<&-
}

case "$1" in
start)
        /lib/init/upstart-job networking start
        ;;

stop)
        check_network_file_systems

        log_action_begin_msg "Deconfiguring network interfaces"
        if [ "$VERBOSE" != no ]; then
            if ifdown -a --exclude=lo; then
                log_action_end_msg $?
            else
                log_action_end_msg $?
            fi
        else
            if ifdown -a --exclude=lo >/dev/null 2>/dev/null; then
                log_action_end_msg $?
            else
                log_action_end_msg $?
            fi
        fi
        ;;

force-reload|restart)
        process_options

        log_action_begin_msg "Reconfiguring network interfaces"
        ifdown -a --exclude=lo || true
        if ifup -a --exclude=lo; then
            log_action_end_msg $?
        else
            log_action_end_msg $?
        fi
        ;;

*)
        echo "Usage: /etc/init.d/networking {start|stop|restart|force-reload}"
        exit 1
        ;;
esac

exit 0


bash shell은 또한 표준 loop 방식을 제공합니다. 다음을 숫자 0~9를 출력합니다.
for NUMBER in 0 1 2 3 4 5 6 7 8 9
do
    echo The number is $NUMBER
done

아래는 ls 명령의 결과를 제공받아 for 구문에 전달하는것을 보여주고 있습니다.
for FILE in `/bin/ls`; do echo $FILE; done

다음과 같이 while loop를 이용하여 VAR 변수가 0에서 3으로 증가하는 것을 확인할 수 있습니다.
"VAR=0"
while [ $VAR -lt 3 ]; do
    echo $VAR
    VAR=$[$VAR+1]
done