쉘 스크립트의 가장 간단한 예는 스크립트 파일에 시스템 명령어들을 단순히 나열해 놓는 것입니다. 이렇게 하면 적어도, 특정한 순서로 명령어를 실행시켜야 할 때 다시 치는 수고를 덜어 줍니다.
예 2-1. cleanup: /var/log 에 있는 로그 파일들을 청소하는 스크립트
# cleanup # 루트로 실행시키세요. cd /var/log cat /dev/null > messages cat /dev/null > wtmp echo "로그를 정리했습니다." |
별 다른게 없죠? 단순히 콘솔이나 한텀에서 쉽게 실행 시킬 수 있는 명령어들의 조합입니다. 명령어들을 스크립트 상에서 실행시키는 것은 이들을 다시 치지 않아도 된다는 것 이상도 이하도 아닙니다. 스크립트는 특정한 응용에 대해 쉽게 고치고 입맛에 맞게 수정하고 일반화 시킬 수 있습니다.
예 2-2. cleanup: 위 스크립트의 향상되고 일반화된 버전.
#!/bin/bash # cleanup, version 2 # 루트로 실행시키세요. LOG_DIR=/var/log ROOT_UID=0 # $UID가 0인 유저만이 루트 권한을 갖습니다. LINES=50 # 기본적으로 저장할 줄 수. E_XCD=66 # 디렉토리를 바꿀 수 없다? E_NOTROOT=67 # 루트가 아닐 경우의 종료 에러. if [ "$UID" -ne "$ROOT_UID" ] then echo "이 스크립트는 루트로 실행시켜야 됩니다." exit $E_NOTROOT fi if [ -n "$1" ] # 명령어줄 인자가 존재하는지 테스트(non-empty). then lines=$1 else lines=$LINES # 명령어줄에서 주어지지 않았다면 디폴트값을 씀. fi # Stephane Chazelas 가 명령어줄 인자를 확인하는 더 좋은 방법을 #+ 제안해 주었는데 지금 단계에서는 약간 어려운 주젭니다. # # E_WRONGARGS=65 # 숫자가 아닌 인자.(틀린 인자 포맷) # # case "$1" in # "" ) lines=50;; # *[!0-9]*) echo "사용법: `basename $0` 정리할파일"; exit $E_WRONGARGS;; # * ) lines=$1;; # esac # #* 이것을 이해하려면 "루프" 절을 참고하세요. cd $LOG_DIR if [ `pwd` != "$LOG_DIR" ] # 혹은 if [ "$PWD" != "LOG_DIR" ] # /var/log 에 있지 않다? then echo "$LOG_DIR 로 옮겨갈 수 없습니다." exit $E_XCD fi # 로그파일이 뒤죽박죽되기 전에 올바른 디렉토리에 있는지 두번 확인함. # 더 좋은 방법은: # --- # cd /var/log || { # echo "필요한 디렉토리로 옮겨갈 수 없습니다." >&2 # exit $E_XCD; # } tail -$lines messages > mesg.temp # message 로그 파일의 마지막 부분을 저장. mv mesg.temp messages # 새 로그 파일이 됨. # cat /dev/null > messages #* 위의 방법이 더 안전하니까 필요 없음. cat /dev/null > wtmp # > wtemp 라고 해도 같은 결과. echo "로그가 정리됐습니다." exit 0 # 스크립트 종료시에 0을 리턴하면 #+ 쉘에게 성공했다고 알려줌. |
시스템 로그 전체를 날려 버릴 생각이 없을 테니까 여기서는 message 로그의 마지막 부분을 그대로 남겨 놓습니다. 앞으로는 이렇게 앞서 썼던 스크립트를 가공해서 다시 쓰는 식의 좀 더 효과적인 방법을 계속 보게 될 것입니다.
The #! 은 스크립트의 제일 앞에서 이 파일이 어떤 명령어 해석기의 명령어 집합인지를 시스템에게 알려주는 역할을 합니다. #! 은 두 바이트 [1] 의 "매직 넘버"(magic number)로서, 실행 가능한 쉘 스크립트라는 것을 나타내는 특별한 표시자입니다(man magic을 하면 재미있는 주제의 이야기들을 볼 수 있습니다). #! 바로 뒤에 나오는 것은 경로명으로, 스크립트에 들어있는 명령어들을 해석할 프로그램의 위치를 나타내는데 그 프로그램이 쉘인지, 프로그램 언어인지, 유틸리티인지를 나타냅니다. 이 명령어 해석기가 주석은 무시하면서 스크립트의 첫 번째 줄부터 명령어들을 실행시킵니다. [2]
#!/bin/sh #!/bin/bash #!/usr/bin/perl #!/usr/bin/tcl #!/bin/sed -f #!/usr/awk -f |
각각의 줄은 기본 쉘인 /bin/sh이나 기본쉘(리눅스에서는 bash), 혹은 다른 명령어 해석기를 부르고 있습니다. [3] 거의 대부분의 상업용 유닉스 변종에서 기본 본쉘인 #!/bin/sh을 쓰면 비록 Bash 만 가지고 있는 몇몇 기능들을 못 쓰게 되겠지만 리눅스가 아닌 다른 머신에 쉽게 이식(port)할 수 있게 해 줍니다(이렇게 작성된 스크립트는 POSIX [4] sh 표준을 따르게 됩니다.
"#!" 뒤에 나오는 경로는 정확해야 합니다. 만약 이를 틀리게 적는다면 스크립트를 돌렸을 때 거의 대부분 "Command not found"라는 에러 메세지만 보게 될 것입니다.
스크립트에서 내부 쉘 지시자를 안 쓰고 일반적인 시스템 명령들만 쓴다면 #!는 안 써도 괜찮습니다. 위의 2번 예제에서는 #!이 필요한데, lines=50이라는 쉘 전용 생성자를 써서 변수에 값을 대입하고 있기 때문입니다. #!/bin/sh이 리눅스에서 기본 쉘 해석기인 /bin/bash을 부르고 있는 것에 주의하십시오.
중요: 이 튜토리얼은 스크립트를 만들 때 모듈별 접근 방식을 사용하도록 유도합니다. 나중에 유용하게 쓸 수 있어 보이고 "자주 등장"(boilerplate)하는 코드 조각들을 모아 두세요. 이렇게 모아두면 나중에 아주 다양하고 매력적인 루틴들을 만들 수 있을 겁니다. 예를 들어, 다음 스크립트 조각은 스크립트 시작 부분에 두어서 원하는 수 만큼의 매개변수를 받았는지 확인하는데 쓰일 수 있습니다.
if [ $# -ne 원하는_매개변수_갯수 ] then echo "사용법: `basename $0` 어쩌구저쩌구" exit $WRONG_ARGS fi
[1] | 몇몇 유닉스 버전(4.2BSD 에 기반한)에서는 매직 넘버로 4 바이트를 받아들이기 때문에 ! 다음에 빈 칸이 필요합니다. #! /bin/sh. | |
[2] | 해당 명령어 해석기(sh이나 bash)는 #!이 있는 줄을 처음 해석하려고 할텐데, 이 줄은 이미 명령어 해석기를 부르는 자신의 역할을 수행했고 #으로 시작하기 때문에 주석으로 올바르게 해석될 것입니다. | |
[3] | 그래서 이렇게 멋진 트릭도 가능해 집니다.
재미있는게 또 있는데, README 파일의 시작 부분에 #!/bin/more 라고 적고 실행 퍼미션을 주면, 자기 스스로 내용을 보여주는 문서 파일이 됩니다. | |
[4] | Portable Operating System Interface, 유닉스류의 OS들을 위한 표준화 작업 |