전형적인 유닉스(UNIX®) 관리자라면 시스템을 관리하면서 나름대로 자주 사용하는 유틸리티, 스크립트, 기교가 있기 마련이다. 이러한 유틸리티, 스크립트, 명령 체인 등은 관리자가 수행할 작업을 단순화시켜 준다. 일부 도구는 운영체제에 딸려오지만, 대다수 도구와 기교는 수년 동안 쌓아온 경험과 시스템 관리를 조금이라도 편하게 하려는 욕구에서 나왔다. 이 기사 연재는 다양한 유닉스 환경에서 제공하는 도구를 최대한 활용하는 방법을 살펴본다. 또한 다양한 유닉스 플랫폼에서 시스템을 단순하게 관리하는 방법도 소개한다.
다양한 유닉스 호스트를 관리한다면, 특히 각 호스트가 BSD(Berkeley Software Distribution), SVR4(UNIX System Release 4) 등 다양한 유닉스 종류와 버전을 탑재한다면, 간단한 작업에도 호스트 환경을 파악하느라 많은 시간을 보내게 되므로 시스템이 운영하는 방식에 대한 변경 사항을 확인할 수 있어야 한다.
예를 들어, ps 명령으로 동일한 정보를 얻으려면 BSD 유닉스 계열에서 지정하는 옵션과 SVR4 유닉스 계열에서 지정하는 옵션이 달라진다(자세한 내용은 시스템 관리 툴킷: 프로세스 관리 기법을 참조한다). 플랫폼이 다르면 차이는 더 크다. 단순히 명령 옵션 정도가 아니라 명령 자체가 달라지기도 한다. 사용자를 추가할 때 리눅스(Linux®)에서는 adduser 명령을 사용하지만 솔라리스에서는 useradd 명령을 사용한다.
명령행 도구를 통일할 때 취할 수 있는 접근 방식은 여러 가지다.
- 기준 플랫폼을 정한 후 다른 플랫폼을 기준 플랫폼에 맞춘다. 예를 들어, 기준 플랫폼을 솔라리스로 선택했다면, 다른 플랫폼에다 솔라리스 표준 명령과 동일한 감싸게 명령을 구현한다.
- 관리자 자신이 자주 수행하는 작업에 가장 적합한 명령어 집합을 기준으로 삼는다. 자신이 원하는 명령을 선택한 후 해당 명령이 없는 플랫폼에다 감싸게 명령을 구현한다.
- 원하는 작업을 수행하는 자신만의 스크립트 모음을 만든다. 원하는 정보를 원하는 형식으로 출력하도록
ls,ps등과 같은 일반 도구를 교체해도 좋다. 하지만 이 방법은 관리자가 원래 도구를 사용하지 않으므로 자기 스크립트가 없으면 작업을 수행하지 못한다는 단점이 있다.
위에서 어떤 접근 방식을 취하든 (원래 명령과 동일하나 이름만 다른 명령을 제공하든, 아니면 여러 명령을 조합하여 새로운 명령을 구현하든) 기존 환경에다 감싸게 계층을 올리는 방법으로는 세 가지가 있다.
- 별칭 -- 일부 셸에서만 제공하는 기능이다. 주어진 문자열을 특정한 명령으로 대체하는 단순한 기능이다.
- 셸 함수 -- 현대적인 대다수 셸에서 제공하는 기능이다. 셸 함수는 좀 더 복잡한 작업을 수행하기 쉽지만, 대신 내장 함수이므로 플랫폼 차이가 상대적으로 적은 경우에 실용적이다.
- 셸 스크립트(Shell Script) -- 구현하려는 감싸게가 특별히 복잡한 경우에는 원래 명령어 자리를 대신할 셸 스크립트를 사용하는 편이 낫다. 셸 스크립트를 사용하면 원래 명령을 대체할 새로운 명령을 좀 더 창의적으로 구현할 수 있다. 필요하다면 기존 명령을 완전히 대체하는 스크립트도 가능하다.
이제 소개한 각 방법을 구체적으로 살펴보면서 이런 방법으로 흉내낼 몇몇 예제 명령도 구현해보자.
별칭(alias) 기능은 ksh(Korn Shell), bash(Bourne-Again Shell), tcsh(TENEX C Shell), zsh(Z Shell)에서 지원한다. 명령에 일정한 옵션을 항상 지정해 놓은 상태에서 다른 옵션도 계속해서 지원하려는 경우에 가장 간단한 방법이다. 별칭 기능은 말 그대로 별칭을 생성한다. 명령에 다른 이름을 붙여도 좋고, 옵션을 추가한 후 같은 이름을 붙여도 좋다. 사용자가 별칭을 입력하면 셸은 이를 원래 문자열로 확장한다.
예를 들어, 유닉스에서 흔히 사용하는 별칭 중 하나가 ll이다. 이 명령은 ls -l과 같다(ll은 long listing을 뜻한다). 사용자가 ll을 입력할 때마다 셸은 이를 ls -l로 확장한다. 즉 사용자가 $ll a*라고 입력하면 셸은 이를 $ls -l a*로 확장한 후 실행한다.
다른 명령행 옵션도 계속해서 제대로 동작한다. 다시 말해, $ll -a는 $ls -l -a로 확장된다.
별칭이 원래 명령 이름과 똑같아도 괜챃다. 나는 ls 명령에 항상 -F 옵션을 적용하도록 ls -F에다 ls라는 별칭을 주었다. 그래서 내가 $ls를 입력할 때마다 셸은 $ls -F를 실행한다.
별칭을 설정하려면 셸이 제공하는 alias 명령을 사용한다. 별칭을 주려는 문자열은 인용부호로 묶는다. 예를 들어, ls -a에 ll이라는 별칭을 주려면 $ alias ll='ls -l'을 실행한다.
별칭 기능은 base 명령에 일정한 옵션을 항상 지정해 놓은 상태에서 다른 (플랫폼에 종속적인) 옵션도 계속해서 지원하려는 경우에 아주 유용하다.
좋은 예가 ps 명령이다. 앞서 언급했듯이, SVR4 계열과 BSD 계열은 ps 명령에 주는 옵션이 다르다. 이 연재 첫 번째 기사인 시스템 관리 툴킷: 프로세스 관리 기법에서는 ps에 각 유닉스 계열에 맞는 옵션을 지정해 동일한 형식을 출력했다. 이 때 별칭을 사용하면 (추가로 옵션을 지정하는 기능은 그대로 유지하면서) 다른 유닉스 계열에서 동일한 명령으로 동일한 형식을 출력할 수 있다. 예를 들어, BSD에서는 Listing 1과 같이 별칭을 지정한다.
Listing 1. BSD 계열에서 별칭 지정
$ alias ps='ps -o pid,ppid,command'
|
반면에 SVR4 계열에서는 Listing 2와 같이 별칭을 지정한다.
Listing 2. SVR4 계열에서 별칭 지정
$ alias ps='ps -opid,ppid,cmd
|
이제 두 시스템에서 ps를 실행하면 (다소 차이는 있지만) 표준 형식으로 결과를 출력한다. 별칭을 지정하기 전과 마찬가지로 옵션을 추가해도 문제가 없다. 예를 들어, 별칭을 지정한 ps 명령으로 시스템에서 실행 중인 전체 프로세스 목록을 출력하려면 -A 옵션을 추가한다. Listing 3은 BSD 계열에서 -A 옵션으로 출력한 결과다. 여기서는 Mac OS X 플랫폼을 사용했다.
Listing 3. BSD 계열에서
-A 옵션으로 출력한 결과
$ ps -A
PID PPID COMMAND
1 0 /sbin/launchd
23 1 /sbin/dynamic_pager -F /private/var/vm/swapfile
27 1 kextd
32 1 /usr/sbin/KernelEventAgent
33 1 /usr/sbin/mDNSResponder -launchdaemon
34 1 /usr/sbin/netinfod -s local
35 1 /usr/sbin/syslogd
36 1 /usr/sbin/cron
37 1 /usr/sbin/configd
38 1 /usr/sbin/coreaudiod
39 1 /usr/sbin/diskarbitrationd
...
|
Listing 4는 SVR4 계열에서 -A 옵션으로 전체 프로세스 목록을 출력한 결과다. 여기서는 Gentoo 리눅스 플랫폼을 사용했다.
Listing 4. SVR4 계열에서
-A 옵션으로 출력한 결과
$ ps -A
PID PPID CMD
1 0 init [3]
2 1 [migration/0]
3 1 [ksoftirqd/0]
4 1 [watchdog/0]
5 1 [migration/1]
6 1 [ksoftirqd/1]
7 1 [watchdog/1]
8 1 [events/0]
9 1 [events/1]
10 1 [khelper]
11 1 [kthread]
14 11 [kblockd/0]
15 11 [kblockd/1]
16 11 [kacpid]
...
|
이 기사에서 소개하는 셸 스크립트나 셸 함수와 비슷하게, 별칭으로 새로운 명령을 생성해도 좋다. 새로운 명령이 각 플랫폼마다 동일한 형식으로 결과를 내놓도록 정의하면 편리하다. 다시 ps 명령을 예로 들어, 모든 플랫폼에서 ps-all이라는 명령으로 프로세스 전체 목록을 얻으려고 한다면, ps-all이라는 별칭을 생성해 플랫폼마다 지정하는 옵션을 조정한다.
이런 별칭을 설정하기 가장 좋은 위치는 셸 초기화 스크립트 안이다. .ksh, .profile, .bashrc 등과 같은 셸 초기화 스크립트는 사용자가 로그인할 때 실행되어 셸 환경을 초기화한다. 모든 사용자에게 별칭을 제공하려면 별칭 정의 파일을 /etc나 /uer/local 아래 넣은 후 각 사용자 초기화 스크립트에서 참조한다.
별칭은 개별 명령에 일정한 명령행 옵션을 항상 지정하려는 경우에 가장 유용하다. 물론 파이프 등과 같이 명령 체인을 별칭으로 정의하는 경우도 있다. 하지만 명령 체인을 별칭으로 정의하면 마지막 명령에만 추가로 옵션을 지정할 수 있다. 복잡한 명령 체인은 인라인 셸 함수를 사용하는 편이 더 낫다.
대다수 셸은 함수를 지원한다. 셸 함수란 기본적으로 셸 명령과 다른 셸 함수를 실행할 수 있는 작은 스크립트다. 셸 함수는 명령행 인수 등 셸 스크립트가 제공하는 기능을 거의 대부분 제공하면서도 기본 셸 내에 정의되므로 성능이 뛰어나고 사용하기도 쉽다.
여러 명령을 조합할 때 명령행 인수가 꼭 필요한 경우가 생기지만, 앞서 보았듯이 별칭은 명령행 인수를 지원하지 않는다. 예를 들어, killall 명령은 가장 기본적으로 주어진 문자열에 일치하는 프로세스를 모두 죽인다. 모든 플랫폼에서 지원하지는 않지만 유용한 명령이므로 있으면 편리하다.
솔라리스에서도 killall 명령을 제공한다. 하지만 시스템을 종료하는 과정에서 모든 프로세스를 죽일 때 사용한다. 솔라리스 호스트에서 아파치 프로세스를 모두 죽이려고 killall 명령을 뜻하지 않게 실행했다가 실제로는 시스템 자체를 죽였다고 상상해보라.
모든 호스트에서 (killall이라는 이름을 사용하든 다른 이름을 사용하든) 프로세스 이름을 받아서 해당하는 프로세스를 모두 죽이는 명령을 구현한다면 위와 같이 의도하지 않은 심각한 사태를 방지할 수 있다. 또한 원래 killall 기능을 제공하지 않는 호스트에서도 유용하게 사용할 수 있다.
그러려면 실행 중인 프로세스에서 주어진 문자열에 만족하는 프로세스만 추출하여 각각에게 kill 명령으로 KILL 시그널을 보내야 한다. Listing 5에서 보듯이, 명령행에서는 (KILL 시그널을 보내기 위해) 여러 명령을 파이프로 연결하면 된다.
Listing 5. killall 명령을 대체하는 명령
$ ps -ef|grep gcc|awk '{ print $2; }'|xargs kill -9
|
위 예제에서는 grep 인수로 지정한 문자열(여기서는 gcc)과 ps 결과 중 필요한 프로세스 ID 열이 중요하다. 위 명령은 솔라리스와 대다수 SVR4 유닉스 계열에서 통한다.
위 예제에서는 별칭을 사용하지 못한다. 명령행 인수로 넘기려는 정보, 즉 gcc가 명령 체인 가운데 있기 때문이다. 별칭은 명령 체인 끝에만 옵션을 추가할 수 있다. 하지만 인라인 셸 함수는 이런 목적에 아주 적합하다.
Bourne 셸 문법을 지원하는 셸에서는 (예를 들어, bash나 zsh에서는) Listing 6과 같은 방식으로 셸 함수를 정의한다.
Listing 6. 함수 정의
function NAME()
{
# 여기에 필요한 내용을 채운다
}
|
전형적인 셸 스크립트와 마찬가지로, 셸 함수로 넘어오는 인수는 $1, $2로 참조한다. 그러므로 killall처럼 문자열 기반 시그널 방식으로 동작하는 셸 함수를 Listing 7과 같이 정의할 수 있다.
Listing 7. killall과 같은 시그널 방식으로 동작하는 셸 함수 정의
function killall()
{
ps -ef|grep $1|awk '{ print $2; }'|xargs kill -9
}
|
awk 명령에서 $2는 단일 인용부호로 둘러싸여 있으므로 확장되지 않는다. 즉 여기서는 셸 함수로 넘어오는 인수 $2가 아니라 grep 결과에서 두 번째 열을 가리킨다.
별칭과 마찬가지로 셸 함수를 지정하기 가장 좋은 위치는 셸 초기화 스크립트 안이다. 단, 셸 함수는 셸 내에서만 사용이 가능하다는 제약이 있다.
셸 함수는 길이에 제한이 없지만 필요 이상으로 긴 경우가 흔하다. 아주 복잡한 명령을 흉내내거나 옵션을 분석하여 현재 시스템에 맞는 동작을 취하는 경우는 인라인 함수보다 셸 스크립트가 더 낫다.
명령행 인터페이스를 통일하는 가장 쉽고 적절한 방법은 원래 명령을 감싸는 셸 스크립트다. 셸 스크립트를 사용하면 다양한 옵션과 설정을 지원할 수 있다.
예를 들어, useradd와 adduser 명령은 명령 옵션이 동일하다. 사용자 ID나 그룹을 지정할 때 한 문자로 옵션을 지정한다. 리눅스에서 $ adduser -u 1000 -G sales,marketing mcbrown 명령은 솔라리스에서 $ useradd -u 1000 -G sales,marketing mcbrown 명령과 똑같다.
그러나 리눅스는 --uid와 --groups 같은 옵션도 추가로 제공한다. 각각 -U와 -G 옵션과 동일하다. 하지만 솔라리스는 이러한 옵션을 제공하지 않는다. 솔라리스에서 리눅스 방식으로 adduser 명령을 사용하려면 adduser라는 이름으로 셸 스크립트를 작성한다. 셸 스크립트는 실제로 솔라리스 useradd 명령을 실행한다.
Listing 8은 adduser 명령과 useradd 명령을 모두 지원하는 셸 스크립트다.
Listing 8. 감싸게로서 사용자를 추가하는 간단한 셸 스크립트
#!/bin/bash
# -*- shell-script -*-
for i in $*
do
case $i in
--uid|-u) OPT_UID=$2; shift 2;;
--groups|-G) OPT_GROUPS=$2; shift 2;;
--gid|-g) OPT_GROUP=$2; shift 2;;
--home-dir|-d) OPT_HOMEDIR=$2; shift 2;;
--shell|-s) OPT_SHELL=$2;shift 2;;
--non-unique|-o) OPT_NONUNIQUE=1;shift 2;;
--comment|-c) OPT_COMMENT=$2;shift 2;;
esac
done
OPTS=""
if [ -n "$OPT_$HOMEDIR" ]
then
OPTS="$OPTS -d $OPT_HOMEDIR"
fi
if [ -n "$GROUP" ]
then
OPTS="$OPTS -g $OPT_GROUP"
fi
if [ -n "$OPT_GROUPS" ]
then
OPTS="$OPTS -G $OPT_GROUPS"
fi
if [ -n "$OPT_SHELL" ]
then
OPTS="$OPTS -s $OPT_SHELL"
fi
if [ -n "$OPT_UID" ]
then
OPTS="$OPTS -u $OPT_UID"
fi
if [ -n "$OPT_COMMENT" ]
then
OPTS="$OPTS -c \"$OPT_COMMENT\""
fi
if [ -n "$OPT_NOUNIQUE" ]
then
OPTS="$OPTS -o"
fi
CMD=adduser
UNAME=`uname`
case $UNAME in
Solaris) CMD=useradd;break;;
esac
$CMD $OPTS $*
|
위 스크립트에서 핵심은 foreach 루프다. foreach 루프는 $*로 명령행 인수를 하나씩 참조한다. 각 옵션마다 case 문 내에서 긴 형식인지 짧은 형식인지 확인한 후 변수를 설정한다. 명령행 옵션은 $1로 참조한다. 옵션 다음에는 보통 값이 따라오므로 (예를 들어, -U 다음에는 사용자 ID가 따라오므로) 이 값은 $2로 참조하여 변수에 할당한다.
옵션을 인식한 후에는 shift 문으로 $* 변수에서 해당 옵션과 값을 제거한다. 그래야 루프가 $*에서 이미 인식한 명령행 인수를 다시 인식하지 않고 다음 옵션을 제대로 인식한다.
명령행 인수와 값을 모두 인식하여 추출한 후에는 새 옵션을 만들어 새로운 명령에 제공한다. useradd와 adduser 명령 모두가 짧은 옵션을 지원하므로, 새 명령 옵션은 짧은 옵션을 사용한다. 스크립트에서 foreach 루프 다음에 나오는 if 문은 각각 해당 옵션이 지정되었는지 확인한 후 명령행에 옵션을 추가한다. 이 때 이중 인용부호를 사용한다는 사실에 주목한다. 혹시라도 인용부호를 사용하는 인수가 있다면 이를 보존하기 위해서다.
위 스크립트를 두 플랫폼에 모두 설치하면, 어느 플랫폼에서든 원하는 명령과 원하는 옵션으로 사용자를 추가할 수 있다. Listing 9처럼, 긴 옵션과 짧은 옵션을 섞어 사용해도 문제가 없다.
Listing 9. 사용자 추가
$ adduser.sh --homedir /etc -g wheel --shell /bin/bash -c "New user" mcbrown
|
다른 명령을 감싸는 스크립트를 구현할 때도 같은 원리가 적용된다. 똑같은 옵션을 제공해도 되지만, 필요하다면 인수 이름이나 옵션을 바꾸거나 동등한 표현을 제공해도 문제가 없다.
스크립트를 원래 명령 이름으로 제공하려면 (예를 들어, 위 스크립트를 adduser라는 이름으로 제공하려면) 스크립트 디렉터리를 PATH 경로에 포함시키되 원래 adduser 디렉터리 앞에 넣어야 한다. 예를 들어, 직접 작성한 스크립트가 /usr/local/compat에 들어 있다면 $ PATH=/usr/local/compat:$PATH라고 설정한다.
단일 환경을 지원하기 위해 이제까지 설명한 세 가지 방법(별칭, 인라인 스크립트, 셸 스크립트) 중 어느 방법 혹은 어느 조합을 사용했든, (각 시스템마다 스크립트를 따로 구현하는 대신) 스크립트 하나에서 모든 환경을 고려하는 편이 바람직하다. 그런 다음 새로운 시스템을 설정할 때 스크립트를 그대로 복사해 사용한다.
그러려면 스크립트 내에서 명령행 도구와 셸 흐름 제어 명령(if나 case)을 사용해 필요한 옵션을 선택한다. 이 때 두 가지 도구가 유용한데, 하나는 호스트를 식별하는 도구(hostname 또는 uname)이고, 다른 하나는 플랫폼을 식별하는 도구(uname)다.
uname은 기본적으로 Linux나 Solaris처럼 운영체제 이름을 출력한다. 예를 들어, ps 예제에서 현재 시스템에 따라 별칭을 다르게 정의하려면 case 문을 사용한다. Listing 10을 참조한다.
Listing 10. uname 결과
UNAME='uname'
case "$UNAME" in
FreeBSD|NetBSD|Darwin)
alias ps='ps -o pid,ppid,command'
break
;;
Solaris|Linux)
alias ps='ps -o pid,ppid,cmd'
break
;;
esac
|
스크립트 내에서 특정한 순서를 선택할 때도 같은 방법을 사용한다.
인라인 셸 함수를 사용할 때도 시스템마다 함수를 별도로 정의해 쓰기보다 위와 같은 소스 파일 하나에서 현재 시스템에 맞춰 적절한 함수를 선택하는 편이 바람직하다.
명령행 인터페이스를 통일하면 시스템 관리가 한층 수월해진다. 각 시스템에 적합한 명령과 옵션을 찾느라 시간을 소모할 필요가 없어진다. 인터페이스를 통일할 때 각 명령마다 어떤 방법을 사용할지는 전적으로 명령 자체와 용도에 달려있다.
개별 명령에서 명령행 옵션을 추가로 지정하려는 경우는 별칭이 가장 적합하다. 좀 더 복잡한 작업은 현재 셸 환경에 내장하기 쉬운 인라인 셸 함수가 적합하다. 스크립트는 여러 단계로 나눠지는 복잡한 작업이나 셸 환경을 변경하지 않고 명령과 옵션을 지원하려는 경우에 적합하다.
세 가지 방법 모두 장점이 명확하지만, 원래 시스템이 제공하는 명령과 옵션에서 너무 멀어지면 그만큼 위험도 존재한다. 시스템이 실패해서 스크립트를 사용하지 못하는 상황을 떠올려 보라. 따라서 위에서 제시한 방법은 기존 방법을 보완하고 연장할 뿐 대체해서는 안 된다.
교육
-
시스템 관리 툴킷: 이 연재에 속하는 다른 기사를 읽어본다.
-
Working in the bash shell (developerWorks, 2006년 5월): 이 튜토리얼은 Bash(Bourne Again) 셸을 사용하는 기본적인 방법을 소개한다. 또한 사용자 환경에 맞게 설정을 변경하는 방법도 살펴본다.
-
Making UNIX and Linux work together(developerWorks, 2006년 4월): 리눅스 시스템과 유닉스 시스템을 함께 사용하는 방법을 소개한다.
-
Bash: Bourne 셸과 문법은 비슷하나 기능이 더 많은 셸이다. 별칭(alias), 작업 제어(job control), 파일/디렉터리 이름 자동 완성(auto-completion) 등 다양한 기능을 제공한다.
-
AIX®와 유닉스 기사: Martin Brown이 쓴 다른 기사와 튜토리얼을 살펴본다.
- AIX/UNIX 라이브러리는 다음 주제에 관련한 정보를 제공한다.
-
AIX와 유닉스: 한국 developerWorks에서 운영하는 AIX와 유닉스 영역이다. AIX와 유닉스 사용자를 위해 기사, 튜토리얼, 기술 자료를 제공한다.
-
AIX와 UNIX 입문: AIX와 유닉스 초보자에게 좋은 정보를 제공하는 사이트다.
-
AIX 5L™ 위키: AIX와 관련한 기술 정보를 공동으로 수집하고 관리하는 공간이다.
-
Safari 온라인 서점: 다양한 기술 서적과 기술 자료를 제공한다.
-
developerWorks 기술 행사와 웹 캐스트
-
Podcasts: IBM 기술 전문가들과 만날 수 있다.
제품 및 기술 얻기
-
IBM 평가판 소프트웨어: 다양한 응용 프로그램 개발 도구와 미들웨어 제품을 developerWorks에서 다운로드해 사용해보자.
토론
-
developerWorks 블로그를 읽고 developerWorks 공동체에 참여하자.
-
AIX와 유닉스 포럼에 참여하자.
Martin Brown은 7년 넘게 기술 필자로 활약해왔다. Brown은 다양한 주제를 다루는 수 많은 책을 집필했고 기사를 작성했다. Brown은 펄, 파이썬, 자바(Java™), 자바스크립트, 베이직, 파스칼, 모듈라-2, C, C++, 레볼, gawk, 셸 스크립트, 윈도우(Windows®), 솔라리스, 리눅스, BeOS, 맥 OS X을 비롯하여 웹 프로그래밍, 시스템 관리, 통합에 이르리까지 다양한 개발 언어와 플랫폼을 경험했다. Brown은 마이크로소프트(Microsoft®) SME(Subject Matter Expert)이며 ServerWatch.com, LinuxToday.com, IBM developerWorks에 주기적으로 기고한다. Brown은 또한 컴퓨터월드, 애플 블로그, 기타 사이트에 주기적으로 블로그 기사를 올린다. 연락 주소는 Brown이 운영하는 웹 사이트를 참조하기 바란다.