리눅스 환경에서의 프로그램의 배포

작성: 2009-06-23 09:39:57

갱신: 2009-06-23 09:47:25

글쓴이: keedi

출력

시작하며

리눅스에서 프로그램을 배포하는 방법은 다양하게 존재합니다. 데비안 계열의 경우 deb, 레드햇 계열의 경우 rpm 등과 같은 바이너리 패키지를 이용해서 실행 파일 뿐만 아니라 관련 라이브러리, 설정 파일 등을 묶어서 제공할 수 있습니다. 하지만 배포판 종류에 따라 바이너리 패키지 파일 형식이 다르기 때문에 다양한 배포판에서도 설치 및 실행이 가능하려면 지원하려는 배포판의 종류에 맞게 바이너리 패키지를 구성해야하는 단점이 있습니다. 특정 배포판이 아닌 다양한 리눅스 환경에서도 설치 및 실행이 가능하도록 하려면 조금 더 원론적인 방법으로 배포할 수 있는 방법을 선택해야 합니다. VMware 의 인스톨러 프로그램이나 nVidia 의 드라이버 설치 프로그램, Firefox 의 인스톨러 등이 그러한 예 입니다. 이들은 압축 파일 형식으로 제공되서 사용자가 직접 압축을 풀고 실행하거나 또는 하나의 실행 파일로 제공되서 실행시키면 자동으로 설치를 진행하거나 합니다. 어찌되었든 중요한 것은 대부분의 리눅스 환경에서 설치 및 실행이 가능하며 사용자가 쉽게 설치할 수 있다는 점입니다. 문서에서는 리눅스 환경에서 프로그램을 배포할 때 생길 수 있는 문제와 그에 대한 경험적인 해결 방법을 정리합니다.

전제 조건

프로그램의 배포에 있어 전제 조건은 다음과 같습니다.

  • 일반적인 리눅스 시스템에서 설치 및 실행이 될 것
  • 루트 권한일 경우와 그렇지 않을 경우 구분해서 설치할 것

리눅스의 실행 파일은 다른 운영체제의 실행 파일 형식과 다를지는 몰라도 정적 라이브러리와 동적 라이브러리를 사용한다는 점에서는 대동소이합니다. 컴파일 시 링크 옵션에 따라 정적으로 연결되어 라이브러리가 바이너리에 포함되거나 동적으로 연결되어 라이브러리가 바이너리에 포함되지 않기도 합니다. 다양한 리눅스 환경에서 동작 가능한 실행 파일을 배포할 때 가장 문제가 되는 부분은 실행 파일 안에 포함되지 않는 동적 라이브러리입니다. 동적 라이브러리는 시스템의 라이브러리 경로에 존재하며 실행하는 시점에 찾아서 연결하기 때문에 실행하는 시스템에 해당 동적 라이브러리가 없을 경우 제대로 실행을 시킬 수 없습니다. 그러므로 실행 파일 뿐만 아니라 실행 파일이 필요로 하는 동적 라이브러리도 같이 포함해야지만 프로그램이 제대로 동작하는 것을 보장할 수 있습니다.

리눅스에서는 사용자 계정의 권한이 존재하며 시스템 전반의 모든 항목에 대해 권한을 갖는 루트 사용자와 그렇지 않은 일반 사용자로 나뉩니다. 시스템 디렉터리에 파일을 추가하거나 변경할 수 있는 루트 사용자와는 달리 일반 사용자는 보통 자신의 홈디렉터리 하부의 파일에 대해서만 갱신이 가능합니다. 이렇게 사용자 계정의 권한에 따라 시스템 디렉터리에 프로그램을 설치 가능 여부가 달라지기 때문에 루트 권한인 경우는 시스템 디렉터리에 일반 사용자의 경우 사용자가 접근할 수 있는 디렉터리에 설치할 수 있는 기능이 필요합니다.

설치

tar 볼 압축 파일

배포할 프로그램을 tar 볼 압축 파일 형태로 제공하는 것은 가장 일반적이며 널리 사용하는 방식입니다. 사용자는 압축 파일을 다운로드 받은 후 압축 해제를 한 후 해당 디렉터리 하부에서 바로 실행을 하거나 또는 install.sh 등의 쉘 스크립트를 실행해서 시스템에 설치합니다. 보통 README 또는 INSTALL 등의 텍스트 파일을 두어서 사용자의 설치 과정을 도와줍니다.

가장 일반적인 형태로 압축을 푼 후의 디렉터리 구조와 install.sh 만 잘 만들어 놓으면 되기 때문에 가장 간편하다는 것이 장점이지만 사용자가 직접 압축을 풀고 특정 쉘 스크립트를 실행해야 하기 때문에 번거로울 수 있다는 점이 단점입니다.

shar 쉘 아카이브

우분투의 경우 sharutils 라는 패키지가 있는데 이 패키지를 설치하면 쉘 아카이브를 만들어주는 shar 유틸리티를 사용할 수 있습니다. shar 를 이용해서 만든 쉘 아카이브는 앞 부분이 평범한 쉘 스크립트이기 때문에 사용자는 sh prog.shar 와 같은 식으로 실행하면 압축을 풀 수 있습니다.

단지 실행만 시키면 되기 때문에 압축 파일을 해제할 줄 모르는 사용자에게는 tar 볼 형태로 제공하는 것에 비해 설치하기 쉬워 보일 수 있을지 모르나 shar 로 묶은 파일 그 자체는 tar 볼 압축 파일과 다를 바가 없으므로 압축 해제 후 다시 install.sh 등의 쉘 스크립트를 실행해서 설치를 진행해야 합니다. 또한 tar 프로그램이 대부분의 시스템에 존재하는 것과는 달리 shar 로 묶은 쉘 아카이브를 풀려면 sharutils 패키지 안에 있는 uudecode 프로그램이 있어야 하기 때문에 자칫 사용자가 압축을 해제하지 못할 수도 있습니다. 또한 tar 볼 압축에 피해 압축 파일의 용량이 크거나 압축 해제시 시간이 상대적으로 더 걸리는 것도 단점입니다.

tar 볼 쉘 아카이브

tar 볼 압축 파일과 shar 쉘 아카이브의 장점을 묶은 방법도 있습니다. tar 볼 압축 파일의 장점은 대부분의 리눅스 시스템에서 해제를 할 수 있으며 속도 및 압축률이 뛰어나다는 것입니다. shar 쉘 아카이브는 사용자가 tar 라는 프로그램을 사용하지 않고 파일 그 자체를 실행하기 때문에 상대적으로 더 쉽게 느낄 수 있으며 그 자체가 쉘 스크립트이므로 압축 해제 전 후에 필요한 사전 또는 사후 작업을 유연하게 처리할 수 있다는 것이 장점입니다.

쉘 아카이브는 다음처럼 만들 수 있습니다:

  #!/bin/sh

  PROG_NAME="dvr-cms"
  INSTALL_DIR="/usr/lib/$PROG_NAME"
  TEMP_DIR="/tmp/$PROG_NAME"
  WORK_DIR=$PWD

  #
  # before decompressing
  #

  cp -f $0 $TEMP_DIR
  cd $TEMP_DIR

  SKIP=`awk '/^__END_OF_HEADER__/ { print NR + 1; exit 0; }' $0`
  tail +$SKIP `basename $0` | tar xz

  cd $WORK_DIR
  cp -r $TEMP_DIR $INSTALL_DIR

  #
  # after decompressing
  #

  __END_OF_HEADER__

앞의 헤더 쉘 스크립트 파일 명을 dvr-cms.template 이라고 하고 배포할 프로그램의 디렉터리 구조를 압축한 파일이 dvr-cms.tar.gz 라고하면 다음 명령을 통해서 tar 볼 쉘 아카이브를 제작할 수 있습니다:

  cat dvr-cms.template dvr-cms.tar.gz > dvr-cms.bin

이렇게 제작한 dvr-cms.bin 프로그램은 다음 명령을 이용해서 설치할 수 있습니다:

  sh dvr-cms.bin

위의 쉘 아카이브 헤더는 단순히 압축을 해제해서 복사하는 동작만 수행하지만 압축을 해제하는 코드 전후로 원하는 작업을 추가할 수 있습니다.

사용자 별 설치 위치 변경

루트 사용자나 일반 사용자에 따라 설치 위치를 달리 하려면 압축을 해제하기 전에 설치용 쉘 아카이브를 실행시킨 사용자를 확인할 필요가 있습니다. 코드는 다음과 같습니다:

  #!/bin/sh

  PROG_NAME="dvr-cms"
  INSTALL_DIR="/usr/lib/$PROG_NAME"
  TEMP_DIR="/tmp/$PROG_NAME"
  WORK_DIR=$PWD

  #
  # before decompressing
  #

  if [ "x$ID" != "xroot" ]; then
    # normal user
    if [ "x$1" != "x" ]; then
      INSTALL_DIR="$1"
    else
      INSTALL_DIR="dvr-cms"
    fi
  fi

동적 라이브러리

구성 요소 확인

프로그램을 배포할 때 주의해야할 것 중 하나는 실행 파일이 사용하는 동적 라이브러리입니다. 동적 라이브러리는 실행 파일에 포함되어 있지 않기 때문에 실행하는 시스템의 라이브러리 경로에 해당 동적 라이브러리가 반드시 존재해야지만 프로그램을 제대로 실행할 수 있습니다. 실행 파일이 필요로 하는 동적 라이브러리 목록을 알고 싶으면 binutils 유틸리티 중 하나인 ldd 명령을 이용합니다:

  $ ldd dvr-cms
    linux-gate.so.1 =>  (0xb7f4c000)
    libsndfile.so.1 => /usr/lib/libsndfile.so.1 (0xb7edc000)
    libgtk-x11-2.0.so.0 => /usr/lib/libgtk-x11-2.0.so.0 (0xb7b2b000)
    libgdk-x11-2.0.so.0 => /usr/lib/libgdk-x11-2.0.so.0 (0xb7a9d000)
    libatk-1.0.so.0 => /usr/lib/libatk-1.0.so.0 (0xb7a82000)
    libgdk_pixbuf-2.0.so.0 => /usr/lib/libgdk_pixbuf-2.0.so.0 (0xb7a68000)
    libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7a42000)
    libpangocairo-1.0.so.0 => /usr/lib/libpangocairo-1.0.so.0 (0xb7a36000)
    libcairo.so.2 => /usr/lib/libcairo.so.2 (0xb79bb000)
    libpango-1.0.so.0 => /usr/lib/libpango-1.0.so.0 (0xb7978000)
    libgobject-2.0.so.0 => /usr/lib/libgobject-2.0.so.0 (0xb793a000)
    libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0xb7882000)
    libgthread-2.0.so.0 => /usr/lib/libgthread-2.0.so.0 (0xb787c000)
    libasound.so.2 => /usr/lib/libasound.so.2 (0xb77b3000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7650000)
    libXext.so.6 => /usr/lib/libXext.so.6 (0xb7640000)
    libX11.so.6 => /usr/lib/libX11.so.6 (0xb7551000)
    libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb754d000)
    libFLAC.so.8 => /usr/lib/libFLAC.so.8 (0xb74fa000)
    libXrender.so.1 => /usr/lib/libXrender.so.1 (0xb74ef000)
    libXinerama.so.1 => /usr/lib/libXinerama.so.1 (0xb74ec000)
    libXi.so.6 => /usr/lib/libXi.so.6 (0xb74e2000)
    libXrandr.so.2 => /usr/lib/libXrandr.so.2 (0xb74da000)
    libXcursor.so.1 => /usr/lib/libXcursor.so.1 (0xb74d1000)
    libXcomposite.so.1 => /usr/lib/libXcomposite.so.1 (0xb74cc000)
    libXdamage.so.1 => /usr/lib/libXdamage.so.1 (0xb74c9000)
    libXfixes.so.3 => /usr/lib/libXfixes.so.3 (0xb74c4000)
    libgio-2.0.so.0 => /usr/lib/libgio-2.0.so.0 (0xb7456000)
    libpangoft2-1.0.so.0 => /usr/lib/libpangoft2-1.0.so.0 (0xb742d000)
    libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0xb73b6000)
    libz.so.1 => /lib/libz.so.1 (0xb739f000)
    libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0xb7372000)
    libgmodule-2.0.so.0 => /usr/lib/libgmodule-2.0.so.0 (0xb736d000)
    /lib/ld-linux.so.2 (0xb7f4d000)
    libpixman-1.so.0 => /usr/lib/libpixman-1.so.0 (0xb732a000)
    libdirectfb-1.0.so.0 => /usr/lib/libdirectfb-1.0.so.0 (0xb72c4000)
    libfusion-1.0.so.0 => /usr/lib/libfusion-1.0.so.0 (0xb72ba000)
    libdirect-1.0.so.0 => /usr/lib/libdirect-1.0.so.0 (0xb72a5000)
    libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb728c000)
    libpng12.so.0 => /usr/lib/libpng12.so.0 (0xb7266000)
    libxcb-render-util.so.0 => /usr/lib/libxcb-render-util.so.0 (0xb7261000)
    libxcb-render.so.0 => /usr/lib/libxcb-render.so.0 (0xb7258000)
    libxcb.so.1 => /usr/lib/libxcb.so.1 (0xb723e000)
    libpcre.so.3 => /lib/libpcre.so.3 (0xb720c000)
    librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb7203000)
    libXau.so.6 => /usr/lib/libXau.so.6 (0xb71ff000)
    libogg.so.0 => /usr/lib/libogg.so.0 (0xb71f8000)
    libselinux.so.1 => /lib/libselinux.so.1 (0xb71de000)
    libexpat.so.1 => /usr/lib/libexpat.so.1 (0xb71b7000)
    libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0xb71b2000)
  $

범위의 결정

ldd 실행 결과로 나오는 라이브러리 목록중 하나라도 빠지면 프로그램을 실행시킬 수 없습니다. 원칙적으로는 모든 모듈을 다 가지고 있어야하지만 대부분의 리눅스 시스템에 설치된 라이브러리도 있으므로 어떤 라이브러리를 포함할지 아니면 제외할지 결정해야 합니다.

linux-gate.so.1 의 경우 vdso (Virtual DSO) 라고하며 실제로 배포하기 위해서 공유 라이브러리를 수집할 때 따로 취할 필요가 없기 때문에 신경쓰지 않아도 됩니다. ldd 는 의존 관계까지 고려해서 보여주므로 예를들어 dvr-cms 프로그램이 gtk 라이브러리에 의존해있다면 gdk 라이브러리까지 같이 보여줍니다. 그러므로 프로그램에서 확실하게 사용하지 않는 라이브러리가 있다면 제외할 수 있습니다. dvr-cms 는 libsndfile 라이브러리에 의존하는데 libsndfile 가 의존하는 동적 라이브러리는 다음과 같습니다:

  $ ldd /usr/lib/libsndfile.so.1
    linux-gate.so.1 =>  (0xb80cc000)
    libFLAC.so.8 => /usr/lib/libFLAC.so.8 (0xb8009000)
    libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7fe3000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e7f000)
    libogg.so.0 => /usr/lib/libogg.so.0 (0xb7e79000)
    /lib/ld-linux.so.2 (0xb80cd000)
  $

그러므로 dvr-cms 프로그램 역시 libFLAC 과 libogg 라이브러리에 의존합니다. 하지만 dvr-cms 프로그램은 내부적으로 libsndfile 의 기능 중 FLAC 과 ogg 관련한 기능을 전혀 사용하지 않으므로 두 라이브러리는 배포할 때 없어도 됩니다.

또한 대부분의 리눅스 데스크탑은 X 윈도우즈를 기본으로 탑재하고 있기 때문에 X 관련 라이브러리까지 배포시 포함할 필요가 없습니다. 심지어 그놈 데스크탑 환경일 경우 gtk 와 glib 라이브러리 역시 기본으로 포함하므로 역시 필요없습니다. 하지만 dvr-cms 의 경우 최신 버전의 gtk 를 사용하기 때문에 오래된 리눅스 배포판에서 사용하는 낮은 버전의 gtk 로는 부족하므로 gtk 와 glib 관련 라이브러리를 포함할 것입니다. 이렇게 배포할 프로그램과 배포할 시스템의 환경을 고려해서 포함할 라이브러리의 종류를 적절하게 결정해야 합니다.

컴파일 중 링크 옵션을 보면 dvr-cms 프로그램의 Makefile 을 보면 다음과 같은 옵션을 사용하고 있습니다:

PROJECT_LDFLAGS := \
  $(LDFLAGS) \
  -Wl,-Bstatic \
  $(EGTK_LDFLAGS) \
  $(EDC_LDFLAGS) \
  $(STW_LDFLAGS) \
  $(PANASONIC_LDFLAGS) \
  $(ICANTEK_LDFLAGS) \
  $(ED_LDFLAGS) \
  $(EDNET_LDFLAGS) \
  $(FFMPEG_LDFLAGS) \
  $(VNC_LDFLAGS) \
  $(SQLITE_LDFLAGS) \
  $(UNIXODBC_LDFLAGS) \
  $(SEXY_LDFLAGS) \
  $(XV_LDFLAGS) \
  $(GOOCANVAS_LDFLAGS) \
  -Wl,-Bdynamic \
  $(SNDFILE_LDFLAGS) \
  $(GTK_LDFLAGS) \
  $(ALSA_LDFLAGS) \

-Wl,-Bstatic 은 정적 링크를 의미하며 -Wl,-Bdynamic 은 동적 링크를 의미합니다. 즉 크게 libsndfile 과 gtk, alsa 라이브러리를 동적으로 링크한다는 뜻입니다. 앞서 나온 ldd 의 결과 목록이 많은 이유는 이들 세 라이브러리가 의존하는 라이브러리까지 모두 포함해서 출력하기 때문입니다.

libc 버전 차이

최근의 리눅스 배포판은 대부분 문제가 없지만 간혹 libc 버전이 달라서 생기는 문제가 있습니다. 페도라 11, 우분투 9.04, 수세 11 모두 libc 2.9 버전을 사용하지만 현재 안정 버전 데비안 5.0 의 경우 libc 2.7 버전을 사용합니다. 데비안 5.0 의 gtk 와 glib 라이브러리 버전도 낮기 때문에 배포시 최신의 gtk 와 glib 을 함께 제공해야 합니다. 문제는 libc 2.9 버전 환경에서 glib 을 컴파일 했을 경우 실행 시점에 glib 이 libc 2.8 미만의 환경에서는 실행할 수 없다는 다음과 같은 메시지가 발생합니다.

  ./dvr-cms: /lib/i686/cmov/libc.so.6: version `GLIBC_2.8` not found (required by lib/libglib-2.0.so.0)

readelf 명령어로 glib 의 심볼을 확인해보면 GLIBC 2.8 의 심볼을 요구하기 때문에 발생하는 문제입니다:

  $ readelf -s /usr/lib/libglib-2.0.so.0 | grep GLIBC_2.8
    157: 00000000     0 FUNC    GLOBAL DEFAULT  UND __vasprintf_chk@GLIBC_2.8 (10)
  $

근원적으로는 glib 에서 glibc 2.8 심볼을 요구하지 않도록 해야하지만 이 문제가 발생하는 원인과 이것을 해결하는 정확한 방법은 찾지 못한 상태입니다.

이런 경우 프로그램을 실행시키려면 glib 이 요구하는 glibc 버전을 맞추는 방법으로 해결할 수 있는데 이를 위해서는 libc 역시 배포시 포함하는 것입니다. libc 의 경우 binutils 등의 시스템 유틸리티와도 연관이 많기 때문에 일반적인 동적 라이브러리를 단순히 옮겨오는 것 보다는 번거롭습니다.

또한 ldd 명령으로 보면 나오는 ld-linux.so.2 항목의 경우 다음 처럼 표시가 됩니다:

  $ ldd dvr-cms
    linux-gate.so.1 =>  (0xb7f4c000)
    ...
    /lib/ld-linux.so.2 (0xb7f4d000)
    ...

ld-linux.so.2 가 libc 가 제공하는 여러 파일 중 대표적인 한 파일 인데 이 항목이 절대 경로로 지정이 되어 있기 때문에 아무리 ld-linux.so.2 파일을 가지고 다니더라도 시스템 /lib 하부에 설치하지 않는 이상 자체 libc 를 사용할 수 없습니다.

glibc 소스 디렉터리 내부를 보면 FAQ 문서가 있는데 2.6 과 3.18 항목을 보면 libc 버전이 다른 환경에서 실행시키는 방법에 대해서 나와있습니다.

  2.6. When I use GNU libc on my Linux system by linking against
    the libc.so which comes with glibc all I get is a core dump.

  3.18. After upgrading to glibc 2.1, I receive errors about
    unresolved symbols, like '_dl_initial_searchlist' and can not
    execute any binaries.  What went wrong?

결론은 --dynamic-linker=/lib/ld-linux.so.2 옵션을 사용하는 것인데 gcc 에서 링커 옵션으로 넘겨줄 때는 다음 처럼 -Wl,--dynamic-linker=/lib/ld-linux.so.2 원하는 경로를 넣으면 됩니다. 절대 경로 상대 경로 모두 사용 가능합니다.

또는 다른 방법으로 컴파일 시 --dynamic-linker 옵션을 주지 않았을지라도 프로그램을 실행할 때 다음 처럼 입력함으로써 자체 libc 를 사용하는 것이 가능합니다:

    LD_LIBRARY_PATH=<path-where-libc.so.6-lives> \
    <path-where-corresponding-dynamic-linker-lives>/ld-linux.so.2 \
    <path-to-binary>/binary

역시 경로는 절대 경로 상대 경로 모두 사용 가능합니다.

덤으로 LD_LIBRARY_PATH-rpath 옵션에 상응하며 gcc 에 링크 옵션으로 넘겨줄 때는 -Wl,-rpath,lib,usr/lib 로 사용합니다. 역시 절대 경로 및 상대 경로 모두 사용 가능하며 여러 개의 디렉터리를 명시할 때는 쉼표로 구분합니다.

필요한 libc 의 위치는 위와 같은 방식으로 지정할 수 있으며 필요한 libc 라이브러리는 패키지 관리자를 이용해서 확인할 수 있습니다. 우분투 9.04 에서 libc 라이브러리는 libc6 라는 패키지로 존재합니다. libc 패키지의 구성 파일을 확인해서 /lib 하부에 설치되는 파일 중에서 심볼릭 링크인 파일만 추려내면 그 목록은 다음과 같습니다:

  $ dpkg-query -L libc6 | perl -nle 'm|^/lib| and -l and print'
  /lib/ld-linux.so.2
  /lib/libanl.so.1
  /lib/libBrokenLocale.so.1
  /lib/libc.so.6
  /lib/libcidn.so.1
  /lib/libcrypt.so.1
  /lib/libdl.so.2
  /lib/libm.so.6
  /lib/libnsl.so.1
  /lib/libnss_compat.so.2
  /lib/libnss_dns.so.2
  /lib/libnss_files.so.2
  /lib/libnss_hesiod.so.2
  /lib/libnss_nis.so.2
  /lib/libnss_nisplus.so.2
  /lib/libpthread.so.0
  /lib/libresolv.so.2
  /lib/librt.so.1
  /lib/libthread_db.so.1
  /lib/libutil.so.1
  $

상기 목록의 공유 라이브러리가 libc 를 구성하는 최소한 파일입니다. 여타 다른 공유 라이브러리와 함께 libc 라이브러리도 같이 묶어서 배포하면 libc 버전이 다른 환경에서도 프로그램을 정상적으로 수행시킬 수 있습니다.

선별적 적재

시스템에 따라 설치된 공유 라이브러리의 버전이 충분히 높아서 시스템의 공유 라이브러리를 사용할 수 있는 경우에는 굳이 따로 지정한 라이브러리를 사용할 필요가 없습니다. 조건이 충족만 된다면 시스템에서 사용하는 라이브러리를 사용하는 편이 안정성이나 메모리 효율 측면에서 더 나을 수 있습니다. 그러므로 프로그램을 실행할 때 라이브러리를 종류별로 나누어서 꼭 필요한 라이브러리만 적재하는 것이 바람직할 것입니다.

예제의 dvr-cms 의 경우 크게 4 가지로 나누어서 적재를 할 수 있습니다.

  • libc
  • gtk
  • asound
  • sndfile

즉 libc(glibc) 라이브러리와 gtk(gtk, glib, gdk, ...) 라이브러리 ALSA 라이브러리와 sndfile 라이브러리로 나누어서 가능한 시스템의 라이브러리를 사용하도록 하는 것입니다.

이를 용이하게 하기 위해서 디렉터리 구조를 다음처럼 나누도록 합니다.

  lib/
  lib/libc/
  lib/libgtk/
  lib/libasound/
  lib/libsndfile/

이렇게 디렉터리를 크게 4 개로 구분한 다음 각 라이브러리 디렉터리에 구분해서 라이브러리를 복사합니다. 프로그램을 실행할 때는 실행 파일을 직접 실행시키는 것이 아니라 dvr-cms.sh 와 같은 래퍼 스크립트를 만들어서 libc 버전을 확인한 후 적재 여부를 판단하고 다음으로 gtk 버전과 glib 버전을 확인합니다. 차례로 alsa 와 sndfile 의 버전 또는 존재 여부를 확인후 적재 여부를 판단합니다. 적재 여부를 판단한 후 적재를 할 디렉터리는 LD_LIBRARY_PATH 환경 변수에 추가하면 간단하게 선별적으로 적재하는 것이 가능합니다.

libc 를 적재한다면 LD_LIBRARY_PATH 에 추가하는 것 뿐만 아니라 실행시 다음처럼 ld-linux.so.2 경로도 입력해주어야 합니다:

  LD_LIBRARY_PATH=lib lib/ld-linux.so.2 ./dvr-cms

[Digest] 펄 스크립트로 상호 작용하는 Ack 쉘 만들기

작성: 2009-02-18 02:31:57

갱신: 2009-02-18 02:34:39

글쓴이: keedi

출력

CPAN의 Andy Lester가 제작한 App::Ack 모듈은 펄로 만든 강력한 문자열 검색 도구 입니다. 한 번 사용하면 즐겨 사용하던 GNU grep을 버려버릴 수 있을 정도로 grep을 사용하면서 한 번쯤은 느낄 아쉬운 부분들을 보완하는 다양한 기능과 옵션을 가지고 있습니다. App::Ack 모듈을 설치하면 명령줄 실행 프로그램인 ack를 같이 설치하므로 특별히 App::Ack 모듈을 이용한 프로그램을 작성할 것이 아니라면 기본으로 제공하는 ack 프로그램만으로도 사용하는데는 지장이 없습니다.

ack는 기본으로 버전 관리 프로그램에서 사용하는 내부 디렉터리를 검색 대상에서 제외하기 때문에 프로그램 작성 중 CVS 또는 서브버전 저장소 안에서 검색시 grep에 비해 아주 쾌적하게 사용할 수 있습니다. 또한 펄 프로그래머라면 당연히 익숙할 펄의 정규표현식 을 동일하게 지원하기 때문에 별도로 정규표현식 문법을 익힐 필요가 없다는 것도 큰 장점입니다. ack의 자세한 기능과 다양한 옵션에 대해서는 ack --help를 참조하세요.

프로그램을 작성할 때 기존 코드를 검색해서 구조를 파악하거나 호출 상관 관계를 살펴볼 때 grep과 같은 문자열 검색 프로그램은 매우 유용합니다. ack를 사용하면 효율을 더욱 높일 수 있는데 한 가지 아쉬운 점은 매번 검색시 마다 ack와 원하는 옵션을 입력해야 한다는 것입니다. 이것은 매번 사용자의 검색 질의어 입력을 받아서 ack를 호출하는 간단한 펄 스크립트를 작성하면 쉽게 해결할 수 있습니다:

  #!/usr/bin/perl

  use strict;
  use warnings;

  input();

  while (<>) {
      chomp;
      last if /^gg$/;
      next if !$_;

      print <<"END_MSG";

  ------------------------------------------------------------------------
  Search Keyword: [$_]
  ------------------------------------------------------------------------
  END_MSG

      if ( m{^/(.*)/$} ) {
          system 'ack', '--pager=more', '--match', $1, '.';
      }
      elsif ( m{^/(.*)/i$} ) {
          system 'ack', '--pager=more', '--ignore-case', '--match', $1, '.';
      }
      else {
          system 'ack', '--pager=more', '--literal', $_, '.';
      }

  } continue {
      input();
  }

  sub input {
      print <<"END_INPUT";

  * This is a Simple Ack shell.
  * /PATTERN/  : PATTERN will be interpreted as Perl's regular expression.
  * /PATTERN/i : PATTERN will be interpreted as Perl's regular expression
                 with ignore case option
  * PATTERN    : PATTERN will be interpreted as literal characters.
  * gg         : quit the program.

  END_INPUT

      print "Search Keyword: ";
  }

위의 펄 스크립트는 매우 간단한 구조를 가지고 있습니다. 사용자의 입력을 받아들인 후 사용자의 입력을 이용해서 정규표현식 검색을 할 것인지 또는 대소문자 구분없는 정규표현식 검색을 할 것인지, 단순 문자열 검색을 할 것인지, 프로그램을 종료할 것인지를 판단합니다. 그 후 실제 검색은 ack 프로그램을 외부 호출을 통해 수행하고 결과 역시 ack가 만들어내는 화면을 그대로 표준 출력에 보여줍니다.

규모가 있는 프로그램을 작성할 때 ctagscgvg 그리고 간단하게 ack와 상호작용하는 펄 스크립트를 이용해서 코드를 검색하는데 작업 능률면에서 매우 만족하고 있습니다. :)

Module::Pluggable 플러그인 프레임워크를 이용한 확장성 있는 프로그램 작성

작성: 2009-02-17 10:53:07

갱신: 2009-02-17 22:47:37

글쓴이: keedi

출력

시작하며

프로그램이 확장성이 좋다는 것은 변화에 유연하게 대처할 수 있으며 기능을 추가하는데 비용이 적게 든다는 것을 의미합니다. 확장성 있는 프로그램을 만드는 여러가지 방법이 있지만 플러그인 구조는 그 중에서도 널리 쓰이고 있는 기법 중 하나입니다. 보통 전산 분야에서 플러그인(plug-in)은 기능 확장용 소프트웨어를 지칭합니다. 플러그인 구조를 지원하는 소프트웨어는 글자 그대로 플러그에 꽂듯이 새로운 기능의 모듈을 제작한 후 원래의 소프트웨어에 꽂으면 별다른 수고없이 해당 기능을 사용할 수 있는 것이 특징입니다. 펄의 Module::Pluggable 모듈을 이용하면 플러그인 구조를 손쉽게 지원할 수 있기 때문에 플러그인 구조를 만들기 위해 수고를 들일 필요없이 최소한의 코드로도 가독성 있고 확장성 있는 프로그램을 작성할 수 있습니다.

관련 연구

Module::Pluggable은 플러그인 형식의 서브 모듈을 지원하는 모듈을 만들 수 있게 도와주는 간단한 프레임워크입니다. 원래는 기본 내장 모듈이 아니었으나 5.9.5 개발 버전부터 기본 내장 모듈로 포함되어서 근래에 배포되고 있는 5.10 안정 버전에서는 추가 설치없이 사용 가능합니다. 만약 자신이 사용하고 있는 펄의 버전이 5.8 이하라면 CPAN을 통해 설치해야 합니다.

사용법은 간단합니다. 플러그인 기능을 지원하려는 모듈에 다음의 예제처럼 Module::Pluggable을 적재하는 코드를 넣어주면 됩니다:

  package MyClass;

  use strict;
  use warnings;
  use Module::Pluggable require => 1;

  sub new {
      my ( $class, @args ) = @_;

      # ...
  }

너무 간단하지만 이것으로 모든 준비는 끝났습니다. MyClass 모듈은 이제 플러그인 모듈들을 마음껏 사용할 수 있습니다. MyClass 모듈이 자신의 모듈 하부에 적재되는 플러그인 모듈을 사용하는 방법은 다음과 같습니다:

  use strict;
  use warnings;
  use MyClass;

  my $mc = MyClass->new;
  my @plugins = $mc->plugins; 

MyClass 모듈에 Module::Pluggable을 적재하면 Module::PluggableMyClass 이름 공간에 plugins 함수를 자동으로 끼워넣습니다. 일종의 이름 공간 침해라고 생각할 수도 있지만 펄에서는 매우 유용하게 사용할 수 있는 기교 중 하나입니다. plugins 함수는 MyClass::Plugin::* 하부의 모든 모듈의 이름 목록을 반환합니다.

MyClass::Plugin::Add 모듈의 예제는 다음과 같습니다:

  package MyClass::Plugin::Add;

  use strict;
  use warnings;

  sub run {
      my ( $self, $num1, $num2 ) = @_;

      return $num1 + $num2;
  }

  1;

MyClass::Plugin::Add 플러그인은 run 함수를 가지고 있으며 MyClass 모듈 사용자는 다음처럼 이 플러그인을 사용할 수 있습니다:

  use strict;
  use warnings;
  use MyClass;

  my $mc = MyClass->new;

  my $result;
  foreach my $plugin ($self->plugins) {
      next $plugin eq 'MyClass::Plugin::Add';
      last $plugin->can('run');
      $result = $plugin->run(4, 5);
      last;
  }

실제 플러그인 모듈을 어떤 식으로 구성해야 하는지에 대해서는 어떠한 제약 사항도 없습니다. 모든 것은 MyClass 모듈을 설계하는 프로그래머에게 달려있습니다. 또한 Module::Pluggable은 다양한 옵션을 지원합니다. 옵션을 이용해서 plugins 대신 자동으로 추가할 함수의 이름을 바꿀 수 있으며 플러그인 하부 모듈의 위치도 MyClass::Plugin:: 하부가 아닌 다른 위치로 변경할 수 있습니다. 그 외 다른 유용한 옵션들이 많으므로 자세한 것은 문서를 참조하세요.

구현

구성

좀 더 실질적인 예제인 Wiki 모듈과 Wiki::Plugin::* 모듈을 작성해보겠습니다. 위키를 만들다보면 해당 페이지의 체크썸을 확인해야할 일이 있습니다. 이 때 MD5 해시라던가 SHA1 등 다양한 체크썸을 기호에 맞게 사용할 수 있으면 유용할 것입니다. 이런 체크썸 기능을 플러그인 방식으로 사용할 수 있게 구성합니다.

또한 위키의 각 페이지는 보통 위키 문법을 이용해서 작성한 후 HTML로 변환합니다. 취향에 따라 위키의 페이지를 위키 문법이 아닌 마크다운(markdown)이나 펄의 POD 문법을 사용하고 싶은 경우도 있습니다. 이 뿐만 아니라 자신만의 즐겨쓰는 문법을 정의해서 사용할 수 있으면 유용할 것입니다. 플러그인 구조를 이용해서 위키가 다양한 문법을 지원할 수 있게 구성합니다.

Wiki 모듈의 플러그인 구조는 크게 Wiki::Plugin::Digest::* 모듈과 Wiki::Plugin::Renderer::HTML::* 모듈로 구성합니다:

  Wiki.pm
  Wiki/
    Plugin/
      Digest/
        HelloWorld.pm
        MD5.pm
        SHA1.pm
      Renderer/
        HTML/
          GoogleWiki.pm
          Markdown.pm
          MediaWiki.pm
          PlainText.pm
          Pod.pm
          WikiText.pm

최상위 모듈

다음은 각 모듈들의 코드입니다. 먼저 최상위 모듈인 Wiki 모듈입니다:

  package Wiki;

  use strict;
  use warnings;
  use Module::Pluggable require => 1;

  use base qw(Class::Accessor::Fast);
  Wiki->mk_ro_accessors(qw(digest renderer));

  sub new {
      my $class  = shift;
      my %params = (
          @_,
      );

      my $self = bless \%params, $class;
      $self->init;

      return $self;
  }

  sub init {
      my $self = shift;

      for my $plugin ( $self->plugins ) {
          if ($plugin =~ m/^Wiki::Plugin::Renderer::HTML::(.*)$/) {
              $self->{renderer}->{lc $1} = $plugin;
          }
          elsif ($plugin =~ m/^Wiki::Plugin::Digest::(.*)$/) {
              $self->{digest}->{lc $1} = $plugin;
          }
      }
  }

  sub handle_renderer {
      my ( $self, $src ) = @_;

      my ( $format, $content ) = split "\n", $src, 2;
      if ( $format =~ m/^#!(.*)/ ) {
          $format = lc $1;
      }
      else {
          $format = 'plaintext';
      }

      my $plugin = $self->renderer->{$format};

      my $dest;
      if ( $plugin && $plugin->can('run') ) {
          $dest = $plugin->run($src);
      }
      else {
          $dest = "<pre>Error: Cannot find [$format] renderer plugin.</pre>";
      }

      return $dest;
  }

  sub handle_digest {
      my ( $self, $type, $src ) = @_;

      my $plugin = $self->digest->{lc $type};
      return unless $plugin->can('run');

      my $dest = $plugin->run($src);

      return $dest;
  }

  1;

체크썸 플러그인

Wiki::Plugin::Digest::HelloWorld 플러그인입니다:

  package Wiki::Plugin::Digest::HelloWorld;

  use strict;
  use warnings;

  sub run {
      my $self = shift;
      my $msg  = shift || q{};

      return "Hello $msg!";
  }

  1;

Wiki::Plugin::Digest::MD5 플러그인입니다:

  package Wiki::Plugin::Digest::MD5;

  use strict;
  use warnings;
  use Digest;

  my $d = Digest->new("MD5");

  sub run {
      my $self = shift;
      my $msg  = shift || q{};

      $d->reset;
      $d->add( $msg );

      return $d->hexdigest;
  }

  1;

Wiki::Plugin::Digest::SHA1 플러그인입니다:

  package Wiki::Plugin::Digest::SHA1;

  use strict;
  use warnings;
  use Digest;

  my $d = Digest->new("SHA-1");

  sub run {
      my $self = shift;
      my $msg  = shift || q{};

      $d->reset;
      $d->add( $msg );

      return $d->hexdigest;
  }

  1;

HTML 변환 플러그인

Wiki::Plugin::Renderer::HTML::GoogleWiki 플러그인입니다:

  package Wiki::Plugin::Renderer::HTML::GoogleWiki;

  use strict;
  use warnings;
  use Text::GooglewikiFormat;

  sub run {
      my ( $self, $src ) = @_;

      my $dest = Text::GooglewikiFormat::format($src);

      return $dest;
  }

  1;

Wiki::Plugin::Renderer::HTML::Markdown 플러그인입니다:

  package Wiki::Plugin::Renderer::HTML::Markdown;

  use strict;
  use warnings;
  use Text::MultiMarkdown;

  my $parser = Text::MultiMarkdown->new(
      empty_element_suffix => ' />',
      tab_width            => 2,
      use_wikilinks        => 1,
      img_ids              => 1,
      heading_ids          => 1,
  );

  sub run {
      my ( $self, $src ) = @_;

      my $dest = $parser->markdown( $src );

      return $dest;
  }

  1;

Wiki::Plugin::Renderer::HTML::MediaWiki 플러그인입니다:

  package Wiki::Plugin::Renderer::HTML::MediaWiki;

  use strict;
  use warnings;
  use Text::MediawikiFormat qw(wikiformat);

  sub run {
      my ( $self, $src ) = @_;

      my $dest = wikiformat(
          $src,
          {},
          { implicit_links => 1, },
      );

      return $dest;
  }

  1;

Wiki::Plugin::Renderer::HTML::PlainText 플러그인입니다:

  package Wiki::Plugin::Renderer::HTML::PlainText;

  use strict;
  use warnings;

  sub run {
      my ( $self, $src ) = @_;

      my $dest = <<"END_HTML";
  <pre>
  $src
  </pre>
  END_HTML

      return $dest;
  }

  1;

Wiki::Plugin::Renderer::HTML::Pod 플러그인입니다:

  package Wiki::Plugin::Renderer::HTML::Pod;

  use strict;
  use warnings;
  use Pod::Simple::HTML;
  use HTML::Entities;

  my $parser = Pod::Simple::HTML->new;
  $parser->index(1);
  $parser->html_header_before_title(0);
  $parser->html_header_after_title(0);
  $parser->html_footer(0);

  sub run {
      my ( $self, $src ) = @_;

      my $dest;
      $parser->output_string( \$dest );
      $parser->parse_string_document( $src );

      # http://chalow.net/2008-05-10-3.html
      $dest = decode_entities($dest);

      return $dest;
  }

  1;

Wiki::Plugin::Renderer::HTML::WikiText 플러그인입니다:

  package Wiki::Plugin::Renderer::HTML::WikiText;

  use strict;
  use warnings;
  use Text::WikiText;
  use Text::WikiText::Output::HTML;

  my $parser      = Text::WikiText->new;
  my $output      = Text::WikiText::Output::HTML->new;
  my %parser_opts = (
      full_page      => 0,
      heading_offset => 0,
  );

  sub run {
      my ( $self, $src ) = @_;

      my $document = $parser->parse($src);
      my $dest = $output->dump($document, \%parser_opts);

      return $dest;
  }

  1;

사용

Wiki 모듈은 두 가지 객체 함수인 메소드를 지원합니다:

  use strict;
  use warnings;
  use Wiki;

  my $wiki = Wiki->new;
  my $digest = $wiki->handle_digest  ( 'md5', $source );
  my $html   = $wiki->handle_renderer( $source        );

체크썸 플러그인과 HTML 변환 플러그인을 다루기 위해 handle_* 함수를 만든 것에 주목하시기 바랍니다. 체크썸 플러그인의 경우 호출하는 시점에 사용할 플러그인을 명시하며 변환 플러그인의 경우 인자의 값을 이용해서 적절한 플러그인을 동적으로 사용하고 있습니다. 물론 어떻게 사용할지는 전적으로 프로그래머에게 달려있습니다.

정리하며

펄은 매우 유연하며 강력한 기능을 많이 가지고 있습니다. 자유로운 이름 공간의 제약을 적극적으로 활용한 Module::Pluggable의 기교도 그러한 예 중의 하나입니다. 플러그인 구조를 지원하는 프로그램을 제작하는 것이 어렵지는 않다하더라도 매번 비슷한 형태의 목업(mock-up) 코드를 작성하는 것은 성가신 일입니다. Module::Pluggable을 이용하면 최소한의 비용으로 확장성있는 플러그인 구조의 장점을 적극 활용할 수 있습니다. 더불어 여유로울때 Module::Pluggable 모듈의 소스 코드를 참고하면 펄에 대해서 더 자세히 알 수 있는 좋은 기회가 될 것입니다.

설치하지 않은 의존 모듈 적재시 발생하는 오류 회피하기

꼬리표: Emstone Perl

작성: 2009-01-28 18:22:29

갱신: 2009-01-29 02:49:18

글쓴이: keedi

출력

시작하며

회사에서 업무용으로 작성하는 펄 스크립트의 경우 개인적으로 작성하는 스크립트에 비해 개인의 취향을 배제하는 것은 물론 비교적 엄격하게 만드는 편입니다. 이것은 어찌보면 조금 괴로운 일인데, 예를 들면 즐겨쓰는 단축 표현은 최대한 자제하는 편이며, 조금 과하다 싶을 정도로 주석을 달고 있습니다. 또한 의존 모듈을 최대한 줄이기 위해 기본 펄 배포판에 들어가있지 않은 CPAN 모듈은 최대한 쓰지않고, 가능한 하나의 파일로 작성해서 배포의 용이성을 살리는 것에 중점을 두고 있습니다.

하지만 CPAN 모듈을 전혀 사용하지 않는 것은 어려운 상황이 많습니다. 이런 경우 어쩔수 없이 CPAN 모듈을 사용하는데 문제는 펄을 즐겨 사용하지 않는 팀원들의 경우 해당 스크립트를 실행할 때 의존 모듈 적재 오류가 발생해 실행이 불가능한 경우가 종종 생긴다는 것입니다. 익숙한 사람이라면 오류 메시지를 확인해서 해당 펄 모듈을 cpan 쉘을 이용해 설치하겠지만 그렇지 않은 경우 모듈 적재 실패 메시지를 보고 모듈 설치를 해야겠다고 생각하는 일은 어려울 수 있습니다. 또한 cpan 쉘을 이용해서 모듈을 설치하는 방법을 모를 수도 있습니다.

이런 상황을 고려해서 CPAN 모듈을 의존하는 스크립트를 작성할 때 해당 모듈을 설치하지 않아 발생하는 오류를 회피하는 방법을 소개합니다. 이 방법을 이용하면 사용자는 펄이 뿌려주는 오류 메시지보다 훨씬 더 자세하고 친절한 메시지를 보고 문제를 쉽게 해결할 수 있을 것입니다.

구현

다음은 코드 예제입니다:

  #!/usr/bin/perl

  #
  # EMSTONE DVR Camera Library - Test Program (Download)
  #
  # Copyright (c) 2009 EMSTONE, All rights reserved.
  #

  BEGIN {
      eval q{
          use DateTime;
          use Net::SSH::Expect;
          use Net::SCP::Expect;
      };

      die <<"END_README" if $@;
  Error:
  $@

  #
  # README:
  #
  # To run this script, install following modules first:
  #
  #   * DateTime
  #   * Net::SSH::Expect
  #   * Net::SCP::Expect
  #
  # You can install requiremnt modules with following commands:
  #
  #   $ sudo cpan DateTime
  #   $ sudo cpan Net::SSH::Expect
  #   $ sudo cpan Net::SCP::Expect
  #
  #     or
  #
  #   $ sudo cpan DateTime Net::SSH::Expect Net::SCP::Expect
  #
  END_README
  }

  use strict;
  use warnings;
  use DateTime;
  use Getopt::Std;
  use File::Basename;
  use Net::SSH::Expect;
  use Net::SCP::Expect;

  #
  # ... rest of the code ...
  #

분석

상기 코드에서 사용한 기법은 크게 네 가지 입니다.

  • BEGIN 블록
  • eval 함수
  • here-document 문자열
  • 추가적인 use 사용

BEGIN 블록

BEGINUNITCHECKCHECK, INIT, END 와 함께 펄 프로그램의 실행 전 또는 실행 후에 동작하는 이름있는 블록(named block)입니다. BEGIN 코드 블록은 프로그램 안에서 여러번 사용할 수 있으며 프로그램의 첫 머리부터 발견하는 즉시 블록 안의 코드를 실행합니다. 프로그램 시작 전에 전처리의 개념으로써 미리 처리할 부분이 있다면 BEGIN 블록을 사용하면 쉽게 구현할 수 있습니다.

자세한 내용은 perlmod 문서를 참고하세요.

eval 함수

eval은 현재 실행중인 펄 프로그램과는 별개의 실행 문맥을 가지도록 하면서 코드를 수행하는 와중에 eval 내의 코드를 실행하기 위해 사용합니다. eval을 사용하면 프로그램을 종료시키는 오류가 발생하더라도 현재 실행중인 프로그램을 종료시키지 않으며 별도의 문맥으로 실행하는 코드를 종료시키고 에러 메시지를 $@ 변수에 저장합니다. 따라서 예외 처리라던가 프로그램 내의 프로그램 실행 등의 동작을 수행할 때 매우 유용하게 사용할 수 있습니다.

eval 함수의 원형은 다음과 같습니다:

  eval EXPR
  eval BLOCK

두 가지 호출 방식은 기능 면에서는 비슷하나 펄이 파싱을 언제, 어떻게 하느냐? 에 따라 미묘한 차이가 있습니다. 효율적인 측면에서는 BLOCK 구문을 사용하는 편이, 컴파일 시간의 오류를 피한다는 측면에서는 EXPR 구문을 사용하는 편이 유리합니다.

자세한 내용은 perlfunc의 eval 함수 설명 문서를 참고하세요.

here-document 문자열

here-document 기법은 많은 양의 문자열을 한 번에 처리할 때 유용한 기법입니다. 대부분의 쉘이 가지고 있는 기능인데 쉘의 특징을 계승한 펄 역시 가지고 있으며, 쉘에 비해 다양한 보간(interpolation) 방식을 지원하며, 후처리 역시 가능하기 때문에 기능면에서는 훨씬 뛰어납니다. here-document 를 사용하지 않을 경우, 인용부호를 남발해야하는 경우가 있는데, 이럴 때 적절하게 사용하면 코드의 가독성을 높일 수 있습니다. 하지만 here-document를 코드 중간에 너무 남발할 경우 오히려 들여쓰기가 시각적으로 망가져서 가독성을 오히려 떨어뜨릴 수 있으니 주의해야 합니다.

자세한 내용은 perlopperldata 문서를 참고하세요.

추가적인 use 사용

대부분의 경우 모듈을 적재하기 위해 use 함수를 사용합니다. use로 모듈을 적재하면 현재 패키지의 이름 공간에서 해당 모듈의 함수나 변수를 사용할 수 있게 도와주며, 특별히 이름 공간에 넣는 함수나 변수가 없다 할지라도 모듈을 사용하기 위해서는 반드시 userequire 등을 이용해서 모듈을 적재해야 합니다. require 보다 use를 사용하는 것이 조금 더 편리한데 만약 자신이 requireuse 의 차이를 정확하게 모른다면 use를 선택하는 것이 대부분의 경우 올바른 선택입니다.

use를 사용하는 것은 다음 코드와 동일합니다:

  BEGIN { require Module; Module->import( LIST ); }

앞의 예제에서는 비록 BEGINeval의 조합에서 use를 이용해 모듈을 적재하기 때문에 아래 부분에서 다시 use를 사용할 필요가 없지만 가독성이라는 측면과, 모듈의 의존성 점검시 정확한 결과를 보여주기 위해 한번 더 use를 사용합니다. 이 때 모듈 적재를 중복해서 시도했다고 할지라도 적재한 모듈의 경우 다시 적재하지 않으므로 성능상의 저하는 없습니다.

use 함수의 원형은 다음과 같습니다:

  use Module VERSION LIST
  use Module VERSION
  use Module LIST
  use Module
  use VERSION

자세한 내용은 perlfunc의 use 함수 설명 문서를 참고하세요.

정리하며

펄 프로그램의 실행을 시작하기 전 의존 모듈을 적재 가능 여부를 점검해서 조건을 충족시키지 못할 경우 사용자에게 펄의 오류 메시지가 아닌 이 프로그램을 실행하기 위해 다음으로 취해야 할 행동을 제시함으로써 사용자 친화적인 프로그램을 작성할 수 있습니다. 대부분의 경우 이런 기법은 프로그램의 기능적인 측면에서 영향을 미치는 요소는 아니나 오히려 펄 프로그래머 입장에서는 초급보다 한 단계 높은 내용이므로 펄을 더욱 잘 활용하기 위해서는 숙지해야할 내용입니다. 또한 회사에서와 같이 여러 사람과 협업을 하는 경우 펄을 잘 모르는 개발자나 사용자를 감안해서 사용자 친화적인 기능을 추가한다면 프로그램의 완성도를 한 단계 더 끌어올릴 수 있습니다.

Mastering::Bach is not in the CPAN

작성: 2009-01-17 14:00:12

갱신: 2009-06-23 15:57:06

글쓴이: keedi

출력

한 여자를 사랑하는 것은 바흐를 마스터하는 것과 같다. 바흐를 한 학기동안 모두 마스터할 수 없듯이 한 여자에 대한 사랑은 평생동안 훈련해야 한다. -Eyes for you forever

우연히 읽게 된 글 조각.

사랑을 시작하는 것은 쉬운 일이다.
불타는 열정, 주체할 수 없는 소유욕.
끓어오르는대로 뿜어내면 그만이잖아.
감정에 솔직하면 그것으로 충분해.

그럼 그 이후는? 그 열정이 사그라들면?
뻔한 결말, 가슴 아픈 이야기.
책과 영화, 드라마에서 아름다운척 쳐발라도
토할것 같이 불쾌한 쓰레기 같은 이야기들.

인정하자.
처음부터 잘못되었다는 것을.
모든 것을 알고 있었다는 것을.
사람은 변하지 않는다는 것을.

비상식적으로 길었던 unit test.
환경 변수의 덕에 skip과 todo는 있었을지언정
결국 자체 결과는 pass.

잘했어.

Mastering::Bach is not in the CPAN:

  keedi:~/what_is_love$ cd Mastering-Bach-0.0.1/
  keedi:~/what_is_love/Mastering-Bach-0.0.1$
  keedi:~/what_is_love/Mastering-Bach-0.0.1$ perl Build.PL
  keedi:~/what_is_love/Mastering-Bach-0.0.1$ ./Build
  keedi:~/what_is_love/Mastering-Bach-0.0.1$ ./Build test
  t/00.start...............................ok # every love starts with secret
  t/10.self/11.true_heart.t................ok
  t/10.self/12.emotion.t...................ok
  t/10.self/13.permanence.t................ok
  t/10.self/14.thowing_up_self_respect.t...ok
  t/10.self/15.patience.t..................ok
  t/10.self/16.best_practices.t............ok
  t/10.self/19.potential.t.................ok # do NOT forget this was the last chance
  t/20.bach/21.true_heart.t................ok
  t/20.bach/22.equipment_with_snobbism.t...fail # TODO: not yet implemented
  t/20.bach/23.patience.t..................fail # SKIP: fail had been expected
  t/20.bach/24.permanence.t................fail # SKIP: fail had been expected
  t/20.bach/25.proud_him.t.................fail # SKIP: fail had been expected
  t/20.bach/26.exclusive_love.t............fail # SKIP: fail had been expected
  t/99.end_with_failure.t..................ok # end with failure as had been expected
  All tests successful.
  Files=15, Tests=15,  9 years
  Result: PASS (TODO: 1, SKIP: 4)
  keedi:~/what_is_love/Mastering-Bach-0.0.1$ sudo shutdown -h now
  keedi:~/what_is_love/Mastering-Bach-0.0.1$