Yong Ki/Abs Guide
1. 들어가면서 ¶쉘(shell)이라는 건 일종의 명령 해석기입니다. OS 커널과 사용자 사이의 중간자적 역할 이상으로 프로그래밍 언어로서의 기능 또한 강력하죠. 스크립트(script)라고하는 쉘 프로그램은 시스템 콜, 유틸리티, 이진파일 등을 서로 붙이고 연결해서 손쉽게 응용프로그램을 개발하도록 도와주는 툴이라고 보시면 됩니다. 사실상 모든 UNIX 명령어, 유틸리티, 툴 등이 스크립트의 재료가 될 수 있습니다만 이것으로도 충분치 않다면 쉘 내부의 조건문, 반복문 등 여러 구문들을 쓰신다면 스크립트가 좀더 강력하고 유연해지겠네요. 쉘 스크립트는 시스템 관리(admin) 작업이나 덩치 큰 프로그래밍 언어까지는 필요하지 않은 루틴 반복적인 작업에 특히 능력을 발휘합니다.
1.1. 왜 쉘 프로그래밍인가? ¶숙련된 시스템 관리자에게 있어서 쉘 스크립팅 지식은 필수입니다. 스크립트 작성할 일이 없다고 생각하는 관리자에게도 예외없죠. 아시다시피 리눅스 시스템이 부팅할 때
/etc/rc.d 에 있는 스크립트들을 실행해서 시스템 설정을 복원하고 서비스들을 세팅합니다. 시스템의 동태를 분석하고 수정하려면 이런 시동(startup) 스크립트들을 세부적으로 이해해야겠죠.
쉘 스크립트를 작성하는 것은 그리 어렵지 않습니다. 스크립트는 한입거리의 부분들로 짜집기 되어 있고 쉘 고유의 연산자나 옵션들[1]도 많지 않기 때문이죠. 문법도 단순하고 직관적이며 명령행 상에서 유틸리티들을 실행하고 연결하는 방식과 비슷합니다. 익혀야 할 규칙(rule)도 그리 많지 않아요.
복잡한 응용프로그램의 프로토타입(prototype)을 작성하는 데 "quick and dirty"한 방법으로 쉘 스크립트가 이용되기도 합니다. 프로젝트 개발의 첫 단계로서 부분적으로 기능을 구현하는 데 쉘스크립트를 이용하여 테스트하면서 C, C++, Java 나 Perl 등으로 최종 코딩되기 전에 루틴의 구조적 혹은 논리적 결함이 발견되죠.
쉘 스크립팅은 복잡한 프로젝트는 더 단순한 요소들로 나눠서 작업하고 다시 결합한다는 고전적인 UNIX 철학을 잘 이행하는 방법입니다. 많은 사람들이 차세대의 올인원(all-in-one) 고급 언어를 사용하는 것보다는 이러한 방식이 더 좋거나 매력적이라고 생각하죠. 모든 사람에게 모든 일이 가능하도록 기획된 그런 언어를 사용하려면 자신의 사고방식과 절차를 그 도구에 맞게 고쳐야 하는 부담은 감수해야 합니다.
다음의 작업을 할 생각이라면 쉘 스크립트는 적당하지 않습니다.
우리가 앞으로 함께 배워볼 쉘은 Bash입니다. Bash는 "Bourne-Again shell"의 머릿글자이고 Stephen Bourne의 Bourne Shell에서 유래했죠. Bash는 사실상 거의 모든 UNIX(-like)에서 쉘스크립팅의 표준이 되었습니다. 앞으로 얘기할 대부분의 원리들은 Korn Shell[2]이나 C Shell과 같은 다른 쉘에서의 스크립팅에도 적용해 볼 수 있어요. (C Shell 프로그래밍은 추천하지 않습니다. 1993년 10월에 Tom Christiansen 이 C Shell의 타고난 결함에 대해 유즈넷에 포스트했었네요.)
이어질 내용은 쉘 스크립팅 튜토리얼(tutorial)입니다. 쉘의 다양한 특징들을 설명하기 위해 많은 예제들을 곁들였는데 몇몇은 그대로 가져다 써도 유용한 것들이죠. 예제 스크립트의 소스파일(
scriptname.sh 또는 scriptname.bash)[3]을 실행하려면 먼저 chmod u+rx scriptname 과 같이 실행퍼미션을 주어야 한다는 것 잊지 마세요.
1.2. Sha-Bang(#!)으로 시작하자 ¶가장 간단한 스크립트를 보면, 스크립트라는 것은 시스템 명령어들을 파일 안에 나열한 것에 지나지 않구나 하는 생각이 들겁니다. 그렇다고 해도 실행할 때마다 동일한 명령어들을 다시 타이핑하는 수고는 덜어주는 거죠.
예제 2-1. cleanup : /var/log 안에 있는 로그파일 청소하는 스크립트
# Cleanup
# Run as root, of course. cd /var/log cat /dev/null > messages cat /dev/null > wtmp echo "Logs cleaned up." 예제 2-2. cleanup : 개선된 clean-up 스크립트
#!/bin/bash
# Proper header for a Bash script. # Cleanup, version 2 # Run as root, of course. # Insert code here to print error message and exit if not root. LOG_DIR=/var/log # Variables are better than hard-coded values. cd $LOG_DIR cat /dev/null > messages cat /dev/null > wtmp echo "Logs cleaned up." exit # The right and proper method of "exiting" from a script. 예제 2-3. cleanup : 위 스크립트를 강화하고 일반화한 버전
#!/bin/bash
# Cleanup, version 3 # Warning: # ------- # This script uses quite a number of features that will be explained #+ later on. # By the time you've finished the first half of the book, #+ there should be nothing mysterious about it. LOG_DIR=/var/log ROOT_UID=0 # Only users with $UID 0 have root privileges. LINES=50 # Default number of lines saved. E_XCD=66 # Can't change directory? E_NOTROOT=67 # Non-root exit error. # Run as root, of course. if [ "$UID" -ne "$ROOT_UID" ] then echo "Must be root to run this script." exit $E_NOTROOT fi if [ -n "$1" ] # Test if command line argument present (non-empty). then lines=$1 else lines=$LINES # Default, if not specified on command line. fi # Stephane Chazelas suggests the following, #+ as a better way of checking command line arguments, #+ but this is still a bit advanced for this stage of the tutorial. # # E_WRONGARGS=65 # Non-numerical argument (bad arg format) # # case "$1" in # "" ) lines=50;; # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;; # * ) lines=$1;; # esac # #* Skip ahead to "Loops" chapter to decipher all this. cd $LOG_DIR if [ `pwd` != "$LOG_DIR" ] # or if [ "$PWD" != "$LOG_DIR" ] # Not in /var/log? then echo "Can't change to $LOG_DIR." exit $E_XCD fi # Doublecheck if in right directory, before messing with log file. # far more efficient is: # # cd /var/log || { # echo "Cannot change to necessary directory." >&2 # exit $E_XCD; # } tail -$lines messages > mesg.temp # Saves last section of message log file. mv mesg.temp messages # Becomes new log directory. # cat /dev/null > messages #* No longer needed, as the above method is safer. cat /dev/null > wtmp # ': > wtmp' and '> wtmp' have the same effect. echo "Logs cleaned up." exit 0 # A zero return value from the script upon exit #+ indicates success to the shell. 스크립트 맨 처음에 등장하는 sha-bang(#!)은 아래의 스크립트는 지정한 명령 해석기를 통해서 수행되어야 한다는 걸 시스템에 알려주는 역할을 합니다. #! 는 사실 2바이트[4] 매직넘버(magic number, 자세한 내용은 man magic을 쳐보세요)입니다. sha-bang 다음에는 곧바로 경로명이 와야 하는데, 스크립트에 쓰인 명령들을 해석할 프로그램 경로이고 쉘, 프로그래밍 언어 또는 유틸리티 등이 올 수 있어요. sha-bang라인의 다음 줄부터는 호출된 명령 해석기가 순차적으로 처리를 하고, 이때 주석(comments)은 무시합니다.[5]
#!/bin/bash #!/usr/bin/perl #!/usr/bin/tcl #!/bin/sed -f #!/usr/awk -f위의 sha-bang라인들은 각기 다른 명령 해석기를 호출하니까 필요한 대로 하나만 선택하세요. 대부분의 상업적인 UNIX류의 운영체제에서 기본(default)으로 채택하고 있는 #!/bin/sh을 사용한다면 스크립트를 비리눅스 머신에 이식할 수 있게 됩니다. 물론 Bash 고유의 특징들은 못 쓰겠지만 스크립트는 POSIX[6] sh표준을 따르게 되는 거죠. sha-bang라인의 경로명은 유효한 것이어야 합니다. 그렇지 않으면 에러 메세지(보통 "Command not found")만 딸랑 내보내거든요.
쉘 내부의 명령들을 사용하지 않고 일반적인 시스템 명령들만 쭉 나열한 스크립트라면(위의 첫번째 예제처럼) #!라인을 생략할 수 있습니다. 위의 두번째 예제에서는 쉘 고유의 구문인 변수 할당문(lines=50)을 사용했기 때문에 반드시 #!라인을 써야 하죠. 또 하나 명심할 것은 #!/bin/sh은 기본(default) 쉘을 실행합니다. 리눅스 머신에서는
/bin/bash가 기본 쉘이에요.
스크립트를 작성하실 때 모듈방식의 접근을 권장합니다. 반복적으로 사용되는 토막 코드(snippets)는 나중에 재사용할 수 있도록 모아두세요. 이렇게 하다보면 마침내는 멋진 라이브러리가 완성되겠죠. 예를 들어, 아래의 토막 코드는 스크립트가 실행될 때 넘겨받은 파라메터(parameter)의 수가 올바른지 검사하는 겁니다.
E_WRONG_ARGS=65
script_parameters="-a -h -m -z" # -a = all, -h = help, etc. if [ $# -ne $Number_of_expected_args ] then echo "Usage: `basename $0` $script_parameters" # `basename $0` is the script's filename. exit $E_WRONG_ARGS fi 1.2.1. 스크립트 실행하기 ¶스크립트를 작성했으면 sh scriptname[7]나 bash scriptname을 쳐서 실행해 보세요. (Not recommended is using sh
chmod 555 scriptname (모든 사용자에게 읽기/실행 퍼미션을 부여합니다.)[8]
또는
chmod +rx scriptname (위와 같습니다.)
이나
chmod u+rx scriptname (파일 소유자에게만 읽기/실행 퍼미션을 부여합니다.)
이렇게 하면 ./scriptname[9]를 쳐서 실행할 수 있어요. 물론, 스크립트가 sha-bang라인이 있으면 지정된 명령 해석기를 호출해서 실행합니다.
스크립트의 테스트와 디버깅이 끝나고 완성도가 높아진 후 root권한으로 스크립트 파일을
/usr/local/bin으로 옮기면, 명령행에서 scriptname를 치는 것만으로도 스크립트를 실행할 수 있게 되고 다른 사용자들도 스크립트를 이용할 수 있습니다.
1.2.2. 연습 문제 ¶
2.1. 특수 문자 ¶스크립트나 기타 곳곳에서 자주 보이는 특수문자들
#
주석(comments). # 에서부터 줄 끝까지는 주석처리 합니다. # 앞에 공백이 와도 되죠.
;
# This line is a comment.
echo "A comment will follow." # Comment here. # ^ Note whitespace before # # A tab precedes this comment. 주석라인 안에 명령문을 쓰시면 마찬가지로 주식처리 되니까 다음 명령문은 새로운 줄에 쓰세요. #로 한번 시작한 주석라인을 그 줄에서 종료시킬 방법은 없습니다.
물론, echo문에서 이스케이프 처리한 #은 주석으로 보지 않습니다. 파라메터 치환식이나 숫자 상수 표현식, 패턴 매칭 연산에서 쓰이는 #도 마찬가지죠.
echo "The # here does not begin a comment."
echo 'The # here does not begin a comment.' echo The \# here does not begin a comment. echo The # here begins a comment. echo ${PATH#*:} # Parameter substitution, not a comment. echo $(( 2#101011 )) # Base conversion, not a comment. # Thanks, S.C. 명령 구분자 [semicolon]. 한줄에 둘 이상의 명령문이 가능하게 하죠.
echo hello; echo there
if [ -x "$filename" ]; then # Note that "if" and "then" need separation. # Why? echo "File $filename exists."; cp $filename $filename.bak else echo "File $filename not found."; touch $filename fi; echo "File test complete."
case 구문에서 선택문 종결자 [double semicolon].
.
case "$variable" in
abc) echo "\$variable = abc" ;; xyz) echo "\$variable = xyz" ;; esac "dot" command [period]. Equivalent to source (see Example 11-20). This is a bash builtin.
.
"dot", 파일명에서. 파일명이 "."으로 시작하면 숨김파일이 됩니다.
.
bash$ touch .hidden-file bash$ ls -l total 10 -rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook -rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak -rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook bash$ ls -al total 14 drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./ drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../ -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook -rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file디렉토리명을 대신해 쓸 수도 있는데, "."은 현재 디렉토리를, ".."은 상위 디렉토리를 의미합니다. bash$ pwd /home/bozo/projects bash$ cd . bash$ pwd /home/bozo/projects bash$ cd .. bash$ pwd /home/bozo/"."은 파일을 이동시키는 명령에서 목적지(디렉토리)를 대신해서 자주 쓰입니다. bash$ cp /home/bozo/current_work/junk/* . "dot", 정규표현식에서. 정규표현식에서 "."은 문자 하나에 매치됩니다.
"
partial quoting [double quote]. "STRING" preserves (from interpretation) most of the special characters within STRING. See also Chapter 5.
'
full quoting [single quote]. 'STRING' preserves all special characters within STRING. This is a stronger form of quoting than using ". See also Chapter 5.
,
콤마(comma) 연산자. 콤마 연산자는 일련의 수치 연산을 연결합니다. 연결된 연산식 모두를 평가하지만 마지막 것만 반환해요.
\
let "t2 = ((a = 9, 15 / 3))" # Set "a = 9" and "t2 = 15 / 3"
이스케이프 문자 [backslash]. A quoting mechanism for single characters.
/
\X "escapes" the character X. This has the effect of "quoting" X, equivalent to 'X'. The \ may be used to quote " and ', so they are expressed literally. See Chapter 5 for an in-depth explanation of escaped characters 파일명 경로 구분자 [forward slash].
`
:
/home/bozo/projects/Makefile처럼 파일명에서 구분자 역할을 하는데, 산술에서 나눗셈 연산자로도 쓰이죠.
null command [colon]. "NOP" (no op, a do-nothing operation)와 같습니다. 쉘 내장의 true와 동의어로 보시면 됩니다. ":" 은 그 자체로 쓰일 수 있고 exit값은 "true" (0)입니다.
:
echo $? # 0
while :
do operation-1 operation-2 ... operation-n done # Same as: # while true # do # ... # done
if condition
then : # Do nothing and branch ahead else take-some-action fi
: ${username=`whoami`}
# ${username=`whoami`} Gives an error without the leading : # unless "username" is a command or builtin... ----
|
Good news from afar can bring you a welcome visitor. |












스크립트를 작성하실 때 모듈방식의 접근을 권장합니다. 반복적으로 사용되는 토막 코드(snippets)는 나중에 재사용할 수 있도록 모아두세요. 이렇게 하다보면 마침내는 멋진 라이브러리가 완성되겠죠. 예를 들어, 아래의 토막 코드는 스크립트가 실행될 때 넘겨받은 파라메터(parameter)의 수가 올바른지 검사하는 겁니다.
주석라인 안에 명령문을 쓰시면 마찬가지로 주식처리 되니까 다음 명령문은 새로운 줄에 쓰세요. #로 한번 시작한 주석라인을 그 줄에서 종료시킬 방법은 없습니다.
물론, echo문에서 이스케이프 처리한 #은 주석으로 보지 않습니다. 파라메터 치환식이나 숫자 상수 표현식, 패턴 매칭 연산에서 쓰이는 #도 마찬가지죠.
