1) 데몬의 종류

데몬이란 시스템에 백그라운드 모드로 수행중이며 어떤 사건, 즉 이벤트를 기다리거나 주기적으로 주어진 작업을 수행하기 위하여 대기하고 있는 프로세스를 의미한다. 일반적으로 데몬프로세스는 이름끝에 'd'자가 붙는다.
기본적으로 리눅스에는 여러 기능과 서비스를 제공하기 위한 다양한 데몬 프로세스들이 시스템에서 돌아가고 있다.
이런 서비스 데몬들의 실행여부는 레드햇 리눅스 계열에서 ntsysv 커맨드로 조정하면 된다. 리눅스상에서 현재 실행되고 있는 데몬의 현황을 보려면 pstree 커맨드를 쓴다.

데몬의 실행방식은 크게 stand-alone방식과 xinetd(슈퍼데몬)방식이 있다.
 # Stand-alone방식
(말뚝보초처럼 항상실행대기)
이 방식의 데몬들은 데몬이 시작되면 말 그대로 자기 스스로 시스템에서 상주하며 클라이언트 요청에 응답을 보내주는 방식으로 보통 아파치 데몬처럼 클라이언트의 요청이 잦은 데몬들이 이런 방식으로 실행된다. 스탠드 얼론방식의 데몬을 확인하려면 쉘상에서 pstree 명령을 내리면 데몬들을 트리형식으로 보여준다. 스탠드 얼론 방식의 데몬들은 xinetd방식보다는 클라이언트 요청에 신속하게 대응한다는 장점은 있으나 이런 스탠드 얼론 데몬들이 시스템에서 많이 생성되게되면 시스템의 자원낭비가 심해지게 되고 퍼포먼스도 떨어지게 된다.

 # inetd방식
(클라이언트 요청이 있을때만 실행)
스탠트 얼론처럼 자기 스스로 독립적으로 시스템에 실행되는게아니라 xinetd라는 수퍼데몬이 시스템에서 대기하고 있다고 클라이언트 요청이 들어오면 xinetd데몬에 의해 해당 서비스를 제공해줄 데몬을 실행시키는 방식이다. telnet이나 ftp같이 비교적 클라이언트 요청의 빈도가 낮은 서비스들은 이런 xinetd데몬이 시스템 활용측면에서는 유리하다. 또한 tcp wrapper를 써서 보안을 손쉽게 설정할 수 있는 것도 장점이다. 그러나 전술한 아파치 데몬같이 클라이언트의 자잘한 요청이 많은 서비스에 대해서는 inetd방식이 시스템효율을 도리어 떨어드리게된다. xinetd데몬은 스탠드 얼론방식으로 실행되는 슈퍼데몬이며 서비스 요청시 xinetd데몬의 차일드 프로세스로 다른 데몬이 실행된다. 이를 확인하려면 pstree로 xinetd데몬을 확인하고 다른 한텀창에서 telnet이나 ftp로 자신의 호스트에 접속해보라. 그럼 xinetd데몬에서 차일드 프로세스로 해당 데몬이 실행되는 것을 확인할 수 있다.

 

 

2) /etc/rc.d/init.d/ 데몬 설명

/etc/rc.d/init.d/autofs
오토마운트 파일시스템(Automount Filesystem) 데몬 스크립트입니다. 파일시스템 사용을 요구받았을 경우에는 마운트를 하고 파일시스템 사용이 끝났을 경우에는 마운트를 해제하게 됩니다.

/etc/rc.d/init.d/crond
cron 데몬 스크립트입니다. 여러분들께서도 잘 아시는 바와 같이 cron은 특정한 시간에 지정된 작업을 주기적으로 하도록 하는 리눅스 작업관리자 데몬입니다. 이런 cron데몬을 시작, 재시작 또는 종료시키는 스크립트입니다.

/etc/rc.d/init.d/functions
리눅스 시스템의 /etc/rc.d/init.d/디렉토리내의 거의 모든 쉘 스크립트에서 사용되어지는 함수를 내장하고 있는 파일입니다.

/etc/rc.d/init.d/gpm
GPM은 Midnight Commander와 같은 텍스트기반의 리눅스 어플리케이션에서 마우스의 복사 및 붙이기 기능과 콘솔상에서의 마우스 팝업기능을 제공하기 위한 데몬입니다.

/etc/rc.d/init.d/halt
halt는 시스템이 런레벨(runlevel) 0또는 런레벨(runlevel)6로 실행이 될 때에 init 프로세스에 의해 실행되는 스크립트로서 현재 시스템에 실행된 모든 프로세스를 죽이고 모든 파일시스템의 마운트를 해제한 후에 시스템을 종료(halt)시키거나 재부팅(reboot)합니다.

/etc/rc.d/init.d/iptables
iptables는 /etc/sysconfig/iptables에 미리 정의된 룰에 따라서 리눅스 서버의 방화벽을 실행 및 중지하기 위한 스크립트입니다. 즉, iptables로 구현하는 패킷필터링(packet filtering)방화벽입니다.

/etc/rc.d/init.d/irda
IrDA (Infrared Data Association)데몬 스크립트로서 IrDA는 무선통신, 적외선 통신을 위한 산업표준입니다. IrDA는 최근 거의 모든 장치들(laptops, LAN아답터, PDA, 프린터, 휴대폰등)에 사용될 수 있으며 9600bps에서 4Mbps의 속도범위를 가집니다.

/etc/rc.d/init.d/isdn
isdn 서비스를 시작하거나 종료하는 스크립트입니다.

/etc/rc.d/init.d/kdcrotate
/etc/krb5.conf에 정의되어 있는 KDC 리스트를 회전시키는 스크립트입니다.

/etc/rc.d/init.d/killall
현재 실행중인 서비스들을 대상으로 모두 중지시키는 스크립트입니다. 따라서 이 스크립트는 주로 시스템을 중지하거나 재시작할 때에 실행되는 스크립트입니다.

/etc/rc.d/init.d/kudzu
kudzu데몬은 새로운 하드웨어나 또는 변경된 하드웨어를 조사하여 설정하는 데몬입니다. 또한 kudzu데몬은 현재의 하드웨어 정보와 /etc/sysconfig/hwconf에 저장해 하드웨어 정보를 비교하여 변경된 하드웨어 또는 해로운 하드웨어가 존재하는가를 알아냅니다.
즉, 시스템 하드웨어의 변경 또는 새로운 하드웨어의 추가장착등과 같은 하드웨어에 대해 조사하는 kudzu데몬을 시작시키거나 종료하는 스크립트입니다.

/etc/rc.d/init.d/netfs
Network Filesystem(NFS)와 SMB(Lan Manager/Windows ), NCP(NetWare)를 마운트 시키거나 마운트 해제하는 데몬 스크립트입니다.

/etc/rc.d/init.d/network
리눅스 서버의 통신을 가능하도록 하기 위하여 관련 파일에 설정되어 있는대로 네트워크 설정을 하거나 중단하는 스크립트입니다.

/etc/rc.d/init.d/nfs
NFS 서비스 데몬을 시작(종료, 재시작)하는 스크립트입니다. NFS는 TCP/IP 네트워크상에서 파일공유를 위해 많이 사용되고있는 프로토콜입니다. 이 스크립트로 NFS서비스를 실행하면 /etc/exports파일의 설정대로 NFS서버기능을 할 수 있도록 설정합니다.

/etc/rc.d/init.d/nfslock
NFS파일 락킹서비스를 시작(종료,재시작)하는 스크립트입니다.

/etc/rc.d/init.d/nscd
네임서비스 캐쉬데몬(Name Service cache Daemon)을 시작하는 스크립트입니다. NSCD데몬은 가장일반적인 네임서비스에 대한 캐쉬기능을 제공하는 데몬으로서 /etc/passwd, /etc/group, /etc/hosts파일등에 대한 캐쉬정보를 가지고 있습니다. NSCD데몬의 설정파일은 /etc/nscd.conf입니다.

/etc/rc.d/init.d/pcmcia
pcmcia 데몬을 시작(종료)하기 위한 스크립트입니다. PCMCIA는 일반적으로 노트북등에서 사용되는 장치(모뎀, 이더넷등)를 지원하기 위한 것입니다.

/etc/rc.d/init.d/portmap
RPM 포트맵퍼(portmap daemon)을 시작(종료)하는 스크립트입니다. 일명 포터맵퍼(portmapper)라고 합니다. 이 portmapper는 NFS와 NIS 등과 같은 프로토콜이 사용되는 RPC연결을 제어하는 역할을 합니다. 따라서 이 portmapper는 RPC매카니즘을 사용하는 프로토콜이 탑재되어있어 서버로 사용되는 시스템에서는 반드시 실행되어있어야 합니다.

/etc/rc.d/init.d/random
리눅스 시스템에서의 난수사용을 위하여 난수발생데몬인 random을 시작시킵니다.

/etc/rc.d/init.d/rawdevices
하드드라이브 파티션과 같은 블록장치에 rawdevice를 할당하는 스크립트입니다. 잘아시는 Oracle과 같은 어플리케이션에서 사용될 수 있습니다. /etc/sysconfig/rawdevices파일을 수정, 편집함으로써 rawdevice에 블록장치매핑과 할당을 할 수 있습니다.

/etc/rc.d/init.d/single
시스템 실행레벨(runlevel) 1로 가고자 할 때 사용하는 스크립트입니다. 이 스크립트가 실행되면 시스템의 모든 프로세스들을 죽인 다음 시스템을 실행레벨 1상태로 만들어 줍니다. 실행레벨 1상태는 관리자(root) 전용모드입니다.

/etc/rc.d/init.d/sshd
sshd라는 OpenSSH서버데몬을 실행(중지)하는 스크립트입니다. telnet과는 달리 ssh는 암호화하여 통신을 하기 때문에 보안을 위한 통신방법으로 주로 사용됩니다. Telnet처럼 원격서버 로그인을 하는 ssh를 사용하려면 sshd데몬이 실행되어 있어야합니다. 이 스크립트는 sshd데몬을 실행시켜주는 스크립트입니다. 참고로 ssh는 TCP 22번과 UDP 22번을 사용합니다.

/etc/rc.d/init.d/syslog
syslog는 리눅스 시스템의 필수적인 로깅시스템입니다. 이런 로깅시스템을 시작(종료)하는 스크립트입니다. 이 스크립트에 의해 로깅시스템의 데몬인 syslogd가 실행되면 /etc/syslog.conf파일을 참조하여 각 설정에 해당하는 조건이 되면 지정된 로그파일에 로그메시지를 기록합니다. 로그파일이 기록되는 디렉토리는 /var/log/이며 대표적인 로그파일로는 /var/log/messages, /var/log/secure, 그리고 /var/log/maillog등이 있습니다. 이 스크립트에 의해 실행되는 데몬은 /sbin/syslogd와 /sbin/klogd가 있습니다.
/sbin/syslogd는 로깅시스템의 주된 그리고 일반적인 로그메시지를 기록하는 주데몬이며 klogd는 커널메시지를 기록하는 커널로그데몬입니다. 그리고 서버의 로그메시지를 원격서버에 실시간으로 저장하고자 한다면 syslogd데몬 실행시에 -r옵션을 사용하여 실행하시기 바랍니다. 참고로 syslogd는 UDP 514번을 사용합니다.

/etc/rc.d/init.d/xinetd
리눅스 서버의 인터넷 수퍼데몬인 XINETD데몬을 실행시키거나 중지시키는 데몬입니다. Xinetd는 TCP_Wrapper와 함께 사용되며 대부분의 인터넷 서비스들을 관장하며 telnet이나 ftp등과 같은 특정 서비스요청을 사용자프로세스와 서비스데몬을 연결시켜주는 역할을 합니다.

/etc/rc.d/init.d/ypbind
NIS/YP 클라이언트 시스템에서 실행되는 데몬으로서 NIS 도메인(domain)에 바인드시키는 역할을 합니다. NIS클라이언트에서 수행되기 위하여 반드시 glibc 기반 시스템에서 실행되어야합니다. 하지만 NIS를 사용하지 않는 시스템에서 실행되어져서는 안됩니다.


'OS > LINUX' 카테고리의 다른 글

Linux서버에 Memcached 설치/실행 하기  (0) 2011.06.09
리눅스의 런레벨(Run level)  (0) 2011.06.09
[/etc/rc.d/init.d/*] 데몬 실행 스크립트의 이해  (0) 2011.06.09
/dev/null 다시 만들기  (0) 2011.06.04
/dev/zero와 /dev/null  (0) 2011.06.04

1. 심볼릭링크

 

윈도우XP에서 한글2005를 실행시킬때의 구조를 살펴보자.

설치시 특별히 디렉토리를 변경하지 않았다면, 실행파일은 다음의 위치에 있다.

 

    C:\HNC\Hwp65\hwp.exe

 

그러나, 우리는 탐색기를 열어서.. C:\HNC\Hwp65\hwp.exe 파일을 더블클릭해서 직접 실행하지는 않는다.

보통은, '시작>프로그램>한글과컴퓨터>한글2005>한글 2005' 를 실행하거나,

아니면, 바탕화면의 단축아이콘을 더블클릭한다.

 

단.축.아.이.콘...!!

윈XP에서의 단축아이콘이 리눅스에서의 심볼릭링크이다.

 

 

2. /etc/rc.d/init.d/* 파일들의 이해

 

MS윈도우즈에서 프로그램을 설치하면 대부분 'C:\Program Files' 안에 설치된다.

물론, 다른 디렉토리에 설치해도 된다.

 

리눅스도 각각의 프로그램들(데몬들)을 원하는 디렉토리에 설치할 수 있다.

rpm으로 설치한다면, 특정 디렉토리가 지정되어 있겠지만...

소스로 설치할 때는 원하는 디렉토리에 설치한다.

apache를 소스설치할 때, --prefix 옵션으로 설치 디렉토리를 지정할 수 있는 것처럼 말이다.

물론, 관습적으로 대부분 /usr/local/ 밑에 설치하는 경향이 많다.

 

이렇게 각각의 디렉토리에 설치된 각각의 데몬들을 실행하거나, 중지하는 등의 관리를 할 때는 상당히 불편하다.

각 데몬이 어디에 설치되어 있는지 모두 알아야 하기 때문이다.

 

생각해보자.

어느누가 네임서버 데몬을 실행시키는데...

 

    # /usr/sbin/named

 

와 같이 데몬의 실행파일을 직접 실행시키겠는가..??

 

보통은...

 

    # /etc/rc.d/init.d/named start

 

와 같이 실행시킨다...

 

그렇다. 각 데몬의 실행파일들이 어디에 있는지와 상.관.없.이,

관리의 편의를 위하여....

각 데몬들을 실행할 수 있게 만든 쉘스크립트 파일모아놓고 관리를 한다.

그 디렉토리가 /etc/rc.d/init.d/ 디렉토리이며,

/etc/rc.d/init.d/ 밑의 모든 파일들은 vi 편집기로도 열리는, 쉘스크립트 파일들이다.

 

마치, 윈XP에서 각 프로그램들을 하드에 설치하고..

실행할 때는 '시작>프로그램>....' 에서 실행하는 것과 비슷하다.

 

 

3. 런레벨의 이해

 

기본적으로 다음 두 가지는 알고 있다고 가정하자.

 

① 런레벨 종류

    - 런레벨 1 : 싱글모드 부팅. MS윈도우즈의 안전모드와 비슷

    - 런레벨 3 : 콘솔모드로 부팅

    - 런레벨 5 : X 윈도우로 부팅

    - 런레벨 6 : 계속 재부팅만 한다.

 

② 런레벨 수정

    - /etc/inittab 파일에서 다음 항목의 숫자를 직접 수정함으로 변경 가능

      id:3:initdefault:

 

이제, 윈XP의 '시작>프로그램>시작프로그램' 을 생각해 보자.

시작프로그램에 특정 프로그램을(프로그램의 단축아이콘을) 등록해 놓으면,

컴퓨터를 켤 때 그 프로그램이 자동으로 실행된다.

 

리눅스도 시작할 때 자동으로 실행될 프로그램(=데몬)을 등록할 수 있다.

윈XP에서는 '시작프로그램'에 등록하지만, 리눅스는 디렉토리에 심볼릭링크를 만들어서 등록한다.

 

    - /etc/rc.d/init.d/rc3.d   : 런레벨 3으로 부팅될 때 자동실행할 데몬을 등록하는 곳

    - /etc/rc.d/init.d/rc5.d   : 런레벨 5로 부팅될 때 자동실행할 데몬을 등록하는 곳

 

필자는 런레벨 3으로 부팅하며, 다음과 같은 심볼릭링크 파일들을 볼 수 있다.

 

    # cd /etc/rc.d/rc3.d/
    # ls -al
    drwxr-xr-x    2 root     root         4096 12월 15 10:32 ./
    drwxrwxr-x   10 root     root         4096 12월 15 19:02 ../
    lrwxrwxrwx    1 root     root           13 12월 15 19:01 K15gpm -> ../init.d/gpm*
    lrwxrwxrwx    1 root     root           20 12월 15 19:02 K44rawdevices -> ../init.d/rawdevices*
    lrwxrwxrwx    1 root     root           18 12월 15 19:02 K45arpwatch -> ../init.d/arpwatch*
    lrwxrwxrwx    1 root     root           15 12월 15 19:02 K75netfs -> ../init.d/netfs*
    lrwxrwxrwx    1 root     root           15 12월 15 10:30 K95kudzu -> ../init.d/kudzu*
    lrwxrwxrwx    1 root     root           17 12월 15 19:02 S10network -> ../init.d/network*
    lrwxrwxrwx    1 root     root           16 12월 15 19:01 S12syslog -> ../init.d/syslog*
    lrwxrwxrwx    1 root     root           18 12월 15 19:00 S17keytable -> ../init.d/keytable*
    lrwxrwxrwx    1 root     root           16 12월 15 19:02 S20random -> ../init.d/random*
    lrwxrwxrwx    1 root     root           17 12월 15 19:02 S29sysstat -> ../init.d/sysstat*
    lrwxrwxrwx    1 root     root           15 12월 15 10:32 S55named -> ../init.d/named*
    lrwxrwxrwx    1 root     root           16 12월 15 19:02 S56xinetd -> ../init.d/xinetd*
    lrwxrwxrwx    1 root     root           15 12월 15 19:02 S90crond -> ../init.d/crond*
    lrwxrwxrwx    1 root     root           11 12월 15 19:02 S99local -> ../rc.local*
    lrwxrwxrwx    1 root     root           23 12월 15 19:02 S99oops-firewall ->
../init.d/oops-firewall*

 

디렉토리 안을 살펴보니, 모두 심볼릭링크 파일이라는 것을 알 수 있다.

심볼릭링크 파일은 제일 앞의 퍼미션을 나타내는 부분이 'l' 로 시작되며,

어떤 파일을 심볼릭링크했는지 화살표로 표시해 준다.

 

살펴보면, 모든 심볼릭링크의 원본파일들이 상위 디렉토리(../)의 init.d 디렉토리 내의 파일들,

, /etc/rc.d/init.d/파일들 이라는 것을 볼 수 있다.

따라서, 각 데몬의 실행파일을 관리하는 스크립트 파일들을 심볼릭링크한 것이다.

 

/etc/rc.d/rc3.d/ 밑의 파일들의 파일명을 살펴보면 다음과 같은 형식이다.

 

  S[혹은 K]{숫자}{데몬파일명}

 

S 로 시작되는 링크파일은 부팅할 때 시작(Start)되는 스크립트파일이다.

K 로 시작되는 링크파일은 부팅할 때 시작되지 않는(Kill) 스크립트파일이다.

 

{숫자}는 실행되는 우선순위를 나타낸다.

crond 데몬보다, named 데몬이 중요하고 꼭 실행되어야 한다면... crond 링크보다 숫자를 낮은 숫자로 하면 된다.

예전에는 컴퓨터(서버)의 사양이 좋지 않아, '어떻해도 가장 중요한 데몬'을 실행시키기 위해서 있었지만..

지금은 별 의미가 없다..

실행되지 않을 정도의 하드웨어 사양을 찾기가 힘들다... ^^;

{숫자}는 같은 숫자로 중복해서 사용할 수도 있다.

 

ntsysv 명령어로 각 데몬을 체크하거나, 체크를 지우는 것도...

알고보면 S 를 K 로 바꾸거나, K 를 S 로 바꾸는 작업이다.

 

/etc/rc.d/rc3.d/ 디렉토리에서는 S로 시작해서 심볼릭링크가 되어 있고,

/etc/rc.d/rc5.d/ 디렉토리에서는 K로 시작해서 심볼릭링크가 되어 있다면...

같은 /etc/rc.d/init.d/ 밑의 같은 데몬스크립트 파일이라고 하더라도..

런레벨마다 실행되거나, 실행되지 않을 수 있다.


출처 :
http://cafe.naver.com/asii/857

'OS > LINUX' 카테고리의 다른 글

리눅스의 런레벨(Run level)  (0) 2011.06.09
데몬의 종류, /etc/rc.d/init.d/  (0) 2011.06.09
/dev/null 다시 만들기  (0) 2011.06.04
/dev/zero와 /dev/null  (0) 2011.06.04
Perl 입문  (0) 2011.05.17

# mknod /dev/null  c  2  2
퍼미션을 644 -> 666으로 변경(?)

'OS > LINUX' 카테고리의 다른 글

데몬의 종류, /etc/rc.d/init.d/  (0) 2011.06.09
[/etc/rc.d/init.d/*] 데몬 실행 스크립트의 이해  (0) 2011.06.09
/dev/zero와 /dev/null  (0) 2011.06.04
Perl 입문  (0) 2011.05.17
프로세스 - fork & exec  (0) 2011.05.14

/dev/zero/dev/null

/dev/null의 쓰임새

/dev/null"블랙홀"이라고 상상해 보세요. 이 파일은 거의 읽기 전용 파일이나 마찬가집니다. 이 파일에 쓰는 모든것은 영원히 사라져 버릴겁니다. 이 파일에서 무언가를 읽으려고 하거나 어떤 결과를 바라는 것은 무의미한 일입니다. 그럼에도 불구하고, /dev/null은 명령어 줄이나 스크립트에서 아주 유용하게 쓸 수 있습니다.

표준출력이나 표준에러 막기(예 30-1에서 인용):

rm $badname 2>/dev/null
#           에러 메세지[stderr]는 완전히 사라져 버립니다.

파일 자체와 모든 퍼미션은 그대로 가지면서 내용만 지우기(예 2-1예 2-2에서 인용):

cat /dev/null > /var/log/messages
#  : > /var/log/messages   라고 해도 같지만, 이렇게 하면 새 프로세스를 띄우지 않습니다.

cat /dev/null > /var/log/wtmp

로그 파일의 내용을 자동으로 비우기(상용 웹 사이트에서 보내는 귀찮은 "쿠키"를 처리할 때 특별히 좋습니다):

예 29-1. 쿠키 항아리를 숨기기

if [ -f ~/.netscape/cookies ]  # 있다면 지우고,
then
  rm -f ~/.netscape/cookies
fi

ln -s /dev/null ~/.netscape/cookies
# 이제 모든 쿠키는 디스크에 저장되지 않고 블랙홀로 보내집니다.
/dev/zero의 쓰임새

/dev/zero/dev/null처럼 가상 파일(pseudo file)이지만, 실제로 널 값을 갖고 있습니다(아스키 문자 0이 아닌 숫자 0). 이 파일에 무언가를 쓰면 그 출력은 사라집니다. 이 파일에서 널 값을 읽어 내는 것은 아주 어렵습니다만 od 명령어나 헥사 에디터로 할 수는 있습니다. /dev/zero는 특정한 길이의 초기화된 더미 파일을 임시 스왑 파일로 만드는데 주로 쓰입니다.

예 29-2. /dev/zero로 스왑 파일 세팅하기

#!/bin/bash

# 스왑 파일 만들기.
# 루트로 실행시키세요.

ROOT_UID=0         # 루트 $UID 는 0.
E_WRONG_USER=65    # 루트가 아님.

FILE=/swap
BLOCKSIZE=1024
MINBLOCKS=40
SUCCESS=0

if [ "$UID" -ne "$ROOT_UID" ]
then
  echo; echo "이 스크립트는 루트만 실행시킬 수 있습니다."; echo
  exit $E_WRONG_USER
fi  
  

if [ -n "$1" ]
then
  blocks=$1
else
  blocks=$MINBLOCKS              # 명령어줄에서 지정해 주지 않으면 
fi                               # 40 블럭을 기본값으로 세트.

if [ "$blocks" -lt $MINBLOCKS ]
then
  blocks=$MINBLOCKS              # 최소 40 블럭이어야 됩니다.
fi  


echo "Creating swap file of size $blocks blocks (KB)."
dd if=/dev/zero of=$FILE bs=$BLOCKSIZE count=$blocks  # Zero out file.

mkswap $FILE $blocks             # Designate it a swap file.
swapon $FILE                     # Activate swap file.

echo "Swap file created and activated."

exit $SUCCESS

/dev/zero의 다른 응용으로는, 파일이 0으로만 이루어진 지정된 크기를 갖게 하는 것인데, 루프백 디바이스를 마운트 하는 등의 특별한 목적을 위해 쓰입니다. 예 13-6예 12-33를 참고하세요.

예 29-3. 램디스크 만들기

#!/bin/bash
# ramdisk.sh

#  "ramdisk" 란 시스템의 RAM 의 일정 부분(segment)을 
#+ 파일시스템처럼 쓰는 것을 말합니다.
#  램디스크의 장점은 읽고/쓰기가 아주 빠르다는데 있습니다.
#  단점: 휘발성이 있기 때문에 시스템이 리부트되거나 꺼지면 그 내용을 잃어버립니다.
#        램디스크로 할당한 만큼의 메모리를 못 쓰게 됩니다.
#
#  램디스크가 뭐가 좋을까요?
#  테이블이나 사전처럼 아주 큰 데이타를 램디스크에 올려 놓으면
#+ 디스크 접근 속도보다 메모리 접근 속도가 훨씬 빠르기 때문에 데이타 탐색 속도가 빨라집니다.


E_NON_ROOT_USER=70             # 루트로 실행.
ROOTUSER_NAME=root

MOUNTPT=/mnt/ramdisk
SIZE=2000                      # 2K 블럭(필요에 따라 수정)
BLOCKSIZE=1024                 # 1K (1024 byte) 블럭 크기
DEVICE=/dev/ram0               # 첫번째 램 디바이스

username=`id -nu`
if [ "$username" != "$ROOTUSER_NAME" ]
then
  echo "\"`basename $0`\" 는 루트로 실행시켜야 됩니다."
  exit $E_NON_ROOT_USER
fi

if [ ! -d "$MOUNTPT" ]         #  마운트 포인트가 존재하는지 확인해서
then                           #+ 이 스크립트를 여러번 실행시켜도 에러가 나지 않도록 함.
  mkdir $MOUNTPT              
fi

dd if=/dev/zero of=$DEVICE count=$SIZE bs=$BLOCKSIZE  # 램 디바이스 초기화(zero out).
mke2fs $DEVICE                 # 램 디스크에 ext2 파일시스템을 만들고,
mount $DEVICE $MOUNTPT         # 마운트.
chmod 777 $MOUNTPT             # 일반 사용자도 접근 가능하게.
                               # 하지만 언마운트는 루트만.

echo "이제 \"$MOUNTPT\" 를 쓸 수 있습니다."
# 이제부터는 일반사용자까지도 램디스크에 파일을 저장할 수 있습니다.

#  주의할 점은 램디스크가 휘발성을 갖기 때문에 리부트나 전원이 꺼질 때에는
#+ 그 내용이 없어집니다. 
#  저장하고 싶은 것이 있다면 램디스크가 아닌 일반 디렉토리로 복사해서 쓰면 됩니다.

# 리부트후에 램디스크를 다시 셋업하고 싶다면 이 스크립트를 실행시키면 됩니다.
#  /mnt/ramdisk 를 이 스크립트를 통하지 않고 
#+ 다른 방법으로 다시 마운트하려고 한다면 제대로 동작하지 않을 것입니다.

exit 0

'OS > LINUX' 카테고리의 다른 글

[/etc/rc.d/init.d/*] 데몬 실행 스크립트의 이해  (0) 2011.06.09
/dev/null 다시 만들기  (0) 2011.06.04
Perl 입문  (0) 2011.05.17
프로세스 - fork & exec  (0) 2011.05.14
쉘 스크립트 #6  (0) 2011.05.14

    리눅스와 함께 꾸준히 발전해온 Perl.
    “CGI 최적언어”라는 평가를 받은 지는 이미 오래지만 국내에 Perl 전문개발자가 의외로 적고 Perl의 쓰임세 또한 가볍게 여기는 경향이 있는 게 현실이다.
    단순히 Perl은 C와 문법이 비슷하고 CGI를 제작하기에 적당하다는 정도로 인식하고 있는 독자라면 연재될 글들을 주의 깊게 살피기를 권하고 싶다.  필자가 연재하게 될 내용은 이미 많은 책자에서 거론되었을 법한 것도 있고 전혀 색다른 시도라고 느껴지는 것도 있을 수 있을 것이다.
    지면 관계상 원하는 모든 내용을 실을 수는 없겠지만 적어도 C보다는 간결하고 쉽게 구현되는 결과물을 접할 수 있도록 구성할 계획이다.

 

Ⅰ. Perl 입문

    필자는 Linux를 접하기 전부터 Perl을 이용해 배포용 CGI(http://way.co.kr)를 위시로 하여 개발해왔으며, Linux를 접하게 된 후부터는 Linux가 Perl 프로그램 개발에 더없이 편리한 환경을 제공한다는 것을 알 수 있었다.
    Linux 설치를 끝냈고 C나 기타 언어를 학습중이거나 학습할 계획인 리눅서라면 Perl을 공부해보라고 권하고 싶다.

    Perl의 기능은 C에서 많이 빌려왔으며 sed나 awk 등의 문자열 처리용 프로그램에서도 많은 기능을 계수하였다.  그래서 C를 어느 정도 알고 있는 사람이 Perl 소스를 접하게 되면 유사한 문법 때문에 대략적인 윤곽을 파악할 수 있게 된다. 그렇다고 Perl을 배우기 위해 일부러 C를 먼저 배우는 어리석음은 범하지 말기를 바란다.

    요즘 필자는 Linux에서 vi에디터를 이용해 Perl 프로그램을 개발하고 있다.  대부분의 Linux 배포판은 Perl이 기본적으로 설치되고 레드햇 등의 배포판은 rpm과 같은 바이너리 패키지 형태로 제공되어 설치 및 관리가 간편하다.  만약 Perl을 직접 컴파일하여 설치하려거든 http://www.perl.com/등에서 파일을 다운로드하면 되지만 구체적인 설치방법은 지면관계상 생략하기로 한다.  만약 윈도우즈 계열의 서버에서 테스트해보고 싶다면 http://way.co.kr의 “CGI 강좌” 게시판을 참고하기 바란다.

    먼저 Perl이 설치되어있는지 확인해보자.  대부분의 경우 /usr/bin 경로에 perl 프로그램이 위치할 것이다.  레드햇의 경우 $ rpm -qa | grep perl 명령으로도 설치여부를 확인할 수 있다.  perl -v를 입력하면 설치된 Perl의 버전정보를 얻을 수 있으며 perl -V는 좀더 상세한 정보를 얻을 수 있다.

    $ perl -v

    This is perl, version 5.004_05 built for i386-linux
    (with 1 registered patch, see perl -V for more detail)

    Copyright 1987-1998, Larry Wall
    < 하략 >

    프로그래밍 언어 중에는 C 등과 같이 컴파일을 통해 독자적인 파일 하나로도 프로그램 실행이 가능한 언어가 있다. 하지만 Perl은 프로그램 소스를 해석하여 실행해주는 프로그램(인터프리터, interpreter)이 설치되어 있어야만 하고 그 때문에 Perl이나 shell 스크립트는 소스의 첫 번째 줄에 #!/usr/bin/perl 등과 같이 해석용 프로그램의 위치를 적어주어야 하는 것이다.  그러니까 Perl이 설치되어있지 않은 서버에서는 Perl 소스 프로그램을 실행할 수 없다고 보면 되는 것이다.(최근에는 소스를 컴파일 하는 방법도 제공되고 있다.)

 

1. 실행방법

    Perl 소스 프로그램에 실행권한(chmod +x)을 부여하고 첫 번째 줄에 Perl의 위치를 적어줄 경우 다음과 같이 프로그램명만으로 직접 실행시킬 수 있다.  
    특히 웹상에서 실행하고자 할 경우 이와 같이 해주어야 한다.

    $ program(프로그램명)

    현재 경로가 PATH에 걸려있지 않다면 ./program과 같이 실행하여야 한다.
    만약 shell 상태에서 다음과 같이 실행한다면 소스 첫 번째 줄에 Perl의 위치를 적어주거나 실행권한을 부여할 필요가 사라진다.

    $ perl 프로그램명

    작성된 소스코드의 오류를 점검하기 위해서는 perl -c 옵션을 이용한다.  
    소스코드에 이상이 없을 경우 “syntax OK”라는 메시지가 나타난다.

    $ perl -c 프로그램명

 

2. 프로그램 맛보기

    Perl이 시스템에 설치되어있다면 이제부터 간단한 Perl 문법부터 익혀보도록 하자.

    print “Hello, Way!\n”;

    간단하면서도 완벽한 하나의 프로그램이다. vi 등의 편집기에 소스내용을 입력하여 저장한 후 실행해 보자.

    $ perl hello(파일명)
    Hello, Way!

    소스를 분석해보면, “Hello, Way!”라는 문자열과 함께 줄 바꿈 문자(\n)를 출력하는 것이며 각 줄의 끝은 ;(세미콜론) 기호로 마무리한다.

 

3. 변수와 기본연산

    다른 언어들과는 달리 변수형을 지정할 필요 없이 대입과 동시에 변수형이 결정된다.  변수는 $기호로 시작되며 배열의 경우 @, 조합배열(hash)은 %기호로 시작된다.  $bom = 123; 과 같이 $bom 변수에 123이라는 정수를 대입하게 되면 별도의 조치 없이 정수형을 갖게 된다.  변수간의 연산도 매우 자유롭다.  예를 들어 다음과 같은 경우 결과값은 정상적인 수치 연산결과인 100이 출력된다.

    $bom = 49;   # 정수
    $danvi = “51”;   # 문자열
    print $bom + $danvi;  # 100 출력

    perl 문법상 # 기호 이후부터는 주석으로 인식한다.  예상하듯이 Perl에서는 사칙연산에 +, -, *, / 기호가 이용되며 나머지(계수)에는 % 기호가 사용된다.
    문자열 연산자는 .(마침표, dot)를 사용하며, 연산방법 또한 앞에서와 같이 간단하다.

    $bom = 100;  # 정수
    $danvi = “200”;  # 문자열

    print $bom . $danvi; 와 같이 문자열 연산자를 이용하면 “100200”이 출력되고 print $bom + $danvi; 와 같이 수치연산자를 이용하면 300이 출력된다.

 

4. 조건문

    if (조건) { 참일 때; }
    else { 거짓일 때; }

    두 개 이상의 조건문은 elsif(“elseif”가 아님에 주의)를 이용하여 다음과 같이 구성할 수 있다.

    if (조건) { 참일 때; }
    elsif (조건) { 새로운 조건이 참일 때; }
    elsif (조건) { 새로운 조건이 참일 때; }
    . . . .
    else { 모두 거짓일 때; }

    조건판별을 위한 연산자로서 ==(eq)는 일치, !=(ne)는 불일치로 사용되며 <, >, <=, >= 등의 기호를 사용할 수 있다.  또한 논리연산자로서 &&(and), ||(or), !(not)도 사용할 수 있다.(기호와 영문 모두 사용할 수 있다.)

    $bom = 5;
    if ($bom < 10 and $bom > 3) {
      print “10보다는 작고 3보다는 크다. \n”; }

    소스를 실행해보면 당연히 “10보다는 작고 3보다는 크다.”가 출력될 것이다.

    if와는 반대로 사용되는 조건문으로는 unless가 있다.

    unless (조건) { 거짓일 때; }
    else { 참일 때; }

 

5. 반복문

    while (조건) { 조건이 참인 동안 실행; }
    until (조건) { 조건이 거짓인 동안 실행; }
    무한루프를 원치 않는다면 당연히 중괄호({})를 실행하는 동안 조건과 관련한 값이 변화되어야 할 것이다.

    $bom = 1;
    while($bom <= 10) {
       print $bom, “\n”;
       $bom++; }

    초기의 $bom의 값은 1이었고 해당 값이 10보다 작기 때문에 while문의 조건에 일치하므로 중괄호({}) 가 실행된다.  중괄호로 처리방향이 넘겨진 후 print 문에 따라 $bom의 값인 1이 출력된다. 만약 $bom++; 와 같이 $bom 값을 변화시켜주지 않는다면 $bom 값은 영원히 1일 것이므로 무한루프가 되어버릴 것이다. $bom++ 는 자동증가를 뜻하는 것으로서 원래의 $bom 값에 1을 가산한 후 다시 $bom에 값을 저장하게 되는 것이다. $bom-- 는 그 반대이다. 루프를 한번 돌게되면 $bom 값은 2가 되고 한번씩 더 돌 때마다 3, 4, 5, 6, 7, 8, 9, 10이 된다.  $bom 값이 10일 경우 10을 출력하고 $bom++ 에 따라 $bom 값이 11이 되면서 while문의 조건에 맞지 않아 루프는 결국 종료된다.

    또다른 반복문으로는 for문이 있다.

    for (초기값; 조건; 변화값) { 조건이 참인 동안 실행; }

    다음 소스는 앞서 설명한 while문의 예제와 동일한 결과를 보이게 된다.

    for ($bom = 1; $bom <= 10; $bom++) {
       print $bom, “\n”; }

    반복문중에는 foreach문도 있으나 배열과 관련하여 주로 사용되므로 설명을 뒤로 미루기로 하겠다.

 

6. 파일 핸들링

    파일을 읽거나 저장하기 위해서는 파일을 열어야하며 일반적으로 다음과 같은 규칙에 의해 사용되어진다.

    open(파일핸들, “[조작방법] 파일명”);

    ‘파일핸들’은 사용자의 식별을 용이하게 하기 위해 임의로 지정하여 사용한다. ‘조작방법’에는 읽기전용(< 또는 생략), 쓰기(>), 덧붙이기(>>) 등이 있다.

    open(FILE, “>bom”);
      print FILE “Way\n”;
    close(FILE);

    분석해보면 다음과 같다.

    open(FILE, “>bom”);

    bom이라는 파일명으로 파일을 쓰기모드로 열되 파일핸들은 FILE로 지정한다.

    print FILE “Way\n”;

    FILE이라는 파일핸들(실제로는 bom이라는 파일)에 “Way\n”라는 문자열을 기록한다.

    close(FILE);

    FILE 이라는 핸들의 파일을 닫는다.  조작이 끝나는 즉시 닫는 게 바람직하다.

    앞에서 알 수 있듯이 파일핸들을 사용함으로 해서 특정 파일에 print(기록)하고, 손쉽게 파일을 닫을 수 있게 된다.
    소스가 실행되게 되면 bom이라는 파일이 생성되어지고 파일에는 Way라는 내용이 들어가게 될 것이다.
    쓰기모드일 경우 이미 동일한 파일이 존재하면 내용을 덮어쓰게 되므로 종전내용을 잃게 된다. 하지만 덧붙이기(>>)모드로 파일을 열게 되면 종전내용의 하단에 새로운 내용이 추가된다.(대부분의 log파일 저장방식과 동일하다.)

    이제 다음과 같은 소스를 작성하여 실행해보자.

    open(FILE, “>>bom”);
      print FILE “Way2\n”;
    close(FILE);

    특별한 문제가 없는 한 bom이라는 파일내용은 다음과 같을 것이다.

    $ cat bom
    Way
    Way2
    $

    저장하는 방법을 알았으니 이제 읽어 오는 방법을 알아보자.
    open(FILE, “bom”);
      $line1 = <FILE>;
      $line2 = <FILE>;
    close(FILE);

    <FILE>이 어떤 기능을 하는지 궁금할 것이다.  <FILE>에서 FILE은 앞에서와 마찬가지로 파일핸들이며 <>(‘줄입력 연산자’라고도 한다.)로 묶이므로 해서 한 줄을 읽어 온다는 뜻이다.

    $line1= <FILE>;
    bom파일의 첫 번째 줄 내용을 $line1 이라는 변수에 저장(대입)한다.

    $line2 = <FILE>;
    한번 더 <FILE>을 사용하였으므로 두 번째 줄을 읽어 $line2에 저장하는 것이다.  그런데 모든 파일의 내용을 하나의 변수에 넣어 조작하고자 한다면 몇 줄인지 알 수 없기 때문에 앞의 방법보다는 다음과 같이 while문을 이용하는 것이 편리하다.

    open(FILE, “bom”);
      while($line = <FILE>) {
            $all_line = $all_line . $line; }
    close(FILE);

    약간 복잡하게 느껴질 수도 있을 것이다.  첫줄과 마지막 줄은 설명이 필요 없을 듯 하여 가운데부분만 설명하도록 하겠다.  while문은 참일 경우 바로 뒤에 위치한 중괄호({})가 실행된다.  Perl에서는 아무런 값이 없을 경우 ‘거짓’을 돌려주므로 bom 이라는 파일을 모두 읽어오고 나면 while 루프가 종료되는 것이다.  약간 더 설명을 덧붙이자면, $line = <FILE> 이 참일 경우(읽어올 줄이 있을 경우는 참이 된다.) $line 변수에 읽어온 줄의 내용을 저장하고 문자열연산자(.)에 의해 $all_line 변수에 추가되며, 파일 끝까지 읽고 나면 읽어올 줄이 없어 거짓을 리턴 하므로 while 루프가 종료되는 것이다.

    다음과 같이 간단히 나타낼 수도 있다.

    open(FILE, “bom”);
      while(<FILE>) { $all_line .= $_; }
    close(FILE);

    $all_line .= $_ 부분은 $all_line = $all_line . $_와 동일한 결과를 보인다.

    이제 특정파일의 내용을 읽어 변수에 저장하였으며 이로써 조작이 가능하게 되었다.

    파일을 열거나 조작하고자 할 때 발생할 수 있는 오류를 미리 예방하고 파일(또는 디렉토리)에 관한 정보를 얻어 활용하기 위해 Perl은 다양한 파일테스트 연산자를 제공한다.

    주요 연산자는 다음과 같다.

     

    - e

      존재 여부

    - r

      읽기 가능 여부

    - w

      쓰기 가능 여부

    - x

      실행 가능 여부

    - d

      디렉토리 여부

    - T

      텍스트파일 여부

    - B

      이진파일(binary)  여부

    - s

      파일크기

     

    if (-e “bom”) { print “파일 있음\n”; }
    else { print “파일 없음\n”; }

    print “bom 파일의 크기는 “, -s “bom”, “ byte입니다.\n”;

 

7. 함수

    구조적인 프로그램을 위해, 특히 빈번히 사용되는 특정 기능이나 처리절차를 하나의 함수로 생성하여 활용할 수 있다.

    함수는 다음과 같이 구성되며 &함수명과 같이 호출한다. 함수는 중괄호({})로 묶이며 함수영역 내외를 불문하고 사용되어지는 문법은 동일하다.

    sub 함수명 { 처리내용; }

    앞서 설명했던 for문을 이용해 함수를 적용해보겠다.

    for ($bom = 1; $bom <= 10; $bom++) {
       print $bom, “\n”;
       &job; }

    sub job { print “===============\n”; }

    소스를 실행해보면 1부터 10까지 출력되는 과정중 숫자 사이사이에 구분선이 그어질 것이다.  이런 용도의 함수는 작업을 좀더 간결히 하는 정도의 효과밖에 기대할 수 없지만, 다음과 같은 경우 호출시 &함수명(“넘길값”)과 같이 인자를 넘기고 호출된 함수에서는 넘겨받은 인자를 계산(처리)하여 결과를 리턴하게되는 완전한 함수의 형태를 띄게 된다.

    for ($bom = 1; $bom <= 10; $bom++) {
       $danvi = &job($bom);
       # job 함수로 $bom이라는 변수값을 넘기고,
       # 돌려 받은 처리결과를 $danvi 변수에
       # 저장한다.
       print $danvi, “\n”; }

    sub job {
      ($input) = @_;   # 값을 넘겨받는다.
      $output = $input * 10;  # 계산한다.
      return $output; }  # 결과를 돌려준다.

    소스를 실행해보면 10, 20, 30, ... 과 같이 $bom 값에 10을 곱한 값이 순서대로 출력된다.

 

8. 배열

    여러 개의 값들을 하나의 변수에 저장하여 활용하기 위해 배열(array)과 조합배열(hash)이 지원된다.  둘의 차이점은 인덱스(index)가 숫자인가 그렇지 않은가 이다.

    배열에 값들을 직접 대입하는 방법은 괄호를 이용하여 다음과 같이한다.
    @way = (1, 2, 3, 4, 5);

    배열대입은 다음과 같이 개별적으로도 가능하다.
    $way[0] = 1;
    $way[1] = 2;
    $way[2] = 3;
    $way[3] = 4;
    $way[4] = 5;

    배열을 일반변수형태로 추출하기 위해서는 다음과 같이 사용한다.
    $way[배열번호]

    첫 번째 값의 배열번호는 0이다.  따라서 $way[0]에는 1이, $way[1]에는 2가 저장되어 있는 것이다.

    배열에서 값을 추출하여 또 다른 변수에 저장해보자.
    $bom1 = $way[0];
    $bom2 = $way[1];
    $bom3 = $way[2];
    $bom4 = $way[3];
    $bom5 = $way[4];

    또 다른 방법으로는 다음과 같은 방법도 존재한다.
    배열에 직접 대입하던 것과는 반대이다.
    ($bom1, $bom2, $bom3, $bom4, $bom5) = @way;

    조합배열은 %기호를 사용하며 숫자인덱스 대신 임의의 문자열(key)을 인덱스로 갖는다.  따라서 저장된 순서는 그다지 큰 의미가 없다.

    %way = (“빨강” => “red”,
                 “파랑” => “blue”,
                 “검정” => “black”);

    앞서 설명한 배열과 동일한 방법으로 대입하였지만 주의 깊게 관찰할 부분은 => 기호를 이용해서 key와 value로 구성된다는 것이다.

    마찬가지로 $way{‘빨강’} = “red”; 와 같은 방식으로 개별적인 저장도 가능하다.

    자료를 추출하기 위해서는 다음과 같이 사용하면 된다.

    $way{key}

    $way{‘빨강’} 에는 “red”가, $way{‘파랑’}에는 “blue”가 저장되어 있다.

    보통 => 기호 대신 ,(comma)를 사용하기도 하지만 가독성을 높이기 위해 => 기호를 사용하는 것이 바람직하다.

    print “파랑색은 영문으로 $way{‘파랑’}입니다.\n”;

    소스가 실행되면 “파랑색은 영문으로 blue입니다.”가 출력될 것이다.
    조합배열은 keys와 values를 이용해 인덱스나 값만을 추출해낼 수 있다.
    print keys %way
    소스가 실행되면 “빨강파랑검정” 등과 같이 출력된다.

    이제 앞에서 설명을 미뤘던 foreach문에 대해 알아보자.

    foreach 추출값 (배열 또는 목록) { 실행영역; }

    배열값을 하나씩 추출하고 그때마다 중괄호로 묶인 실행영역을 거치게 된다. 배열의 모든 원소가 추출되고 나면 루프는 종료된다.

    @bom = (“봄”, “여름”, “가을”, “겨울”);
    foreach $danvi (@bom) {
      $count ++;
      print “$count, $danvi\n”; }

    소스를 실행하면 다음과 같이 출력될 것이다.

    1, 봄
    2, 여름
    3, 가을
    4, 겨울

    foreach문은 다음과 같이 사용할 수도 있다.  결과는 앞의 결과와 동일하다.

    foreach $danvi (“봄”,”여름”,”가을”,”겨울”) {
      $count ++;
      print “$count, $danvi\n”; }

    배열은 @way = (); 또는 %way = (); 와 같이 초기화할 수 있다.

    배열에 원소를 추가하고자 할 경우 push를 이용한다.
    push(배열명, 추가할 값);

    @way = (1, 2, 3, 4, 5);
    push(@way, 6);

    @way 에는 6이라는 원소가 추가된다.

 

9. 문자열 처리

    Perl은 문자열 처리에 상당한 능력을 과시하고 있다.

    “ ”기호로 묶인 상태에서 변수를 직접 사용할 수 있다.
    $way = “CDE”;
    $way1 = “AB$way”;  # ABCDE

    특정 문자를 지정한 수량만큼 복제하기 위해 x 라는 특수한 연산자를 이용한다.  구분선 출력이나 열을 맞추기 위한 공백 등에 자주 사용된다.
    $way2 = “=” x 10;   # ==========

    문자열중 일부분을 추출하는 방법은 다음과 같다.  시작위치는 0부터이다.

    substr(변수, 시작위치, 글자수);

    $way 라는 변수의 값중 첫 번째부터 2글자를 추출하여 $way3에 저장하는 방법은 다음과 같다.
    $way3 = substr($way, 0, 2);   # CD

    문자열 중에서 특정문자의 존재여부를 파악하는 방법은 다음과 같다.  실행결과 “있음”이 출력된다.

    $way = “봄 여름 가을 겨울”;
    if($way =~ /봄/) { print “있음\n”; }
    else { print “없음\n”; }

    문자열을 치환(substitution)하려면 다음과 같이 하면 된다.  실행결과 “이것이 사랑일까?”가 출력된다.

    $way = “이것이 우정일까?”;
    $way =~ s/우정/사랑/;
    print “$way\n”;

 

10. 시스템 명령

    `(역따옴표)기호를 이용할 경우 원하는 시스템 명령을 실행할 수 있으며 실행결과를 얻어와 활용할 수 있다.

    $way = `ls -al`;
    print $way;

    이렇게 할 경우 shell에서와 마찬가지로 ls 명령결과가 출력된다.

    $bom = `w`;
    @user = (“way”, “lawwal”, “danvi”);
    foreach $user_id (@user) { &login_check($user_id); }

    sub login_check {
     ($id) = @_;
     if ($bom =~ /$id/) { print “$id : On-Line\n”; }
      else { print “$id : Off-Line\n”; }
    }

    필자가 사용중인 서버에서 소스를 실행한 결과 다음과 같이 출력되었다.

    way : On-Line
    lawwal : On-Line
    danvi : Off-Line

    만약 결과를 얻어올 필요가 없고 명령에 따라 부수적인 작업을 해야 한다면 system이나 exec를 사용한다.  exec의 경우 실행중인 Perl 프로그램은 종료되고 지정된 명령어가 실행된다는 차이점이 있다.

    print “top 명령을 실행할까요? [Y/n] “;
    $input = <STDIN>;
    if ($input =~ /y/i) { system(“top”); }
    else { print “취소되었습니다.\n”; }

    줄입력연산자인 <> 사이에 STDIN(표준입력)이 들어가게 되면, 키보드로부터 값을 입력받는다. 입력된 값에 Y 또는 y가 존재하는 경우(i 옵션은 대소문자를 구별하지 않는다.) top 명령을 실행하도록 한다.

    이번에는 대략적인 문법을 위주로 설명하였다. 기본적인 문법을 토대로 하나씩 응용범위를 늘려가다 보면 여러분도 어렵지 않게 쓰임세 있는 Perl 프로그램을 개발할 수 있을 것이다.

    다음에는 웹프로그램을 위한 CGI로서의 Perl을 살펴보도록 하겠다.

 

    UNIX 계열에서 사용되는 웹프로그램 언어로는 C, Perl, PHP 등이 있는데 장단점을 개인적 견해로 비교해보면 다음과 같다.

    C는 코딩속도와 라이브러리 등에서 열세를 보이는 반면 실행속도가 빠르다는 장점이 있다.
    Perl의 경우 거의 대부분의 UNIX 시스템에 기본적으로 설치되어 있어 시스템간 호환성이 뛰어나고 코딩 속도도 빠르지만 인터프리터 언어 특성상 실행속도가 약간 느리고 다수 프로그램이 동시에 실행될 경우 서버에 부담이 될 수 있다.  PHP는 코딩 속도와 실행속도 면에서 우위를 보이지만 웹서버와 조화를 이뤄야 하므로 범용 프로그램으로 배포하기엔 부적절한 데다 비대한 웹서버가 동작하게 되어 서버에 부담이 될 수 있다.

    필자가 다른 언어보다 Perl을 주로 사용하는 이유는 코딩속도와 시스템 도구나 문자열 조작도구로 사용할 수 있기 때문이다.

 

Ⅱ. CGI Using Perl

    생소할지 모르지만 다음의 소스는 본쉘(bash) 스크립트를 이용한 간단한 CGI이다.

      #!/bin/bash
      echo Content-type: text/html
      echo
      echo “bash를 이용한 CGI입니다.<BR>”
      echo “접속주소 : $REMOTE_ADDR”

    다음은 C를 이용한 CGI이다.  소스를 way.c라는 파일에 저장한 후 컴파일($ gcc -oway.cgi way.c)해서 웹상에서 실행해보면 간단한 결과가 출력된다.

      main() {
       printf (“Content-type: text/html\n\n”);
       printf (“C를 이용한 CGI입니다.<BR>”);
       printf (“접속주소 : %s”, getenv(“REMOTE_ADDR”));
      }

    이렇듯 많은 언어나 쉘스크립트를 이용해 CGI를 만들어낼 수 있다.

 

1. Perl CGI 맛보기

    Perl로 웹상에서 특정 문자를 출력하기 위해서는 내용을 출력하기 전에 다음과 같은 헤더를 출력하여 형식을 선언해줘야 한다.

      print “Content-type: text/html\n\n”;

    간혹 뉴스그룹(han.comp.lang.perl)에 올라오는 질문 중 적지 않는 질문이 이런 사소한 문제가 원인인 경우가 많다.
    특히 “Internal Server Error(500)”는 대부분 웹서버 Error log나 구문점검 옵션($ perl -c 파일명)을 통해 원인을 파악할 수 있다.
    다음 소스를 way.cgi 라는 파일로 저장한 후 실행권한을 부여(chmod +x)하면 웹브라우져로 “Hello, Way!”라는 내용을 접할 수 있다.

      #!/usr/bin/perl
      print “Content-type: text/html\n\n”;
      print “<H1>Hello, Way!</H1>\n”;

    특정 URL로 접속을 전환(Redirect)하기 위해서는 다음과 같이 사용한다. 보통 http:// 등의 프로토콜 지정 없이 상대 URL로 사용할 수도 있지만 원칙적으로 프로토콜까지 포함해서 적어줘야 한다. 생략할 경우 오라클 웹서버, PWS 구버전 등의 일부 웹서버에서는 에러가 발생하기도 한다. 다음 소스가 브라우져로 실행되면 지정된 URL로 접속이 전환된다.

      #!/usr/bin/perl
      print “Location: http://way.co.kr\n\n”;

 

2. 아파치 웹서버 설정

    만약 사용하는 웹서버에서 CGI 실행을 허용하지 않거나 CGI 파일에 실행권한이 부여되지 않았을 경우 “Forbidden(403)”이나 “Method Not Allowed(405)” 에러가 발생할 것이다. 경우에 따라 Perl 소스가 브라우져에 그대로 출력되기도 한다.

    ※ 아파치 웹서버 CGI 실행오류 해결방법

    가. AddHandler cgi-script .cgi 부분의 주석 제거
    나. <Diretory> 설정 중 Options 에 ExecCGI 추가

     

      <Directory “/usr/local/apache/htdocs”>
      Options Indexes Includes ExecCGI
      AllowOverride FileInfo AuthConfig
      </Directory>
      다음은 계정사용자의 웹문서 경로에 CGI실행을
      허용하는 설정이다.
      <Directory /home/*/public_html>
      Options Indexes Includes ExecCGI
      AllowOverride FileInfo AuthConfig
      </Directory>

     

    다. ScriptAlias 설정

     

      ScriptAlias /cgi-bin/ “/usr/local/apache/cgi-bin/”
      (VirtualHost의 경우도 적용이 가능하다.)

      ·ScriptAlias란?
      1. 실제 디렉토리를 가상 디렉토리로 인식하도록
          한다.(Alias, 별칭 기능)
      2. 디렉토리 내의 파일을 브라우저로 접근하면
          무조건 실행 프로그램(CGI)으로 취급하여 실행한다.
      3. 보안상 HTML 문서 등은 퍼미션 에러나 서버에러가
          발생하고 이미지는 나타나지 않는다.

     

3. FORM 태그

    HTML 문서를 이용해 CGI에 정보를 전달하기 위해 FORM 태그가 사용된다.  FORM 태그는 여느 HTML 태그와 마찬가지로 <FORM>으로 시작하여 </FORM>으로 끝이 난다.

    <FORM ACTION=”연결할 CGI URL” METHOD=GET>

    TARGET을 지정하거나 NAME을 지정할 수 있으며, 다음과 같이 파일 업로드를 위한 선언도 가능하다.

    <FORM ACTION=”연결할 CGI URL” METHOD=POST
      ENCTYPE=”multipart/form-data”>

    다음과 같이 FORM 태그 내부에 <INPUT>, <SELECT>, <TEXTAREA> 등의 태그를 사용하여 실질적인 정보를 전달하도록 한다.

    <FORM ACTION=”way.cgi” METHOD=GET>
      성명 <INPUT TYPE=TEXT NAME=user><BR>
      주소 <INPUT TYPE=TEXT NAME=address><BR>
      성별 <SELECT NAME=sex>
              <OPTION>남
              <OPTION>여
           </SELECT><BR>
           <INPUT TYPE=SUBMIT VALUE=”전송”><BR>
    </FORM>

    성명에 way를 입력하고 주소에 Kwangju를, 성별은 ‘남’을 선택하면 브라우져 주소입력줄에 다음과 같이 나타나며 지정된 CGI에 정보를 전달하게 된다.(GET 방식의 예이다.)

    way.cgi?user=way&address=Kwangju&sex=%B3%B2
    ----- ----- -------- ------- -------------
      CGI        성명                   주소                    성별

    ? 기호를 구분자로 하여 실행될 CGI(좌측)와 인자(우측)로 구성되며 각 인자들은 & 기호로 연결된다.  각 인자들은 = 기호로 키(항목명)와 값으로 구성된다.

 

4. 인자의 전달과 처리

    FORM 태그나 GET방식의 직접연결을 통해 전달된 정보는 약간의 절차를 거쳐야 Perl에서 활용할 수 있다.  특정 기호로 나뉜 인자들을 추출해 내야하고 성별과 같이 16진수 형태로 전달된 한글이나 특수문자를 정상적인 자료로 변환해야 한다.  이를 위해 많은 이들이 cgi-lib.pl 라이브러리나 CGI.pm 모듈을 사용하는 것이 보통이다.  하지만 필요한 범위 이상의 비대한 소스를 사용하고 싶지 않기에 필자는 한번도 사용해본 적이 없다.  적어도 다음의 소스 정도면 넘겨받은 인자를 Perl에서 정상적인 자료로 활용할 수 있기 때문이다.

     

      sub input_form {
      # 사용할 변수를 지역변수로 선언
      local ($form, $tmp, $key, $value);
      # GET, POST 방식을 판별하여 인자 습득
      if($ENV{‘REQUEST_METHOD’} eq “GET”) {
      $form = $ENV{‘QUERY_STRING’}; }
      if($ENV{‘REQUEST_METHOD’} eq “POST”) {
      read(STDIN, $form, $ENV{‘CONTENT_LENGTH’}); }
      # 키와 값으로 구분하고 16진수를 일반문자로 변환
      foreach $tmp (split(/&/, $form)) {
        ($key, $value) = split(/=/, $tmp);
        $key   =~ tr/+/ /;
        $value =~ tr/+/ /;
        $key   =~ s/%(..)/pack(“C”, hex($1))/ge;
        $value =~ s/%(..)/pack(“C”, hex($1))/ge;
        $input{$key} = $value; }
      }

     

    소스를 간략히 설명하면 다음과 같다.
    1. 인자 전달방식(GET 또는 POST) 판별
    2. 전체 인자 습득
    3. 전체인자에서 & 기호를 기준으로 각 인자 추출
    4. 각 인자에서 = 기호를 기준으로 키와 값으로 구분
    5. + 기호로 넘어온 공백문자를 정상적인 공백으로 변환
    6. 한글이나 특수문자인 16진수 자료를 일반문자로 변환
    7. %input 조합배열에 키와 값 저장

    이제 &input_form; 을 먼저 호출하고 $input{‘항목명’} 과 같이 자료를 추출하여 활용할 수 있다.

    앞서 설명한 FORM 태그가 포함된 HTML 문서에서 CGI를 호출하면 전달된 정보를 출력하는 CGI를 만들어 보자.

    다음은 HTML문서(FORM 태그 사용)에 웹브라우져로 정보를 입력하는 화면(lynx)이다.

     

      성명   way_________________
      주소   Kwangju_____________
      성별   [남]
      [ 전송 ]

     

    다음은 way.cgi 파일이다.  반드시 Perl경로를 확인하고 실행권한을 부여해야 정상적인 결과를 얻을 수 있을 것이다.

     

       #!/usr/bin/perl

      &input_form;
      &print_html;

      sub input_form { ... }  # 앞서 설명한 소스
      sub print_html {
      print “Content-type: text/html\n\n”;
      print “<HR>\n”;
      print “<H2>원래 전달되었던 인자</H2>\n”;
      print “$ENV{‘QUERY_STRING’}<BR>\n”;
      print “<HR>\n”;
      print “<H2>변환하여 활용이 가능해진 자료</H2>\n”;
      print “성명 : $input{‘user’}<BR>\n”;
      print “주소 : $input{‘address’}<BR>\n”;
      print “성별 : $input{‘sex’}<BR>\n”;
      print “<HR>\n”;
      exit;
      }

     

    정상적인 경우 결과는 다음과 같다.

     

       ___________________________________________
      원래 전달되었던 인자
      user=way&address=Kwangju&sex=%B3%B2
      ___________________________________________
      변환하여 활용이 가능해진 자료
      성명 : way
      주소 : Kwangju
      성별 : 남
      ___________________________________________

     

    5. 모듈과 라이브러리
    Socket, DBI, DBD 등의 Perl 모듈은 Perl의 활용범위를 확장시켜주는 매우 유용한 도구이다.  Perl 모듈을 사용하기 위해서는 다음과 같이 선언해주어야 한다.

    use 모듈명;

    Socket 모듈의 경우 원격통신을 가능하게 해주는 도구로서, sendmail 이 지원되지 않는 UNIX계열 서버나 WindowsNT/98/95 운영체제의 서버에서 SMTP 서버에 접속하여 메일을 전송하는 경우가 좋은 예이다.  관심 있는 독자는 Way-BOARD(Ver1.2)를 다운로드 하여 압축을 해제한 후 lib/mail.cgi 파일 하단의 Socket Mail 부분을 참고하기 바란다. 많은 모듈은 Perl이 설치되면서 함께 설치되지만 그 밖의 모듈은 http://www.perl.com/CPAN/에서 구할 수 있다.

    자주 사용되는 함수를 특정 파일에 넣어두고 필요에 따라 꺼내 쓸 수 있다.

    require(“라이브러리 파일명”);

    C나 PHP의 include문과 거의 동일한 기능을 한다.  앞의 인자변환 소스도 라이브러리 파일에 넣어두고 각종의 CGI에서 함께 공유할 경우 작업효율은 극대화 된다.  라이브러리 파일에 넣어둘 경우 실행권한을 부여하거나 Perl 위치를 적어줄 필요가 없지만 유의할 점은 파일의 제일 하단에 다음과 같이 return 1; 이라는 줄을 추가해줘야 에러가 발생하지 않는다는 것이다.

     

      # Perl Library File

      sub input_form { ... }

      sub html_header { ... }

      return 1;

     

    6. 환경변수
    앞서 설명한 인자처리 소스와 bash, C를 이용한 CGI에서 각각 환경변수를 읽어들여 활용하는 법을 볼 수 있었다.  Perl에서는 환경변수가 조합배열인 %ENV에 저장되어있다.
    다음은 Perl, PHP, C, bash에서 접속자의 IP정보를 얻어내는 방법이다.

     

    구  분

    환경변수 습득방법

    perl

    $ENV{‘REMOTE_ADDR’}

    PHP, C

    getenv(“REMOTE_ADDR”)

    bash

    $REMOTE_ADDR

     

    다음의 소스를 실행해보면 Perl에서 사용되는 각종의 환경변수명과 값을 구할 수 있다.

      #!/usr/bin/perl
      print “Content-type: text/html\n\n”;
      foreach $env_val (keys %ENV) {
        print “* $env_val : $ENV{$env_val}<BR>\n”; }

    다음은 필자의 서버에서 소스를 실행한 결과다.  대충 훑어보면 어떤 기능을 하는 것들인지 알 수 있을 것이다.  경우에 따라 환경변수 종류가 늘어나거나 줄어들 수 있다.

     

      * SERVER_SOFTWARE : Apache/1.3.9 (Unix)
      * GATEWAY_INTERFACE : CGI/1.1
      * DOCUMENT_ROOT : /home/way/public_html
      * REMOTE_ADDR : 210.123.123.123
      * SERVER_PROTOCOL : HTTP/1.1
      * SERVER_SIGNATURE : Apache/1.3.9 Server
         at way.co.kr Port 80
      * REQUEST_METHOD : GET
      * QUERY_STRING :
      * HTTP_USER_AGENT : Mozilla/4.0 (compatible;
         MSIE 5.0); Windows 98
      * PATH : /bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
      * HTTP_ACCEPT : */*
      * HTTP_CONNECTION : Keep-Alive
      * REMOTE_PORT : 1129
      * SERVER_ADDR : 210.123.254.100
      * HTTP_ACCEPT_LANGUAGE : ko
      * SCRIPT_NAME : /test/env.cgi
      * HTTP_ACCEPT_ENCODING : gzip, deflate
      * SCRIPT_FILENAME : /home/way/
         public_html/test/env.cgi
      * SERVER_NAME : way.co.kr
      * REQUEST_URI : /test/env.cgi
      * SERVER_PORT : 80
      * HTTP_HOST : way.co.kr
      * SERVER_ADMIN : webmaster@way.co.kr

     

    만약 브라우져에 입력된(특히 VirtualHost에서) 도메인을 얻기 위해서는 $ENV{‘HTTP_HOST’}라는 변수를 읽어들이면 된다.

    예시에 나와있지 않지만 HTTP_COOKIE(쿠키정보), HTTP_REFERER(CGI 실행 URL), REMOTE_USER
    (인증 사용자 ID) 등의 환경변수도 매우 유용하다.

    다음 소스는 환경변수를 이용해 스스로의 URL을 표시하는 간단한 소스이다.

      #!/usr/bin/perl
      print “Content-type: text/html\n\n”;
      print “http://$ENV{‘HTTP_HOST’}$ENV{‘SCRIPT_NAME’}\n”;

 

7. 시간정보

    시간정보를 얻기 위해서는 time() 함수를 이용한다. 얻을 수 있는 값은 1970년 1월 1일을 기준으로 한 초 단위 값이다. 보통의 경우 localtime() 함수를 사용하여 단위별 값들을 리스트로 추출해낸다. 다음은 시간정보를 보기 좋게 추출해내는 소스이다. 주의할 점은 요일($weekday)이나 월($month)은 0부터 시작된다는 것이다. 또한 항간에 시끄러운 Y2K문제와 관련해 고려할 부분은 $year가 실제 연도에서 1900을 뺀 수치로 구성된다는 것이다. 그러니까 $year 값에 1999년은 99, 2000년은 100이 저장된다.
    외국서버를 사용하는 경우와 같이 필요에 따라 지역시간대에 따른 조정이 필요하면 time()에 초단위로 조정을 가할 수 있다. localtime(time()+(12*60*60))와 같이 사용할 경우 서버시간에서 12시간 빠른 시간정보가 추출된다.

     

      #!/usr/bin/perl
      print “Content-type: text/html\n\n”;
      ($second, $minute, $hour, $date, $month,
      $year, $weekday, $yearday, $Isdst)
      =  localtime(time());
      $month ++;
      $year += 1900;

      @dayname = (“일”,”월”,”화”,”수”,”목”,”금”,”토”);
      $weekday = $dayname[$weekday];

      print “일자 : $year년 $month월 $date일 $weekday요일<BR>\n”;
      print “시간 : $hour시 $minute분 $second초<BR>\n”;

     

    PHP에서 사용하는 date 함수처럼 표시형식의 자유로운 지정이 가능한 Perl 함수를 만들어보는 것도 좋은 공부가 될 것이다.

     

8. 연속출력

    정보를 연달아 출력하기 위해서는 수많은 print문을 사용하기도 하고 print문 하나로 다수의 라인을 출력하기도 하지만 “ 기호 등을 표시할 때 \”와 같이 표시해 줘야하므로 매우 불편하다. 하지만 다음과 같이 print문을 print <<”구분문자”;와 같이 사용하게 되면 “구분문자”가 나타날 때까지 계속 출력할 수 있다. 해당범위 내에서는 HTML 문서처럼 코딩해도 크게 문제가 없으며 Perl에서 사용중인 변수를 직접 사용할 수도 있고 “ 등의 큰따옴표를 가공 없이 사용할 수도 있다.  
    유의할 점은 끝을 알리는 “구분문자”는 첫 번째 자릿수부터 입력되어야 한다는 것과 메일주소에 쓰이는 @기호는 \@와 같이 사용해야 한다는 것이다. 만약 print <<’EOF’;와 같이 작은따옴표를 이용해 구분문자를 감싸주면 @ 기호를 그대로 사용할 수 있지만 변수참조가 불가능하다.

     

      #!/usr/bin/perl
      $user = “way”;
      $address = “Kwangju”;
      print “Content-type: text/html\n\n”;
      print <<EOF;
      <HTML>
      <HEAD>
      <TITLE>연속출력 시험</TITLE>
      </HEAD>
      <BODY>
      성명 : “$user”<BR>
      주소 : “$address”<BR>
      </BODY>
      </HTML>
      EOF
      print “출력완료”;

     

9. 메일전송

    게시판에 새로운 글이 등록되거나 방문자가 방명록에 글을 남길 경우, 특정양식의 신청서를 입력받는 경우 메일로 정보가 전달되도록 메일을 전송하는 방법을 알아보자.
    앞서 설명했듯이 파일을 조작하기 위해 다음과 같이 사용했었다.

    open(파일핸들, “[조작방법] 파일명”);

    여기에 조작방법을 “|” 기호로 하고 파일명 대신 명령어(sendmail)나 장치(프린터 등)를 적어주면 Perl에서 정보를 전달할 수 있다.

    다음은 메일을 전송하는 소스이다.  @기호는 \@와 같이 표시하거나 큰따옴표 대신 작은따옴표로 감싸주면 에러가 발생하지 않는다.

     

      #!/usr/bin/perl
      open(MAIL,”|/usr/sbin/sendmail -t”);
        print MAIL “To: way\@abc.co.kr\n”;
        print MAIL “Subject: 제목입니다.\n\n”;;
        print MAIL “메세지 본문입니다.”;
      close (MAIL);

      print “Content-type: text/html\n\n”;
      print “전송완료”;

     

    발송자(From)를 지정하려면 To 아랫줄에 다음과 같이 삽입한다.

    print MAIL “From: 임대호 <lawwal\@way.co.kr>\n”;

    HTML형식으로 메일을 보내려면 Subject 부분을 다음과 같이 바꿔주면 된다.

    print MAIL “Subject: 제목입니다.\n”;
    print MAIL “Content-type: text/html\n\n”;

 

10. 쿠키

    게시판에 글을 쓰면 다음에 글을 쓸 때 자동으로 이름과 메일주소를 적어주거나 접속자 인증에 주로 쿠키가 사용된다.  Perl에서도 쿠키를 직접 핸들링할 수 있다.
    다음 소스는 Perl에서 사용자의 브라우져에 쿠키값을 저장하는 함수이다.

     

      sub cookie_set {
         local($name, $value, $exp) = @_;
         $exp = scalar localtime(time()+(24*60*60)*$exp);
         print “Content-type: text/html\n”;
         print “Set-Cookie: $name=;\n”;
         print “Set-Cookie: $name=$value;expires=$exp;\n”;
       }

     

    사용자의 브라우져에 저장된 쿠키값은 환경변수인 $ENV{‘HTTP_COOKIE’}에 저장된다.
    다음 소스는 쿠키를 읽어들여 %cookie라는 조합배열에 넣어 활용이 가능토록 하는 함수이다.  자세히 보면 앞서 설명한 input_form() 함수와 유사하다는 것을 알 수 있을 것이다.

     

      sub cookie_read {
      @strings = split(/; /,$ENV{‘HTTP_COOKIE’});
      foreach $strs (@strings) {
         local ($name, $value) = split(/=/,$strs);
             $value =~ tr/+/ /;
             $value =~ s/%(..)/pack(“C”, hex($1))/ge;
             $cookie{$name} = $value; }
        }

     

    두 개의 함수를 이용해서 쿠키를 저장하고 읽어보도록 하겠다.

      #!/usr/bin/perl
      &cookie_set(“name”, “way”, 7);   # 키, 값, 만료기일
        print “Content-type: text/html\n\n”;
        print “쿠키를 저장하였습니다.”;

      #!/usr/bin/perl
      &cookie_read;
      print “Content-type: text/html\n\n”;
        print “읽어온 name의 값은 $cookie{‘name’}입니다.”;

 

11. 실행권한과 보안

    Perl뿐만 아니라 모든 CGI가 파일을 조작할 경우 웹서버가 nobody 등으로 실행되므로 nobody 등의 소유로 파일이 생성되고 조작되어진다.  이는 실제 파일소유자가 접근하기도 곤란할뿐더러 동일한 서버를 사용하는 구성원은 공통의 권한을 가지는 형국이 되므로 보안문제가 야기될 수 있다.(역효과를 우려해 소스를 소개하지는 않겠다.)  해결방법으로는 다음 세 가지 정도로 요약할 수 있다. 세 가지 방법 모두 실제 사용자의 권한으로 실행되므로 중요한 정보를 지닌 자료파일이나 디렉토리는 chmod go-rwx 와 같이 해두어도 무방하다.

가. 아파치 suexec 적용

    아파치 설치시 다음과 같이 suexec를 적용하고 컴파일한다.  가상 도메인의 경우 아파치 설정시 <VirtualHost> 부분에 User와 Group을 실제 계정 사용자로 지정해주면 된다.

    # ./configure --prefix=/usr/local/apache
     --enable-suexec
     --suexec-caller=nobody
     --suexec-docroot=/home
     --suexec-logfile=/usr/local/apache/logs/suexec_log
     --suexec-userdir=public_html
     --suexec-uidmin=500
     --suexec-gidmin=500
     --suexec-safepath=/usr/local/bin:/usr/bin:/bin
    # make
    # make install

    * 아파치 VirtualHost 설정
    <VirtualHost 210.123.254.100>
       User           way
       Group          user
       ServerAdmin    webmaster@way.co.kr
       DocumentRoot   /home/way/public_html
       ScriptAlias    /cgi-bin/ /home/way/public_html/cgi-bin/
       ServerName     www.way.co.kr
       ServerAlias    way.co.kr www.way.co.kr
       TransferLog    /home/way/www_log/today.log
       ErrorLog       /home/way/www_log/error.log
    </VirtualHost>

나. cgiwrap 적용

    ftp://ftp.umr.edu/pub/cgi/cgiwrap/cgiwrap-Version.tar.gz
    # tar xvfz cgiwrap-Version.tar.gz
    # ./configure --help(옵션 확인)
    # ./configure --with-httpd-user=nobody
    # make
    # make install
    # cp cgiwrap /usr/local/apache/cgi-bin
    # cd /usr/local/apache/cgi-bin
    # chown root cgiwrap
    # chmod 4755 cgiwrap
    # ln -s cgiwrap cgiwrapd
    # ln -s cgiwrap nph-cgiwrap
    # ln -s cgiwrap nph-cgiwrapd

    필자의 경우 위와 같이 설치한 후 cgi-bin은 이미 /home/way/public_html/cgi-bin 으로 ScriptAlias 되어있으므로 /usr/local/apache/cgi-bin 디렉토리를 cgi-sys로 ScriptAlias해서 다음과 같이 실행하였다.

    * 시스템 경로 : /home/way/public_html/cgi-bin/file.cgi
    * 실행URL : http://way.co.kr/cgi-sys/cgiwrap/way/file.cgi
      (우측의 way는 사용자 ID를 뜻한다.)

다. Set User ID

    Perl 경로를 적는 소스 첫 번째줄에 -U 옵션을 함께 적어준 후 chmod +s way.cgi 와 같은 방법으로 파일에 실행권한을 부여한다.
    #!/usr/bin/perl -U

     

12. 방명록 만들기

    이제 CGI 예제로 간단한 방명록을 한번 만들어 보자.  지면관계상 메일이나 쿠키, 페이지 나눔 등은 배제하도록 하겠다.  참고로 방명록파일(guestbook.dat)은 nobody 등의 웹사용자가 쓸 수 있는 권한이 부여되어야 한다.

    #!/usr/bin/perl
    # 먼저 인자정보를 습득한다.
    &input_form;

    # 전달된 값중 ‘작업’ 항목의 값이 없을 경우
    # 이미 등록된 글을 출력(방명록 보기)하고 종료한다.
    if(!$input{‘작업’}) { &print_guestbook; exit; }

    # 전달된 값중 ‘작업’ 항목의 값이 ‘쓰기’일 경우
    # 쓰기화면을 출력한다.
    elsif($input{‘작업’} eq “쓰기”) {
           &guestbook_write; exit; }

    # 전달된 값중 ‘작업’ 항목의 값이 ‘처리’일 경우
    # 글을 등록하고 등록된 글을 출력한다.
    elsif($input{‘작업’} eq “처리”) {
           &guestbook_add; &print_guestbook; exit; }

    # 어느것도 아닐 경우 그냥 종료한다.
    else { exit; }

    sub input_form {

    # 앞서 설명했던 인자처리함수이다.
    local ($form, $tmp, $key, $value);
    if($ENV{‘REQUEST_METHOD’} eq “GET”) {
      $form = $ENV{‘QUERY_STRING’}; }
    if($ENV{‘REQUEST_METHOD’} eq “POST”) {
      read(STDIN, $form, $ENV{‘CONTENT_LENGTH’}); }
    foreach $tmp (split(/&/, $form)) {
      ($key, $value) = split(/=/, $tmp);
      $key   =~ tr/+/ /;
      $value =~ tr/+/ /;
      $key   =~ s/%(..)/pack(“C”, hex($1))/ge;
      $value =~ s/%(..)/pack(“C”, hex($1))/ge;
      $input{$key} = $value; }
     }

    sub print_guestbook {

    # HTML 머릿말 출력
    &html_start(“방명록 보기”);

    print “<TABLE BORDER=1 WIDTH=500>\n”;

    # 방명록 파일로 사용할 파일명을 지정한다.
    local $file = “guestbook.dat”;

    # 파일이 존재하지 않거나 크기가 0byte면 작성된
    # 자료가 없다고 출력하고 종료한다.
    if(!-e $file || -s $file eq 0) {
      print “<TR><TD>작성된 글이 없습니다.</TD></TR>\n”;
      &guestbook_write_link;
      print “</TABLE>\n”;
      &html_end; exit; }

    local $print_msg;

    # 방명록 파일을 열어 출력한다.
    # 파일을 열 수 없을 경우 종료한다.
    open(FILE, “$file”) || die “파일을 열 수 없습니다.($file)”;
      while(<FILE>) {
        # 한줄을 읽어와서 끝에있는 개행문자(\n)를 제거한다.
        chomp $_;
        # 읽어온 줄의 자료를 | 기호를 기준으로 추출하여
        # @guest 라는 배열에 집어 넣는다.
        local @guest = split(/\|/, $_);
        # 항목별로 추출한다.
        local $name = $guest[0];
        local $mail = $guest[1];
        local $home = $guest[2];
        local $date = $guest[3];
        local $usip = $guest[4];
        local $mesg = $guest[5];
        # 자료를 가공(링크 삽입 및 시간 출력)한다.
        if($mail) { $mail =
         “<A HREF=mailto:$mail>$mail</A>”; }
        if($home) { $home =
         “<A HREF=$home>$home</A>”; }
        $date = &time_conv($date);
        # HTML 형식으로 자료를 변수에 저장한다.
        $print_msg .= “<TR>\n”;
     

       $print_msg .= “<TD><FONT SIZE=2>\n”;
        $print_msg .= “◈ 성명 : $name($mail)<BR>\n”;
        $print_msg .= “◈ 홈페이지 : $home<BR>\n”;
        $print_msg .= “◈ 일자 : $date (IP:$usip)<BR>\n”;
        $print_msg .= “◈ $mesg”;
        $print_msg .= “</FONT></TD>\n”;
        $print_msg .= “</TR>\n”;
      }  # -- while 루프 종단점
    close(FILE);

    # 모든 글을 출력한다.
    print $print_msg;

    # 글쓰기 부분의 링크를 출력한다.
       &guestbook_write_link;
       print “</TABLE>\n”;
    # HTML을 마무리한다.
       &html_end;
    }

    sub guestbook_write {

    &html_start(“방명록 쓰기”);

    # 등록양식을 출력한다.
    # 주의할 사항은 FORM ACTION 부분과
    # 하단의 ‘작업’ 부분이다.
    print <<”EOF”;
    <FORM ACTION=$ENV{‘SCRIPT_NAME’}
                 METHOD=POST>
    <TABLE BORDER=1 WIDTH=500>
    <TR>
    <TD>성명</TD>
    <TD><INPUT TYPE=TEXT NAME=name  SIZE=20></TD>
    </TR>
    <TR>
    <TD>E-mail</TD>
    <TD><INPUT TYPE=TEXT NAME=mail SIZE=35></TD>
    </TR>
    <TR>
    <TD>홈페이지</TD>
    <TD><INPUT TYPE=TEXT NAME=home SIZE=35
            VALUE=”http://”></TD>
    </TR>
    <TR>
    <TD>남기실 내용</TD>
    <TD><TEXTAREA NAME=mesg
               ROWS=5 COLS=35></TEXTAREA>
    </TR>
    <TR>
    <TD COLSPAN=2>
    <INPUT TYPE=SUBMIT VALUE=”글을 등록합니다.”>
    </TD>
    <INPUT TYPE=HIDDEN NAME=”작업” VALUE=”처리”>
    </FORM>
    </TR>

    </TABLE>
    EOF
    &html_end;
    }

    sub guestbook_add {

    # 전달받은 각 항목별 정보를 저장하기 위해 준비한다.
    # 등록된 시간과 글쓴이의 IP주소도 습득한다.
    local $name = $input{‘name’};
    local $mail = $input{‘mail’};
    local $home = $input{‘home’};
    local $date = time();
    local $usip = $ENV{‘REMOTE_ADDR’};
    local $mesg = $input{‘mesg’};

    # $mesg 값에서 개행문자를 공백문자로 변환한다.
    $mesg =~ s/\cM//g;
    $mesg =~ s/\r\n/\n/g;
    $mesg =~ s/\n/ /g;

    # 가급적 자료 구분자로 사용할 | 기호가 입력된
    # 자료를 다른 기호로 변환한다.
    $mesg =~ s/|/\:/g;
    local $file = “guestbook.dat”;
    local $original;

    # 최근에 등록된 글을 먼저 출력하기위해
    # 종전에 등록된 글이 있을 경우 읽어온다.
    if(-e $file) {
    open(FILE, “$file”) || die “파일을 열수 없습니다.($file)”;
      while(<FILE>) { $original .= $_; }
    close(FILE); }

    # 새로운 글을 저장하고 종전의 글도 뒤이어 저장한다.
      open(NEW, “>$file”) || die “파일을 열수 없습니다.($file)”;
      print NEW “$name|$mail|$home|$date|$usip|$mesg|\n”;
      print NEW $original;
    close(NEW);
    }

    sub html_start {

    # TITLE로 인쇄할 정보를 넘겨받는다.
    local($title) = @_;

    # HTML 문서형식으로 출력하겠다고 선언한다.
    print “Content-type: text/html\n\n”;

    # HTML 태그 연속 출력(TITLE 포함)
    print <<”EOF”;
    <HTML>
    <HEAD>
    <TITLE>$title</TITLE>
    </HEAD>
    <BODY>
    <HR SIZE=1>
    <H2>$title</H2>
    <HR SIZE=1>
    EOF
     }

    sub html_end {

    # HTML 출력을 마무리한다.
    print <<”EOF”;
    <HR SIZE=1>
    </BODY>
    </HTML>
    EOF
      }

    sub guestbook_write_link {

    # ‘쓰기’ 링크를 출력한다.
    print <<”EOF”;
    <TR>
    <TD>[ <A HREF=”$ENV{‘SCRIPT_NAME’}?작업=쓰기”>글쓰기</A> ]</TD>
    </TR>
    EOF
     }

    sub time_conv {

    # 시간변환 함수이다.
    # time() 값으로 저장된 자료를 출력형식에 맞게
    # 변환한다.
    local ($data) = @_;
    local ($second, $minute, $hour, $date, $month,
    $year, $weekday, $yearday, $Isdst) = localtime($data);
    $month ++;
    $year += 1900;
    $month < 10 && ($month = “0$month”);
    $date < 10 && ($date = “0$date”);
    $hour < 10 && ($hour = “0$hour”);
    $minute < 10 && ($minute = “0$minute”);
    $second < 10 && ($second = “0$second”);
    return “$year/$month/$date $hour:$minute:$second”;
     }

 

    Perl을 이용해서 할 수 있는 일에는 무엇이 있을까? 그나마 많은 이들에게 알려진 Perl의 용도가 CGI 개발일 것이다. Perl이 CGI 개발도구로 부각되기까지는 자유로운 자료형, 막강한 문자열 처리, 소스의 공개성, CPAN 등을 통해 보급되는 풍부한 모듈과 스크립트 등이 많은 기여를 했다.
    이번 단원에서는 CGI로서의 Perl이 아니라 UNIX 시스템을 좀더 편리하게 다루기 위한 ‘시스템 도구로서의 Perl’을 소개해보도록 하겠다.

     

Ⅲ. Perl 시스템 도구

    사용하는 서버에 mysql이 설치되어 있다면 mysql 설치경로에서 mysql_setpermission 프로그램을 찾아보기 바란다. 이 프로그램은 다름 아닌 Perl로 만들어진 시스템 도구의 일종이다. 이 프로그램은 mysql을 사용함에 있어 번거로운 작업이 아닐 수 없는 database와 user의 생성 및 권한조정 등의 작업을 자동화한 도구이다. 뿐만 아니라 my sqlaccess, mysql_find_rows, mysql_zap 등의 프로그램도 마찬가지로 Perl로 만들어진 시스템 도구의 일종이다. 이렇듯 Perl을 이용해 시스템 도구를 만들어두면 빈번하게 발생하는 복잡한 작업들을 자동화할 수 있어 서버 운영자 및 시스템 사용자에게 편리함을 제공한다.

    Perl 소스 프로그램을 시스템 도구로 직접 사용하기 위해서는 앞서 설명했듯이 실행권한(chmod +x)을 부여하고 소스 첫 번째 줄에 Perl의 위치를 지정해줘야 한다. 또한 Perl 소스 프로그램이 PATH에 걸려있지 않다면 ./program 과 같이 실행해야 한다는 점을 잊지 말자.

     

    1. Perl 명령행 옵션

    Perl을 명령행에서 실행할 경우 목적에 따라 다양한 옵션을 사용할 수 있다. Perl이 시스템 도구로 사용될 때 특히 옵션을 자주 사용하게 되는데, 주로 사용되는 명령행 옵션은 다음과 같다.
     

    옵션

    설명

    e

    주어진 Perl 명령 실행

    p

    지정한 파일을 대상으로 작업

    i

    원본파일을 결과파일로 대체

    c

    구문검사(문법에러 점검)

    v

    Perl 버전정보

    V

    Perl 버전정보(상세)

    w

    경고 출력(개발과정에 주로 사용)

    U

    비안전모드, SUID 적용시 사용

     

    2. 문자열 일괄 치환

    회사명이나 도메인, 메일주소 등이 바뀌었을 때 수많은 문서파일에 적힌 내용을 모두 고치기란 쉬운 일이 아니다. 하지만 Perl을 이용하면 너무도 간단히 변경 내용을 반영할 수 있다. 아래 명령은 html 확장자를 지닌 문서파일에서 “우정”이라는 문자열을 찾아 “사랑”이라는 문자열로 모두 치환하는 명령(Perl 명령행 옵션)이다.
     

      $ perl -pi -e “s/우정/사랑/g”  *.html

     

    find 명령을 이용하면 하위 경로의 문서도 일괄적으로 치환할 수 있다.
     

      $ find . -type f -name “*.html”  \
      -exec perl -pi -e “s/우정/사랑/g” {} \;

     

    3. 파일명 대소문자 일괄변경

    파일명의 대소문자를 구분하지 않는 운영체제(윈도우즈 등)에서 생성된 파일명은 많은 문제를 야기하곤 한다. 특히 HTML 문서에서 링크오류를 유발하거나 파일명을 입력할 때 입력혼선을 가중시키기도 한다. 하지만 다음 도구(recase)를 이용하면 파일명을 소문자 또는 대문자로 일괄적으로 변경할 수 있다.
     

    $ ls
    HOME.GIF LIST.GIF NEXT.GIF
    $ recase * (또는 $ recase -l *)
    $ ls
    home.gif list.gif next.gif
    $ recase -u *
    $ ls
    HOME.GIF LIST.GIF NEXT.GIF

     

    File Name : recase

    #!/usr/bin/perl
    # 사용법 : recase [옵션] [파일 리스트]
    # 옵션 : -l(소문자, 기본값), -u(대문자)
    # 옵션 점검(소문자화 또는 대문자화)
    if($ARGV[0]  =~  /-L/i) { $method=”L”; shift; }
    elsif($ARGV[0]  =~  /-U/i) { $method=”U”; shift; }
    else { $method = “L”; }

    foreach $file (@ARGV) {
    # 디렉토리인 경우 처리하지 않음
    next if -d $file;
    ($dir, $filename)  =  $file  =~  /(.*[\/\\])(.*)$/;
    $filename = $file if $file !~ /\//;
    # 변경이 필요한 파일명인지 확인
    next if $method eq “L” and
    $filename  !~  /[A-Z]/;
    next if $method eq “U” and
    $filename  !~  /[a-z]/;
    # 대소문자 변환
    $filename  =~  tr/A-Z/a-z/ if $method eq “L”;
    $filename  =~  tr/a-z/A-Z/ if $method eq “U”;
    if(-f “$dir$filename”) {
    # 새로운 이름의 파일이 이미 존재하는 경우
    print “Already exist! : $dir$filename\n”;
    next; }
    # 파일명 변경
    rename(“$file”, “$dir$filename”);

     

    특정경로 이하의 모든 파일명을 소문자로 변경하려면 다음과 같이 사용하면 된다.
     

    $ find way-cart -type f -exec recase {} \;

     

    4. 문서형식 변환(UNIX ↔ DOS)

    Perl이나 shell 스크립트 파일을 바이너리(Binary) 모드로 전송(FTP 등)하면 스크립트 실행시 에러가 발생하곤 한다. 이를 극복하기 위해서는 파일을 아스키(ASCII) 모드로 다시 전송하면 되지만, Perl을 이용해 간단히 해결할 수도 있다. 이는 앞서 설명한 ‘문자열 치환’과 동일한 원리다.
     

    # DOS → UNIX 문서형식으로 변환
    $ perl -pi -e “s/\r//” *.cgi

    # UNIX → DOS 문서형식으로 변환
    $ perl -pi -e “s/\n/\r\n/” *.txt

     

    명령(Perl 명령행 옵션)이 기억하기 쉽지만은 않을 테니 shell 스크립트로 한번 만들어 보자. 파일에 실행권한을 부여한 후 사용자들의 PATH에 포함되어있는 경로(/usr/local/bin 등)에 위치해두면 편리하게 사용할 수 있을 것이다.
     

    File Name : dos2unix

    #!/bin/sh
    # DOS → UNIX 문서형식으로 변환
    perl -pi -e “s/\r//” $*

    File Name : unix2dos
    #!/bin/sh
    # UNIX → DOS 문서형식으로 변환
    perl -pi -e “s/\n/\r\n/” $*

     

    $ dos2unix way-board.cgi

     

    5. 파일 크기정보 출력 및 활용

    다음은 지정한 파일의 크기를 출력하는 도구(sizeof)로서, 얻어진 정보는 다른 응용프로그램에서도 활용할 수 있다.

     

    File Name : sizeof

    #!/usr/bin/perl
    # 1. 파일테스트 연산자중 -s(Size) 옵션 이용
    # 2. 하나의 파일만 확인할 때는
    # 다른 응용프로그램에서 활용할 수 있도록
    # 파일크기만 출력
    if($#ARGV eq 0) {
    print -s $ARGV[0]; exit; }
    foreach (@ARGV) {
    print “$_ : “, -s $_, “\n”; }

     

    $ sizeof  *.cgi
    way-admin.cgi : 46031
    way-board.cgi : 19065
    way-notice.cgi : 6264

    그런데 이런 도구는 언제 쓰일까?
    필자는 매주 1G byte가 넘는 자료를 원격으로 백업 받는다. 그런데 백업파일이 저장될 컴퓨터를 항상 켜둘 수 없는 특수한 상황인지라 백업이 종료되면 자동으로 halt시켜야 한다.
    문제는 정상적으로 백업이 이루어졌는지 알 수 없는 상태에서 무턱대고 halt시켜서는 안된다는 점이다. 그래서 생각해낸 것은(물론 다른 방법으로도 가능하다.) 수신된 백업파일 크기를 확인하여 원본파일과 동일한 크기일 경우만 halt명령을 실행하도록 하는 것이다.
     

    File Name : remotebackup

      #!/bin/sh
      # 사용법 : remotebackup [백업파일크기]
      # 파일 다운로드
      ncftpget -u way -p “********”  \
      “ftp://way.co.kr/backup/19991231.tar.gz”
      # 외부명령인 Perl 프로그램을 실행하고
      # 결과를 받아오기 위해 Perl에서와 마찬가지로
      # shell 스크립트도 `(역따옴표)기호 사용
      SIZE=`sizeof 19991231.tar.gz`
      # 입력한 파일크기와 백업 받은 파일크기 비교
      if [ “X$1” = “X${SIZE}” ]
      # 일치(정상백업)할 경우 시스템 종료
      then halt
      fi

     

    6. 수치정보 컴마 표기

     

    File Name : comma

    #!/usr/bin/perl
    # 사용법 : comma [수치]
    1 while $ARGV[0]   =~   s/(\d+)(\d{3})/$1,$2/;
    print $ARGV[0];

     

    이 도구는 또 언제 사용될까? 필자는 계정 사용자의 웹서버 로그파일을 이용해 일일 자료 전송량을 산출한다. 산출된 정보를 출력할 때 식별을 용이하게 하기 위해 이 도구를 이용해 3자리마다 컴마를 넣어 출력하고 있다.

    $ comma 1234567890
    1,234,567,890

    앞서 소개한 remotebackup과 마찬가지로 shell 스크립트 등의 응용프로그램에서 외부명령으로 comma 명령을 사용하면 보기 좋게 수치정보를 표시할 수 있을 것이다.

    파일 크기를 파악한 후 보기 좋게 출력해보자.

    $ comma `sizeof way-board.cgi`
    19,065

     

    7. 파일 부분 출력

    이제 소개할 도구는 특정 파일의 일부분을 출력하는 도구다. 예를 들어 tmp라는 파일의 5번째 줄부터 3줄을 출력하고자 한다면 다음과 같이 사용한다.

     

    $ partprint 5 3 tmp

     

    $ cat tmp
    Line 1
    Line 2
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    Line 8
    Line 9
    Line 10

    $ partprint 5 3 tmp

            FILE : tmp
            LINE : (from 5 to 7 / total 10)

    Line 5
    Line 6
    Line 7

     

    File Name : partprint

    #!/usr/bin/perl
    # 사용법 : partprint [시작줄] [출력줄] [파일]
    $start = shift;
    $end = shift;
    $filename = shift;
    open(FILE, “$filename”) or
    die “Error: $filename, $!”;
    @File = <FILE>;
    close(FILE);
    $start --;
    $start = 0 if $start < 0;
    $end = 0 if $end < 0;
    $end  --;
    $end = $start + $end;
    print “=” x 80, “\n”;
    print “FILE : $filename\n”;
    print “LINE : “;
    print “(from “, $start + 1, “ to “, $end + 1,
    “ / total “, $#File+1, “)\n”;
    print “=” x 80, “\n”;
    @File = @File[$start .. $end];
    foreach (@File) { print $_; }

     

     

    8. 웹호스팅 서버관리 시스템

     

    이제 간단하면서도 종합적인 기능을 수행하는 시스템 도구를 만들어 보자. 이 도구는 세 곳의 웹호스팅 업체에서 10개월 전부터 유용하게 사용되고 있다. 지면으로 옮기기 어려운 일부 기능은 배제하였으며, 소스를 소개하고 각 부분의 기능을 설명함으로써 이해와 응용을 돕고자 한다.


    실제 사용을 원한다면 임시 서버를 이용해 여러 차례 시험을 거친 후 사용하기를 권하며 super user 권한으로 실행되므로 세심한 주의를 요한다.

     

    주요 기능은 다음과 같다.


    가. 사용자 계정 관리
    나. POP 계정 관리
    다. 디스크 할당
    라. 도메인 설정
    마. 데몬(bind, sendmail, apache) 관리

     

    #!/usr/bin/perl


    #=====================================
    config();
    menu();
    #=====================================

     

    sub config {
    # 이 루틴은 시스템에서 사용할 주요 설정을 선언하는
    # 부분이다.

    # 그룹 및 사용자 기본값으로서
    # 이미 존재하는 그룹 및 사용자여야 한다.
    # 계정 추가시 기본 그룹
    $user_basegroup = “user”;
    # POP계정 추가시 기본 그룹
    $pop_basegroup = “pop”;
    # 디스크 할당 표본 사용자
    $disk_baseuser = “way”;

    # 사용자 계정 관리 등에서 조작이 허용되는 최소 UID
    $min_uid = 500;

    # 사용자에게 부여할 IP 주소를 기록한다.
    # Name-based Virtual Host(apache)를 전제로 한다.
    $ip_address = “210.123.12.1”;

    # 아래 경로들은 /etc/skel에 미리 생성되어 있어야 한다.
    # $ mkdir /etc/skel/public_html  /etc/skel/wwwlog
    # 웹문서 경로
    $webdir = “public_html”;
    # 웹로그 경로
    $logdir = “wwwlog”;

    # 아래는 특수한 경우가 아닌 한 변경할 필요가 없다.
    # shadow 시스템을 사용하더라도 변경할 필요가 없다.
    # 비밀번호 파일
    $pass_file = “/etc/passwd”;
    # 그룹파일
    $group_file = “/etc/group”;

    # Message
    $prog_msg = “[MSG] “;
    }

     

    #=====================================

     

    sub menu {
    # 이 루틴은 기본 메뉴화면을 출력하는 기능을 한다.

    # 실행자가 super user가 아니면 종료한다.
    chomp($run_id = `id -u`);
    if($run_id ne “0”) {
    print “$prog_msg 실행권한이 없습니다.(UID:$run_id)\n”;
    exit; }

    # get_date 함수를 통해 현재 시간정보를 얻는다.
    $date_info = get_date(time);
    $date_seri = “$date_info” . “01”;

    # 화면을 정리하고 출력한다.
    system(“clear”);
    print  <<”__END__”;

            웹호스팅 서버 관리 시스템
    11. 계정 등록                      26. 사용자 password 지정
    12. 계정 삭제                      27.
    13. 계정 조회                      28. 프로세서 조회
    14. 전체 계정 조회               29. 데몬 관리
    15. POP 계정 등록               30.
    16.                                    31.
    17. 디스크 할당                   32.
    18. 디스크 할당(수동)           33.
    19. 디스크 할당 조회            34.
    20.                                    35.
    21. 도메인 등록                   36.
    22. 도메인 정보 갱신            37.
    23. 도메인 정보 상세조회      38.
    24.                                    39.
    25.                                    40.
    \\. EXIT
    __END__

    print “ 선택 : “;
    $choice  =  <STDIN>;
    chomp $choice;
    print “-”  x 80, “\n”;


    # 입력한 메뉴번호에 따라 해당되는 기능을 수행한다.
    if ($choice eq 11) { add_user(); }
    elsif ($choice eq 12) { delete_user(); }
    elsif ($choice eq 13) { query_user(); }
    elsif ($choice eq 14) { all_user_info(); }
    elsif ($choice eq 15) { pop_add(); }
    elsif ($choice eq 17) { disk_quota(); }
    elsif ($choice eq 18) { disk_quota_edit(); }
    elsif ($choice eq 19) { disk_query(); }
    elsif ($choice eq 21) { add_domain(); }
    elsif ($choice eq 22) { domain_apply(); }
    elsif ($choice eq 23) { domain_display(); }
    elsif ($choice eq 26) { pass_define(); }
    elsif ($choice eq 28) { ps_ax(); }
    elsif ($choice eq 29) { demon(); }
    elsif ($choice eq “\\”) { print “\nbye...\n\n”; exit; }
    else { menu(); }

    }

     

    #=====================================

    # 여기서부터는 시스템에서 공통적으로 사용되는
    # 함수들이다.

     

    sub check {
    # 명령의 실행을 최종적으로 확인하도록 하는 루틴
    print “$prog_msg 확실합니까? [yes/No] “;
    $input  =  <STDIN>;
    chomp $input;
    if ($input  =~  /yes/i) { return; }
    else { print “\n$prog_msg 실행을 취소합니다. [Enter] “;
    $input = <STDIN>;
    menu(); }
    }

     

    sub end {
    # 실행결과를 출력하고 초기화하는 루틴
    local ($end) = @_;
    if ($end eq “”) { $end = “처리가 완료되었습니다.”; }
    print “\n$prog_msg $end [Enter] “;
    $input = <STDIN>;
    exec(“$0”);
    }

     

    sub file_error {
    # 파일 조작시 문제가 발생하면 에러사실을 경고하는 루틴local ($err) = @_;
    if ($err eq “”) { $err = “파일 저장 오류, $FILE”; }
    print “=” x 30 . “ 중대 오류 발생 “ . “=”  x 30 . “\n”;
    print “ 오류 내용 : $err \n”;
    print “=” x 80;
    print “중대한 오류입니다.”;
    print “ 해당 파일을 면밀히 검토하시기 바랍니다.\n”;
    exit;
    }

     

    sub user_check {
    # 넘겨받은 사용자 ID 등의 정보를 이용해 사용자 정보를
    # 얻는 함수
    local ($search_data, $search_field) = @_;
    $FILE = “$pass_file”;
    open(FILE, “$FILE”) || file_error(“파일 읽기 오류, $FILE”);
    while($user_data = <FILE>) {
    chomp $user_data;
    @ud  =  split(“:”, $user_data);
    $search_fld  =  $search_field  - 1;
    $search_fld_data = $ud[$search_fld];
    if ($search_data eq $search_fld_data) {
    return $user_data; }
    }
    close(FILE);
    return 0;
    }

     

    sub group_check {
    # 넘겨받은 그룹 ID 등의 정보를 이용해 그룹 정보를
    # 얻는 함수
    local ($search_data, $search_field) = @_;
    $FILE = “$group_file”;
    open(FILE, “$FILE”) || file_error(“파일 읽기 오류, $FILE”);
    while($group_data  =  <FILE>) {
    chomp $group_data;
    @gd = split(“:”, $group_data);
    $search_fld = $search_field - 1;
    $search_fld_data = $gd[$search_fld];
    if ($search_data eq $search_fld_data) {
    return $group_data; }
    }
    close(FILE);
    return 0;
    }

     

    sub get_date {
    # 시간정보를 얻는 함수
    local ($data) = @_;
    local ($second, $minute, $hour, $date, $month,
    $year, $weekday, $yearday, $Isdst) = localtime($data);
    $month  ++;
    $year  +=  1900;
    $month  <  10 && ($month  = “0$month”);
    $date  <  10 && ($date  = “0$date”);
    $hour  <  10 && ($hour  = “0$hour”);
    $minute  <  10 && ($minute  = “0$minute”);
    $second  <  10 && ($second  = “0$second”);
    return “$year$month$date”;
    }

     

    #=====================================

    # 여기서부터는 메뉴에 나열된 실제 작업을 수행하는
    # 루틴이다.

     

    sub add_user {
    # 11. 계정 등록
    print “$prog_msg 등록할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if ($id eq “”) { add_user(); }
    if ($id eq “\\”) { menu(); }
    if (user_check($id, “1”)) {
    end(“이미 등록되어있는 ID입니다.”); }
    print “$prog_msg 등록할 사용자의 소속그룹”;
    print “(default=$user_basegroup) : “;
    $grp  =  <STDIN>;
    chomp $grp;
    if  ($grp eq “”) { $grp = $user_basegroup; }
    if  ($grp eq “\\”) { menu(); }
    if  (!group_check($grp, “1”)) {
    end(“등록되지 않은 GROUP입니다.”); }
    check();
    # adduser 명령에 -g 옵션을 사용해 그룹을 지정한다.
    system(“/usr/sbin/useradd $id -g $grp”);
    # 웹로그 경로의 소유권을 super user로 바꾼다.
    system(“/bin/chown 0.0 /home/$id/$logdir”);
    end();
    }

     

    sub delete_user {
    # 12. 계정 삭제
    print “$prog_msg 삭제할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { delete_user(); }
    if  ($id eq “\\”) { menu(); }
    if  (!($user_data = user_check($id, “1”))) {
    end(“등록되지 않은 ID입니다.”); }
    ($ud1, $ud2, $ud3, $ud4, $ud5, $ud6, $ud7)
    =  split(“:”, $user_data);
    if  ($ud3 < $min_uid) {
    end(“해당 ID를 삭제할 수 없습니다.”); }
    check();
    # userdel 명령에 -r 옵션을 이용해 계정을 삭제한다.
    # 자료보호를 위해 삭제할 계정의 홈경로가 /home 경로
    # 하위에 위치하지 않으면 홈경로를 삭제하지 않는다.
    if($ud6 =~ /^\/home/) { $home_del = “-r”; }
    system(“/usr/sbin/userdel $home_del $id”);
    end();
    }

     

    sub query_user {
    # 13. 계정 조회
    # 지정한 사용자에 대해 다음과 같은 결과를 출력한다.
    # [MSG] 조회할 사용자 ID : way
    # [MSG] ID : way (539)
    # [MSG] GROUP : user (500)
    # [MSG] HOME DIR : /home/way
    # [MSG] SHELL : /bin/bash
    print “$prog_msg 조회할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { query_user(); }
    if  ($id eq “\\”) { menu(); }
    if  (!($user_data = user_check($id, “1”))) {
    end(“등록되지 않은 ID입니다.”); }
    ($ud1, $ud2, $ud3, $ud4, $ud5, $ud6, $ud7)
    =  split(“:”, $user_data);
    $s_group  =  $ud4;
    if  (!($group_data = group_check($s_group, “3”))) {
    end(“등록되지 않은 GROUP입니다. (GID=$s_group)”); }
    ($gd1, $gd2, $gd3, $gd4) = split(“:”, $group_data);
    print “$prog_msg ID : $id ($ud3)\n”;
    print “$prog_msg GROUP : $gd1 ($s_group)\n”;
    p

    rint “$prog_msg HOME DIR : $ud6\n”;
    print “$prog_msg SHELL : $ud7\n\n”;
    end(“조회가 완료되었습니다.”);
    }

     

    sub all_user_info {
    # 14. 전체 계정 조회
    # 비밀번호 파일을 출력한다.
    $FILE = “$pass_file”;
    open(FILE, “$FILE”) || file_error(“파일 읽기 오류, $FILE”);
    while($user_data  =  <FILE>) {
    chomp $user_data;
    @ud = split(“:”, $user_data);
    $search_fld_data = $ud[2];
    if ($search_fld_data > =  $min_uid ) {
    $print_data .= “$user_data\n”; }
    }
    close(FILE);
    print $print_data;
    end(“조회가 완료되었습니다.”);
    }

     

    sub pop_add {
    # 15. POP 계정 등록
    print “$prog_msg 등록할 POP ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { pop_add(); }
    if  ($id eq “\\”) { menu(); }
    if  (user_check($id, “1”)) {
    end(“이미 등록되어있는 ID입니다.”); }
    print “$prog_msg 등록할 사용자의 소속그룹”;
    print “(default=$pop_basegroup) : “;
    $grp = <STDIN>;
    chomp $grp;
    if  ($grp eq “”) { $grp = $pop_basegroup; }
    if  ($grp eq “\\”) { menu(); }
    if  (!group_check($grp, “1”)) {
    end(“등록되지 않은 GROUP입니다.”); }
    check();


    # POP 계정은 noshell이라는 간단한 쉘스크립트를
    # 기본 shell로 지정한다.

     

    File Name : /usr/local/bin/noshell
    #!/bin/sh


    echo
    echo “ ============================= “
    echo “ 현재 접속하신 ID는”
    echo “ TELNET, FTP 접속이 허용되지”
    echo “ 않습니다.”
    echo “ ============================= “
    echo

    system(“/usr/sbin/useradd $id -g $grp -M  \\
    -s /usr/local/bin/noshell -d /”);
    end();
    }

     

    sub disk_quota {
    # 17. 디스크 할당
    # quota 기능을 이용해 사용자의 디스크를 할당한다.
    print “$prog_msg 디스크를 할당할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { disk_quota(); }
    if  ($id eq “\\”) { menu(); }
    if  (!user_check($id, “1”)) {
    end(“등록되지 않은 ID입니다.”); }
    print “$prog_msg 표본 사용자 ID(default:$disk_baseuser) : “;
    $id_2  =  <STDIN>;
    chomp $id_2;
    if  ($id_2 eq “”) { $id_2 = $disk_baseuser; }
    if  ($id_2 eq “\\”) { menu(); }
    if  (!user_check($id_2, “1”)) {
    end(“등록되지 않은 ID입니다.”); }
    check();
    # edquota 명령에 -p 옵션을 이용해 새로 지정할
    # 사용자에게 표본 사용자의 디스크 할당정보를
    # 복사한다.(man edquota)
    system(“/usr/sbin/edquota -p $id_2 $id”);
    end();
    }

     

    sub disk_quota_edit {
    # 18. 디스크 할당(수동)
    print “$prog_msg 디스크를 할당할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { disk_quota_edit(); }
    if  ($id eq “\\”) { menu(); }
    if  (!user_check($id, “1”)) {
    end(“등록되지 않은 ID입니다.”); }
    check();
    system(“/usr/sbin/edquota -u $id”);
    end();
    }

     

    sub disk_query {
    # 19. 디스크 할당 조회
    print “$prog_msg 디스크 사용상황을 조회할”;
    print “ 사용자 ID(default:all) : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { $id = “ALL”; }
    if  ($id eq “\\”) { menu(); }
    if  ( !user_check($id, “1”) && ($id ne “ALL”) ) {
    end(“등록되지 않은 ID입니다.”); }
    # 전체 사용자의 디스크 정보는 repquota 명령
    # (man repquota)을 이용하고, 개별 사용자는 quota 명령
    # (man quota)을 이용한다.
    if($id eq “ALL”) { system(“/usr/sbin/repquota -avug”); }
    else { system(“/usr/bin/quota -u $id”); }
    end(“조회가 완료되었습니다.”);
    }

     

    sub add_domain {
    # 21. 도메인 등록
    print “$prog_msg 도메인을 등록할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { add_domain(); }
    if  ($id eq “\\”) { menu(); }
    if  (!user_check($id, “1”)) {
    end(“등록되지 않은 ID입니다.”); }
    print “$prog_msg 등록할 도메인(ex. abcd.co.kr) : “;
    $domain  =  <STDIN>;
    chomp $domain;
    if  ($domain eq “”) { add_domain(); }
    if  ($domain eq “\\”) { menu(); }
    $domain  =~  tr/[A-Z]/[a-z]/;
    $domain  =~  s/^ +| + _$//g;
    # 중복을 막기 위해 네임서버 zone 파일이
    # 이미 존재하는지 점검한다.
    if(-e “/var/named/custom/$domain”) {
    print “\n$prog_msg ERROR, 동일한 도메인이”;
    print “ 이미 등록되어 있습니다. [Enter] “;
    $input  =  <STDIN>;
    menu(); }
    check();

    # bind 8.x대에서 사용하는 /etc/named.conf에
    # 추가할 정보를 생성한다.
    $named_conf  =  <<”__END__”;

    zone “$domain” {
    type master;
    file “custom/$domain”;
    };

    __END__

    # zone 파일(/var/named/custom/도메인명)에
    # 기록할 정보를 생성한다.
    # serial 부분의 $date_seri 값은 일자정보에
    # 번호(01)를 조합한 값(1999123101)을 사용한다.
    $named_zone  =  <<”__END__”;
            @       IN      SOA     ns.way.co.kr domain.way.co.kr.
            (
                                    $date_seri   ; serial
                                    8H ; refresh
                                    2H ; retry
                                    1W ; expiry
                                    1D ) ; minimum

                    IN      NS      ns.way.co.kr.
                    IN      NS       dns.way.co.kr.
                    IN      A       $ip_address
                    IN      MX      10 mail.way.co.kr.
        www     IN      A       $ip_address
                    IN      MX      10 mail.way.co.kr.
    __END__

    # apache 설정파일(httpd.conf)의 VirtualHost
    # 정보를 생성한다.
    # httpd.conf 파일에 미리 NameVirtualHost
    # 지시자를 선언해 두어야 한다.
    # 추가할 모든 도메인이 동일한 IP를 사용하는
    # Name-based Virtual Host 임을 전제로 한다.
    $httpd_conf  =  <<”__END__”;
    <VirtualHost $ip_address>
    ServerAdmin webmaster\@$domain
    DocumentRoot /home/$id/$webdir
    ScriptAlias /cgi-bin/ /home/$id/$webdir/cgi-bin/
    ServerName www.$domain
    ServerAlias $domain www.$domain
    TransferLog /home/$id/$logdir/today.log
    ErrorLog /home/$id/$logdir/error.log
    </VirtualHost>

    __END__

    # 생성한 named.conf 정보를 파일에 기록한다.
    $FILE = “/etc/named.conf”;
    open(FILE, “>>$FILE”) || file_error();
    print FILE $named_conf;
    close(FILE);

    # 생성한 named zone 파일 정보를 기록한다.
    $FILE = “/var/named/custom/$domain”;
    open(FILE, “>$FILE”) || file_error();
    print FILE $named_zone;
    close(FILE);

    # 추가한 도메인으로 도착한 메일을 수신할 수 있도록
    # 설정한다.
    $FILE = “/etc/sendmail.cw”;
    open(FILE, “>>$FILE”) || file_error();
    print FILE “$domain\n”;
    close(FILE);

    # 도메인별로 가상 사용자 mail을 사용할 수
    # 있도록 virtusertable을 설정(추가)한다.
    # @way.co.kr way라고 지정하면 way.co.kr 도메인으로
    # 수신되는 모든 메일은 way 계정으로 배달된다.
    # sendmail.cf 파일에 virtusertable을 사용할 수 있는
    # 설정이 되어있어야 한다.
    $FILE = “/etc/mail/virtusertable”;
    open(FILE, “>>$FILE”) || file_error();
    print FILE “\@$domain $id\n”;
    close(FILE);

    # 추가한 도메인의 VirtualHost정보를 httpd.conf
    # 파일에 기록(추가)한다.
    # apache를 RedHat rpm으로 설치한 경우에는
    # /etc/httpd/conf/httpd.conf 파일에 반영해야한다.
    $FILE = “/usr/local/apache/conf/httpd.conf”;
    open(FILE, “>>$FILE”) || file_error();
    print FILE “$httpd_conf”;
    close(FILE);
    end();
    }

     

    sub domain_apply {
    # 22. 도메인 정보 갱신
    # “도메인 등록”후 등록내용을 반영하기 위해 실행한다.
    # apache를 RedHat rpm으로 설치한 경우에는
    # /etc/rc.d/init.d/httpd 파일을 이용한다.
    # restart를 이용할 수도 있지만 연속되는 실행에 약간의
    # 여유를 두기 위해 1초 간격으로 stop과 start를 실행한다.

    check();
    system(“/usr/local/apache/bin/apachectl stop”);
    sleep 1;
    system(“/usr/local/apache/bin/apachectl start”);
    sleep 1;
    system(“/etc/rc.d/init.d/named stop”);
    sleep 1;
    system(“/etc/rc.d/init.d/named start”);
    sleep 1;
    system(“/etc/rc.d/init.d/sendmail stop”);
    sleep 1;
    system(“/etc/rc.d/init.d/sendmail start”);
    sleep 1;
    end();
    }

     

    sub domain_display {
    # 23. 도메인 정보 상세조회
    # 도메인의 등록내용(각 설정파일)을 조회한다.
    # vi editor를 이용해 설정파일들을 조회하고
    # 편집할 수 있다.
    print “$prog_msg 조회할 도메인(ex. abcd.co.kr) : “;
    $domain  =  <STDIN>;
    chomp $domain;
    if  ($domain eq “”) { domain_display(); }
    if  ($domain eq “\\”) { menu(); }
    $domain_file_list = “/etc/named.conf “;
    $domain_file_list .= “/var/named/custom/$domain “;
    $domain_file_list .= “/etc/sendmail.cw “;
    $domain_file_list .= “/etc/mail/virtusertable “;
    $domain_file_list .= “/usr/local/apache/conf/httpd.conf “;
    check();
    system(“vi $domain_file_list”);
    end();
    }

     

    sub pass_define {
    # 26. 사용자 password 지정
    print “$prog_msg password를 지정(변경)할 사용자 ID : “;
    $id  =  <STDIN>;
    chomp $id;
    if  ($id eq “”) { pass_define(); }
    if  ($id eq “\\”) { menu(); }
    if  (!($user_data = user_check($id, “1”))) {
    end(“등록되지 않은 ID입니다.”); }
    ($ud1, $ud2, $ud3, $ud4, $ud5, $ud6, $ud7)
    =  split(“:”, $user_data);
    if  ($ud3 < $min_uid) {
    end(“해당 ID의 password를 지정(변경)할 수 없습니다.”); }
    check();
    system(“/usr/bin/passwd $id”);
    end();
    }

    sub ps_ax {
    # 28. 프로세서 조회
    system(“/bin/ps ax”);
    end();
    }

     

    sub demon {
    # 29. 데몬 관리
    # “도메인 정보 갱신”과 “프로세서 조회”를 통해 데몬의
    # 수동 조작(stop, start, restart)이 필요한 경우 사용한다.
    print “$prog_msg {httpd,named,sendmail}”;
    print “ {start,stop,restart}\n”;
    print “$prog_msg # “;
    $cmd  =  <STDIN>;
    chomp $cmd;
    if  ($cmd eq “”) { demon(); }
    if  ($cmd eq “\\”) { menu(); }
    ($cmd, $act) = $cmd =~ /^(.*) (.*)$/;
    if($cmd  =~  /^httpd/) {
    $cmd = “/usr/local/apache/bin/apachectl”; }
    elsif($cmd  =~  /^named/) {
    $cmd = “/etc/rc.d/init.d/named”; }
    elsif($cmd  =~  /^sendmail/) {
    $cmd = “/etc/rc.d/init.d/sendmail”; }
    # 임의작동을 막기 위해 httpd, named, sendmail 이외의
    # 데몬은 조작할 수 없도록 한다.
    else { end(“허용되지 않는 명령입니다.”); }
    system(“$cmd $act”);
    demon();
    end();
    }

     


 

출처: http://www.linuxlab.co.kr

       임대호( lawwal@way.co.kr / Way-CGI 시리즈 개발자)

 

 

 

 

 

'OS > LINUX' 카테고리의 다른 글

/dev/null 다시 만들기  (0) 2011.06.04
/dev/zero와 /dev/null  (0) 2011.06.04
프로세스 - fork & exec  (0) 2011.05.14
쉘 스크립트 #6  (0) 2011.05.14
쉘스크립트 ex5  (0) 2011.05.14

출처 kal2suma의 공부터 | 하기나름
원문 http://blog.naver.com/kal2suma1205/100002345091
프로세스
지금 터미널에서 ps 명령을 실행하면 수많은 프로세스가 떠 있음을 알수 있다. 프로세스란 시스템상에서 어떠한 명령을 실행함으로써 사용자와 OS, OS 와 시스템 간의 대화가 가능하도록 해주는 실행 객체이다.
이러한 프로세스는 여러분이 이미지를 보거나, 웹서핑을 하거나, 음악을 듣거나 혹은 웹서비스를 하는등 각 객체의 특성에 따라 다양한 업무를 수행하게 된다.
[root@localhost root]# ps -aux
 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 1384 520 ? S 21:37 0:04 init
root 2 0.0 0.0 0 0 ? SW 21:37 0:00 [keventd]
root 3 0.0 0.0 0 0 ? SW 21:37 0:00 [kapm-idled] 
root 4 0.0 0.0 0 0 ? SWN 21:37 0:00 [ksoftirqd_CPU0] 
root 5 0.0 0.0 0 0 ? SW 21:37 0:00 [kswapd]
 ...
root 930 0.0 0.3 2920 1016 tty1 S 21:39 0:00 /bin/sh /usr/X11R 
root 937 0.0 0.2 2320 672 tty1 S 21:39 0:00 xinit /etc/X11/xi 
root 938 0.7 5.9 74844 15056 ? S< 21:39 0:34 /etc/X11/X :0 
root 946 0.0 0.4 2956 1108 tty1 S 21:39 0:00 /bin/bash /usr/bi 
root 1011 0.0 1.9 16044 5020 ? S 21:39 0:00 kdeinit: Running.
... 
그런데 이러한 프로세스가 어느순간 갑자기 생기진 않았을것이다. 최초의 어느한 시작점이 있을것이다. 이 시작점에 대해서 알아보자

init 프로세스
모든 프로세스는 위에서 처럼 PID를 가지고 또한 PPID 를 가진다. PID란 프로세스자신을 가리키는 일련의 번호이며 PPID란 자신을 실행시킨 부모프로세스의 PID를 가리킨다.
그렇다면 자신의 부모 프로세스를 실행시킨 프로세스가 존재할것이고, 또 그 부모 프로세스를 실행한 프로세스가 존재할것이다.
이런 식으로 유추해서 생각해 보면 결국 최초의 조상 프로세스가 존재할것이라는 결론에 도달할수 있을것인데(인류로 생각해보자면, "아담" 정도), 그것이 바로 PID 1번을 가지는 init 프로세스이다.
모든 프로세스는 init 로부터 fork & exec 과정을 거쳐서 독립된 프로세스로 임무를 수행하게 된다.

fork 와 exec
그럼 fork 와 exec 에 대해서 좀 알아보도록 하겠다.
fork 와 exec 는 둘다 유닉스 시스템에서 새로운 프로세스를 생성시키기 위해서 사용하는 System Call 함수들이다.
둘다 새로운 프로세스를 생성하는데 그 행동이 약간 다르다.
fork 의 경우는 어떤 프로세스에서 fork 를 실행하게 되면, 자신의 프로세스와 똑같은 프로세스를 copy-on-write 형식으로 실행하게 되며, 이때 생성된 프로세는 자신만의 PID를 가지고 독자적인 길을 가게 된다. 물론 이때 생성된 프로세서의 부모 프로세스는 최초에 실행된 프로세스가 될것이다.
우리는 이러한 최초의 프로세스를 부모 프로세스라고 하고 fork 되어서 만들어진 프로세스를 자식 프로세스라고 한다.

예제 fork.c
#include <unistd.h>
#include <stdlib.h> 
#include <string.h> 
int main() 
{ 
int pid; pid = fork();
 if (pid == -1) { perror("fork error ");
 exit(0); } // 자식프로세스일경우 
else if (pid == 0) 
{
printf("자식\n"); 
pause(); } // 부모프로세스의 경우 
else { printf("부모\n"); 
pause();
 } 
} 
위의 프로그램을 실행시키고
[root@localhost test]# ./fork 부모 자식 
ps 를 이용해서 확인해 보면 아래와 같은 ps 상태를 보여줄것이다.
[root@localhost root]# ps -efjc | grep fork | grep -v fork
UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD 
root 2375 1963 2375 1963 - 30 23:20 pts/6 00:00:00 ./fork
root 2376 2375 2375 1963 - 30 23:20 pts/6 00:00:00 ./fork 

보면 알겠지만 똑같은 이름의 프로세스가 2개 생성되었음을 알수 있으며, 2375 가 부모프로세스이고, 2376 이 2375 의 자식프로세스임을 PPID 정보를 이용해서 알수 있을것이다.
이처럼 fork 는 프로세스의 복사본을 만들때 사용한다.


이러한 fork 의 특성으로 다수의 클라이언트 연결을 처리해야 하는 네트웍서버 를 제작할때 매우 흔히 사용된다.


fork 함수를 실행하면 int 형의 정수를 넘겨주게 되는데, 0일경우 자식프로세스, 0보다 큰정수일경우 부모프로세스를 실행하게 된다.
여러가지 이유로 fork 가 실패한다면 -1 을 돌려주게 될것이다.

exec
exec 는 흔히 exec 계열함수군에 의해서 구현되며, exec 함수군에는 execl(3), execlp, execle.. 등이 있다. 모두 같은 일을 하며, 단지 프로그램실행 아규먼트를 다루는데 약간씩의 차이를 가지고 있을 뿐이다.
exec 역시 fork 와 마찬가지로 새로운 프로새스를 생성시키지만, fork 와 같이 copy-on-write 를 이용한 전혀 새로운 프로세스를 실행시키지 않고, 현재의 프로세스이미지를 새로운 프로세스 이미지가 덮어써 버린다.
다음의 예제를 컴파일한후 실행하면 이해가 쉬울것이다.

myexec.c

#include <unistd.h>
#include <string.h>
int main()
{
 printf("원래 프로세스 : %d\n", getpid());
 sleep(1); execl("/bin/sh", "sh", NULL);
 printf("I will be back\n"); // 실행될까 ?
 exit(0);
} 
위의 프로그램을 컴파일한후 실행시키면 "/bin/sh" 가 실행되고 쉘 프롬프트가 사용자 입력을 기다리는걸 볼수 있을것이다.
여기에서 exit 명령을 사용해서 쉘을 종료하면 어떻게 될까 ?
다음 의 printf 행을 실행해서 "I will be back" 라는 문장을 볼수 있게 될까 ?
대답은 아니오 이다. 이유는 위에서 설명했듯이, execl 함수를 호출해서 /bin/sh 를 수행한순간 myexec 프로세스를 /bin/sh 가 덮어써 버리기 때문이다.
ps 를 이용해서 한번 확인을 해보도록 하자.
먼저 ./myexec 를 실행시키고
[root@localhost test]# ./myexec 원래 프로세스 : 2447 sh-2.05# 

 

ps 를 이용해서 myexec 라는 이름의 process 라는 이름의 프로세스가 생성되었는지 알아보자.

[root@localhost root]# ps -aux | grep myexec
 root 2449 0.0 0.2 2492 684 pts/9 S 23:40 0:00 grep myexec 

나타나지 않음을 알수 있다. 그러면 pid 2447 번이 존재하는지 한번 확인해 보도록 하자.

[root@localhost root]# ps -aux | grep 2447
root 2447 0.0 0.5 3280 1356 pts/6 S 23:39 0:00 sh 

위의 결과를 보면 알겠지만 myexec 대신 execl 함수를 이용해서 실행시킨 sh 가 2447 프로세스를 완전히 덮어써 버렸음을 알수 있을것이다.

참 그리고 system(3) 이라는 새로운 프로세스를 시키는 함수도 있는데, fork & exec 의 다른 구현으로 보면 될것이며, 실제로 system 을 사용하지 않고, fork & exec 를 통하여 구현하는 경우도 있다.


위의 fork 와 exec 의 개념을 완전히 이해했다면 이제 단지 하나의 init 프로세스에서 다른 모든 프로세스가 fork & exec 방식으로 어떻게 파생되어서 실행되는지 감을 잡을수 있을것이다.
init 프로세스에게 어떤 프로세스를 실행시켜라는 메시지가 전달되면, init 는 fork 를 이용해서 자기자신을 복사한 자식 프로세스를 하나 실행시키게 될것이다. 그리고 나서 복사된 자식프로세스에서 exec 를 써서 새로운 프로세스를 실행시키면 init 는 새로운 프로세스로 대체실행되는 것이다.

 

 new_process 를 실행하라
 | 
 V 
+---------+ fork +---------+ exec(new_process) +-------------+ 
|init(1) |----->>| init(?) |-------------------->| new_process | 
+---------+ +---------+ +-------------+ 


프로세스의 집단(group)과 세션(session)
우주를 예로 들어보면 행성 하나하나를 프로세스라고 생각할수 있을것이다, 그런데 보통 행성은 하나의 항성계에 포함되게 된다. 태양계와 같은 것이 일반적인 경우가 될것이며, 이러한 항성계는 다시 은하계라는 더욱 큰 규모의 천체에 속하게 된다.
프로세스도 마찬가지이다. 각각의 프로세스는 어떠한 집단에 포함될수 있으며, 각각의 집단은 더욱큰 집단에 포함될수 있는데, 이러한 각각의 집단을 우리는 Group 라고 부르며, 여러개의 Gruop을 포함하는 더큰 그룹을 세션이라고 부른다.
보통 그룹은 어떠한 작업을 하기 위해서 공통의 목적으로 생성된 프로세스들의 집단을 말하는데, 가장 일반적인 예가 fork 로 생성된 자식 부모간의 프로세스 그룹이 될것이다.
[yundream@localhost yundream]$ ps -efjc | grep httpd
 UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD
 root 29635 1 29635 29635 - 30 Mar20 ? 00:00:00 httpd
nobody 29636 29635 29635 29635 - 30 Mar20 ? 00:00:09 httpd
nobody 29637 29635 29635 29635 - 30 Mar20 ? 00:00:07 httpd
nobody 29638 29635 29635 29635 - 30 Mar20 ? 00:00:07 httpd
nobody 29639 29635 29635 29635 - 30 Mar20 ? 00:00:07 httpd 
위는 fork 를 사용하는 대표적인 서버프로그램인 httpd의 ps 결과 이다. 보면 알겠지만 29635 프로세스가 최초에 생성되고, 나머지 프로세스들이 29635 를 부모로 가지는 자식프로세스로 생성되었음을 알수 있다(PPID를 확인인하라).
그리고 이들은 동일한 그룹(PGID를 확인하라)으로 묶여 있음을 알수 있다.
이들프로세스가 바로 하나의 프로세스 그룹이 되는것이다.
모든 그룹에는 지도자가 있다(태양계의 지도자가 태양? 이듯이). 프로세스 집단도 마찬가지로 지도자(집단을 최초 생성한)가 있기 마련이다. PID와 PGID 가 같은 프로세스가 그 프로세스 집단의 지도자이며, 위에서 29635 번 프로세스가 PGID 29635 집단의 지도자 프로세스임을 알수 있다. 같은 프로세스 집단에 속하는 프로세스들은 pipe 등을 통하여서 서로간에 통신이 가능하며, signal 등을 처리함에 있어서 그룹내의 프로세스는 동일한 액션을 취한다.
예를 들어서 29635 프로세스를 죽이면, 해당 그룹에 포함되어 있는 모든 자식프로세스도 그에 대한 영향을 받는다.
[yundream@localhost yundream]$ kill -9 29635 
[yundream@localhost yundream]$ ps -efjc | grep httpd
UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD
nobody 29636 1 29635 29635 - 30 Mar20 ? 00:00:09 httpd
nobody 29637 1 29635 29635 - 30 Mar20 ? 00:00:07 httpd
nobody 29638 1 29635 29635 - 30 Mar20 ? 00:00:07 httpd
nobody 29639 1 29635 29635 - 30 Mar20 ? 00:00:07 httpd 

세션은 그룹을 포함하는 그룹이라고 생각하면 된다. 하나의 세션은 여러개의 그룹을 가질수 있으며, 보통 login 시 생성된다. 다른경우로 데몬과 같이 자기 자신이 세션을 가져야하는 경우가 있는데 그러한 경우에 세션이 생성되기도 한다.
[yundream@localhost yundream]$ ps -efjc | grep 945 | grep -v grep
 UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD
root 945 1 945 945 - 30 Mar13 tty3 00:00:00 login -- root
root 21604 945 21604 945 - 30 11:47 tty3 00:00:00 -bash
root 23964 21604 23964 945 - 30 13:27 tty3 00:00:00 ./fork
root 23965 23964 23964 945 - 30 13:27 tty3 00:00:00 ./fork 
위의 ps 화면은 root 유저로 새로 로그인 해서 ./fork 를 실행시킨 과정 을 통해서 세션이 어떻게 생성되는지를 보여주고 있다. ./fork 프로그램은 fork 를 통해서 부모자식 프로세스를 생성한것이며, 이들은 PGID-23964 의 그룹식별자를 가지는 같은 그룹으로 묶이게 된다. 또한 이 그룹은 세션아이디 945 를 가지는 세션에 묶이게 된다.
결론적으로 945 세션은 945, 21604, 23946 세개의 그룹을 가지게 된다. 세션역시 그룹과 마찬가지로 세션지도자를 가지며, 세션지도자는 그 세션을 최후에 만든 프로세스로 PID 와 SID가 같게 된다(자신의 PID번호로 SID를 만든다).
만약에 세션지도자 프로세스를 kill 시키면 어떻게 될까 ?
[root@localhost root]$ ps -efjc | grep 945 | grep -v grep 
세션 프로세스가 사라지자 그와 관련된 모든 프로세스가 kill 되었음을 알수 있다. 

'OS > LINUX' 카테고리의 다른 글

/dev/zero와 /dev/null  (0) 2011.06.04
Perl 입문  (0) 2011.05.17
쉘 스크립트 #6  (0) 2011.05.14
쉘스크립트 ex5  (0) 2011.05.14
쉘스크립트 Ex4  (0) 2011.05.14
실무에서 자주 사용되는 쉘 스크립트  #6
           (요일별 차등백업 프로그램)



  지난강좌(#5)는 서버의 트래픽을 측정해 보았다.
금번 강좌에서는 서버에서 가장 중요한 백얼을 효율적으로 할 수 있는 프로그램을 만들어 보겠다. 서버 장애/삭제등의 이유로 데이터를 잃게 된다면, 가장 힘이 되는 것은 백업이다.

다음과 같은 상황을 고려하여 프로그램을 만들어 갈 것이다.
  1. 매일 백업을 한다.
  2. 데이터는 일주일간 유지되어야 하다.
    ( 토요일에 데이터가 없어진것을 화요일에 발견해도 금요일 데이터로 복원가능해야 한다.)
  3. 데이터 양이 많아 변경(추가/삭제/수정)된 부분만 백업하려 한다.

위 조건을 만족하는 프로그램을 만들어보자~



1) 차등백업을 한다?
  차등이란 변경(추가/삭제/수정)된 파일 및 디렉토리만을 백업하는 것을 말한다.
그래도 비교 대상 데이터가 있어야 하기 때문에 최초 1회는 full 백업할 것이다.
처음 모든것을 가져오고 시간이 지난다음 그것과 비교하여 변경된 것을 가져온다. #$@#$@%
아주 복잡하다... 하지만 하나의 명령어면 우리의 고민을 해결해 줄 수 있다.


  (1) rsync명령어를 알자!
    rsync -av /home/  /backup/home/

  위 명령어를 내리면, /home/ 디렉토리의 모든 파일 및 디렉토리를 /backup/home/ 에 모두 복사해 준다. 권한, 소유자 등 모든 것을 똑같이 복사해 준다.

   한번더, 명령어를 내리면 추가된 파일이, 수정된 파일이 있으면 복사해 준다.
하지만, 원본에서 지우면 사본에서는 지워지지 않는다.


  (2) 원본에서 지워지면, 사본에서도 지워라!
    rsync -av --delete /home/  /backup/home/

  위 명령어를 내리면, 원본에서 지워졌다면 사본에서도 지워진다.
 --delete 옵션이 바로 그런 역할을 한다.


이렇게 하면, 우리가 고민하던 것을 말끔히 해결해 준다. 모든 파일을 동일하게^^



 
2) 어떻게 누적 백업 할까?
  누적 백업? 일주일간 누적? rsync명령어로는 원본과 사본을 유지 할 수 밖에 없다.
  어떤 방법이 좋을까? 여러가지 방법이 있겠지만, 가장 효율적인 방법이라 생각한 것이 요일별로 디렉토리를 만들어 차등백업 하는 것이다.

  (1) 요일별로 디렉토리를 만들자
     
     export LANG=en
     week="`date '+%A'`"

     mkdir -p /backup/$week

   일단, 언어를 영어로 한다. 디렉토리는 한글을 사용하지 않는 것이 좋다.
 위급한 상황에 리눅스콘솔에서 한글파일및 디렉토리명을 지원하지 않을 수 있기 때문이다.

   오늘은 무슨요일? date 에서 '%A'는 요일을 의미한다. Friday 이렇게 나온다^^
  그런다음, mkdir으로  디렉토리를 만들어 주면 끝^^


  (2) 요일별 차등백업
     위에서 만든 디렉토리에 rsync명령으로 백업하면..

     export LANG=en
     week="`date '+%A'`"

     mkdir -p /backup/$week
     rsync -av --delete /home/  /backup/$week/home/

   위 명령어를 내리면 백업이 될 것이다.!!


  (3) 앗! 디렉토리가 이미 있다면 에러가 난다~
     export LANG=en
     week="`date '+%A'`"

     if [ ! -d "/backup/$week/home/" ] ; then
        mkdir -p /backup/$week/home/
     fi
     rsync -av --delete /home/  /backup/$week/home/

     이렇게 하면 디렉토리가 없다면, 만들어서 백업해 줄것이다.



3) 여러개의 백업 디렉토리가 있다면?
  지금까지 예제에서는 /home/ 디렉토리를 대상으로 했다. 하지만 서버에는 수 많은 백업대상이 있다. 그럴 때 마다 위와같이 줄~줄 나열하는 것은 비효율적이다.
 이제! 함수라는 것을 배워보자~

  (1) 함수(function)
  모든 언어에는 함수가 존재한다. 일부 언어에는 프로시져와 함수를 달리 사용하기도 한다.
 하지만, bash쉘에서는 함수만 제공한다.
   함수는 어떤 일의 단위로 작성하기도 하고, 반복되는 루틴을 작성하여 효율적으로 사용하기도 한다.  함수에 대한 자세한 설명은 본 강좌에서 하지 않겠다.


  (2) 함수의 적용
   function sum {
     echo $(($1+$2))
   }

  sum 1 2


 위 예는 간단한 함수를 만들었다.  함수에 2개의 파라미터를 넣어서 더하게 하는 함수이다.
sum 1 2 이렇게 하면 결과는 3이 출력된다. bash에서 함수는 명령어의 형식과 같다.  함수명 뒤에 나오는 것이 파라미터 1번, 두번째 나오는 것이 파라미터 2번이다. 함수 내에서도 $1은 파라미터 1번, $2는 파라미터 2번이 된다. 다른 언어와 같이 파라미터 갯수를 미리 선언할 필요 없다. 명령어의 인자와 같이 넘겨서 처리 하기만 하면 된다.


  (3) 백업 프로그램에 함수 사용
 
    export LANG=en
    week="`date '+%A'`"
    backup_dir="/backup/$week"

    function sync_bak {
      from=$1
      to=$2
      if [ ! -d "$to" ] ; then mkdir -p $to ; fi
      rsync -av --delete $from $to
    }

    sync_bak /home/ $backup_dir/home/
    sync_bak /usr/local/mysql/data/ $backup_dir/mysql-data/


함수를 사용하여 우리 프로그램을 위와 같이 완성하였다.

sync_bak 함수를 만들었고, 그 파라미터는 백업대상과 백업할 곳을 넣었다.
그리고 백업 할 곳이 없으면, 만들게 했다.

위 프로그램에서는 /home/과 /usr/local/mysql/data/를 백업하고 있다.
물론 더 많다면 비슷한 형식으로 기술해 주면 된다.^^

  위 프로그램 작성후 cron에 등록해 하루에 한번씩 실행하면,
우리가 원하는 결론은 얻은 것이다.^^



위 쉘스크립트로  원하는 디렉토리를 원하는 장소에 백업 할 수 있을 것이다.
일주일간 누적보관하기 때문에 매우 좋다.^^ 단, 백업 디스크 용량이 큰것이 필요하겠다.~~~



우리는 이것으로
 가장힘이 되는 백업을 효율적으로 할 수 있는 프로그램을 만들었다. 위 스크립트를 작성하면서
date, rsync, mkdir 명령어  및  함수, 파라미터 $1, 변수, if
, export  문을 알게 되었다.
 잘 만들어진 백업 스크립트로 안전한 서버 운영을 하게 되었다. 작은행복^^

'OS > LINUX' 카테고리의 다른 글

Perl 입문  (0) 2011.05.17
프로세스 - fork & exec  (0) 2011.05.14
쉘스크립트 ex5  (0) 2011.05.14
쉘스크립트 Ex4  (0) 2011.05.14
쉘스크립트 Example2  (0) 2011.05.14
실무에서 자주 사용되는 쉘 스크립트  #5
           (트래픽 점검(측정) 유틸리티)



  지난강좌(#4)는 ping을 이용하여 여러 서버를 점검하는 스크립트를 만들어 보았다.
금번 강좌에서는 서버의 트래픽을 간단하게 확인 할 수 있는 트래픽 측정 유틸리티를 만들어 보도록 하겠다. 우리가 흔히 얘기하는 트래픽은 초당 얼마나 많은 데이터들이 소통되는지를 의미한다. 트래픽의 단위는 대부분 Bit/Sec로 표현한다. 원하는 목표를 달성해 보자^^

항상 그렇듯 차근 차근 문제를 풀어 보도록 하자 ...



1) 트래픽 정보를 어디에서 구할 수 있을까?
  트래픽을 측정하기 위해서는 어디에서 정보를 구해와야 한다.
트래픽.... 그렇다면 네트웍 장치와 관련이 있을 것이며, 그 정보를 /proc 에서 찾을 수 있었다.
물론 .. ifconfig 명령어를 입력하면 찾을 수 도 있다.


  (1) /proc의 트래픽 관련 정보 수집
    cat /proc/net/dev

    결과 :
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo:85543370 1148565    0    0    0     0          0         0 85543370 1148565    0    0    0     0       0          0
  eth0:1829471133618 19299120632    0    0    0     0          0   1498386 14446844326628 21830048151    0    0    0     0       0          0
  eth1:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  sit0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

  위 정보가 복잡해 보이지만, 많은 정보를 내포하고 있다.
네트웍 장치가 여러개 보이지만, eth0, eth1 이 주요 측정 대상이 되며, 숫자가 많이 적힌 eth0이 주 인터페이스인 것을 확인 할 수 있다.



  (2) 위 정보에서 eth1에 대한 정보만 수집하자.
    grep eth0 /proc/net/dev

    결과 :
  eth0:1829520748902 19299665416    0    0    0     0          0   1498445 14446945435248 21830514044    0    0    0     0       0          0

  원하는 정보를 추출 하였다. 각 값들은 여러가지 의미를 내포하지만, 우리에게 필요한 것은 인터페이스를 통해 전달된(송/수신)데이터 양이다.  1번째숫자(수신)와, 9번째 숫자(송신)만 필요하다.


  (3) 원하는 값만 뽑아 내자 (awk)
    grep eth0 /proc/net/dev | awk '{print $1}'

    결과 :
eth0:1829578744769

   첫번째 값을 뽑아 내었다. 이 값은 인터페이스가 수신한 데이터 양이다.
여기서 사용한 awk 명령어는 아주 아주 유용한 명령어이다. ㅤㅅㅞㅂ 스크립트를 작성하는데 꼭! 꼭! 필요한 명령이며 아주 많은 기능을 가지고 있다. 위 예제에서 사용한 것은 표준 입력값을 받아서 첫번째($1) 단어만 추출한 것이다. 물론 공백을 기준으로 단어를 나누게 된다.
  그런데! 앞에 장치명(eth0)이 붙은  것을 확인할 수 있다.. 제거해야 하는데..


  (4) "eth0:"를 제거해 보자 (sed)
    grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'

    결과 :

1829619555710

   eth0: 를 제거했다.
 그런데 sed 는 무엇이고 그 다음에 나오는 것은 무엇일까???
 sed는 스트림에디터 이다. 표준 입력으로 들어오는 내용을 편집 하는 것이다.
   "s/.*:// :"  s/표현1/표현2/ 를 의미하며 표현1을 표현2로 바꾸겠다는 것이다.
  ".*:"  "."은 아무 글자를 의미하고, "*"는 0개 이상을 의미한다.
 결론적으로 ":" 앞에 아무글자가 오는 것을 모두 지우라는 의미이다.
 그렇다면 생각해 보자.
   eth0:232312
 에서 eth0는 : 앞에 오는 0개 이상의 아무 글자이다 그래서 제거하면,
   232312 가 된다.^^
 

  (5) 뽑아낸 정보로 트래픽을 계산해 보자

    rx1=`grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
    sleep 3
    rx2=`grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
    rx3=$((rx2-rx1))
    echo "$rx3"

    결과 :
  352273
 
  rx1을 조사하고 3초간 기다린다.  기다리게 하기 위해 sleep 명령어를 사용하였다.
  3초후 rx2를 조사했다.  그런다음 rx2에서 rx1을 뺐다.
  bash 쉘에서 숫자 계산을 하기 위해서는 $((계산식)) 을 사용한다!! 계산식에 변수는 $를 붙이지 않는다.

  위 계산 결과는 3초 동안의 트래픽 변화양이다. 그리고 그 단위는 Byte 이다.


 
(6) KBit/Sec 단위로 변환해 보자
  rx3=$(( (rx2-rx1)/3*8/1024 ))

  위 계산식에서 3초간 조사했기에 3으로 나눴고,
  Byte를 Bit로 바꾸기 위해 8을 곱하고, Bit를 KBit로 바꾸기 위해서 1024로 나눴다.
  이미 계산할 수 있는 것은 계산하여 컴퓨터의 부하를 줄여보자.
   x*8/1024 = x*128   이 된다.
  그렇다면 계산식은

  rx3=$(( (rx2-rx1)/3*128 ))

 


2) 3초간 트래픽을 측정할 수 있는 프로그램 완성!!
 
  rx1=`grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx1=`grep eth0 /proc/net/dev | awk '{print $9}'`
  sleep 3
  rx2=`grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx2=`grep eth0 /proc/net/dev | awk '{print $9}'`

  rx3=$(((rx2-rx1)/128/3))
  tx3=$(((tx2-tx1)/128/3))

  echo "`date '+%k:%M:%S'` : $rx3 / $tx3"


  위 스크립트는 rx(수신), tx(송신) 용량을 조사하여 3초간 기다린 다음 계산하여 보여주게 만들었다.
  시간을 출력하기 위해 date 명령을 사용하였다.
 '
+%k:%M:%S' 는 시(24시) 분(60분) 초(60초)를 출력하기 위한 형식이다.



3) 계속 반복시키자
  위와같이 작성하면 1번 실행하고 끝난다. #1번 강좌에서 배웠던 while 문을 이용하여 반복시켜 보겠다.

echo "시간 : 수신(Kbit/Sec) / 송신(Kbit/Sec)"
while ( true ) ; do
  rx1=`grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx1=`grep eth0 /proc/net/dev | awk '{print $9}'`
  sleep 3
  rx2=`grep eth0 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx2=`grep eth0 /proc/net/dev | awk '{print $9}'`

  rx3=$(((rx2-rx1)/128/3))
  tx3=$(((tx2-tx1)/128/3))

  echo "`date '+%k:%M:%S'` : $rx3 / $tx3"
done

while 문을 사용하여 무한 반복하였다.!!
자 이렇게 하면 eth0의 트래픽을 3초간 조사하여 평균을 보여주는 프로그램을 완성하였다.!!

But!. 네트웍 장치가 eth0가 아닌 다른것이며? 3초 단위가 아닌 10초 단위로 보고 싶다면,... 그럴 때 마다 쉘 스크립트를 수정하기엔 너무 번거롭다.!!! 명령어 처럼 인자로 받아서 처리하고 싶은데......



4) 다양한 장치, 다양한 delay 시간 제공 
  프로그램이 인자를 받아서 처리 하는 것은 아주 당연한 일이다.. 쉘 스크립트와 같이 소스를 수정할 수 있으면 좋겠지만, 수정하지 못하는 컴파일된 명령어는 아주 힘든 일이다.  그럼 다음과 같이 처리해 보자!


echo "시간 : 수신(Kbit/Sec) / 송신(Kbit/Sec)"
while ( true ) ; do
  rx1=`grep $1 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx1=`grep $1 /proc/net/dev | awk '{print $9}'`
  sleep $2
  rx2=`grep $1 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx2=`grep $1 /proc/net/dev | awk '{print $9}'`

  # 1024/8 == 128
  rx3=$(((rx2-rx1)/128/$2))
  tx3=$(((tx2-tx1)/128/$2))

  echo "`date '+%k:%M:%S'` : $rx3 / $tx3"
done


a.sh 파일에 위와같이 입력하고 파일을 만든 다음.  실행권한을 주고 다음과 같이 실행한다.

./a.sh eth0 2

위와같이 입력하면, 인터페이스는 eth0를 사용하고 , delay 시간은 2초로 한 것이다.
위 쉘스크립트에서 첫번째 인자가 $1 (eth0) ,  두번째 인자가 $2 (2) 인것이다.!!



5) 다른 사람들도 쓰게 하자!
 '4)' 와 같이 작성하면, 명령어의 인자가 있다는 사실을 아는 사람은 작성한 사람 또는 쉘 스크립트를 본 사람만이 알 수 있다. 사람의 머리는 시간이 지나면 잊어 먹기 마련이다. 작성한 사람조차 뭘 넣어야 잘 작동할지 모른다.  다음과 같이 개선해 보자.


if [ "$1" == "" ] ; then
  echo "사용법 : $0 장치명 [delay]"
  echo "예) $0 eth0 3 "
  exit 1
fi
if [ "$2" == "" ] ; then delay=3 ; else delay=$2 ; fi

echo "시간 : 수신(Kbit/Sec) / 송신(Kbit/Sec)"
while ( true ) ; do
  rx1=`grep $1 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx1=`grep $1 /proc/net/dev | awk '{print $9}'`
  sleep $delay
  rx2=`grep $1 /proc/net/dev | awk '{print $1}' | sed 's/.*://'`
  tx2=`grep $1 /proc/net/dev | awk '{print $9}'`

  # 1024/8 == 128
  rx3=$(((rx2-rx1)/128/delay))
  tx3=$(((tx2-tx1)/128/delay))

  echo "`date '+%k:%M:%S'` : $rx3 / $tx3"
done


처음에 2번째 인자($2)를 조사하여 아무것도 없으면, 즉 입력이 되지 않았다면, 메시지를 뿌려준다.
 $0는 실행한 프로그램 그 자체 파일명을 의미한다. exit 1는 리턴값 1을 반환하면서 종료하게 되는 것이다.

그리고 delay 시간은 있어도 되고 없어도 되게 하며, 기본값은 3으로 하였다.
3번째 인자($3)를 조사하여 없으면 $delay변수에 3을 넣고, 있으면 그 값을 넣어준다.



위 쉘스크립트를 이용하여 사용하고 서버의 트래픽을 점검 할 수 있다.


우리는 이것으로
 아주 쉽게 트래픽을 확인 할 수 잇는 프로그램을 만들었다. 위 스크립트를 작성하면서
awk, grep, sed, sleep명령어  및 숫자 계산 $(()), 아규먼트 $1 , while, if, exit 문을 알게 되었다.
  서버에 간단한 유틸리티를 깔아 놓음으로 트래픽을 바로 확인 할 수 있게 되었다. MRTG에서 그래프를 보려면 많이 기다려야 하는데.. 답답하지 않고 좋다^^ 넘넘 뿌듯하다^^..

'OS > LINUX' 카테고리의 다른 글

프로세스 - fork & exec  (0) 2011.05.14
쉘 스크립트 #6  (0) 2011.05.14
쉘스크립트 Ex4  (0) 2011.05.14
쉘스크립트 Example2  (0) 2011.05.14
쉘스크립트 example  (0) 2011.05.14

+ Recent posts