왜 배쉬 프로그래밍을 배워야 하는지 궁금한가? 두 가지 중요한 이유가 있다:
자세히 살펴보면 지금도 배쉬를 실행하고 있는 자신을 발견할 것이다. 비록 여러분의 디폴트 쉘을 변경하더라도 배쉬는 여전히 시스템 어딘가에서 실행되고 있을 것이다. 배쉬는 표준 리눅스 쉘이고 다양한 용도로 사용되기 때문이다. 배쉬는 이미 실행되고 있기 때문에 추가적인 배쉬 스크립트는 메모리 효율성이 높다. 이미 실행중인 배쉬 프로세스와 메모리를 공유하기 때문이다.
이미 배쉬를 구동하고 있을 뿐만 아니라, 매일매일 배쉬를 사용하고 있다. 이것은 항상 존재하기 때문에 숨겨진 부분까지 완전히 이용하는 방법을 터득하는 것이 좋을 것이다. 이렇게 될 때 배쉬 사용은 더욱 흥미로워지고 생산적인 일이 된다. 하지만 왜 배쉬 프로그래밍을 배워야 하는가? 이미 사용 방법을 알고 있는 강력하고 빠른 언어를 과연 배울 필요가 없는지 자문해보기 바란다. 명령어 쉘은 유닉스 시스템의 잠재성을 드러내고, 배쉬는 리눅스 쉘이다. 이것은 여러분과 머신을 이어주는 고급 접착제이다. 배쉬 지식을 향상시켜라. 그러면 리눅스와 유닉스에서의 생산성은 자연스럽게 올라간다.
그릇된 방식으로 배쉬를 배우면 혼란만을 가져올 뿐이다. 많은 초보자들은 배쉬 man page를 보기위해서 "man bash"를 타이핑한다. 매우 간단한 쉘 기능에 대한 기술적인 설명을 접한다. 또 어떤 사람들은 "info bash" (GNU info 문서를 보기위해서)를 타이핑한다. man page가 다시 디스플레이 되거나 (운이 좋으면) 접근하기 쉬운 좀더 친숙한 info 문서를 접하게 될 뿐이다.
이러한 사실이 초보자들에게는 실망스러울 수 밖에 없지만, 표준 배쉬 문서는 모든 사람들에게 "완벽한 것"이 될 수 없으며 쉘 프로그래밍에 익숙한 사람들에게 오히려 더욱더 많은 것을 제공하는 편이다. man page에는 상당히 많은 기술 정보들이 있지만 초보자들에게는 한계가 있다.
바로 이것이 이 시리즈가 나아가야 할 방향을 제시해 주고 있다. 이 글을 통해 실제로 배쉬 프로그래밍 구조체를 사용하는 방법을 제시하여, 자신만의 스크립트를 작성할 수 있도록 할 것이다. 기술 설명 대신, 무엇이 어떻게 작동하고, 실제로 어떻게 사용하는지를 설명할 것이다. 3 회에 걸친 시리즈를 통해, 여러분은 난해한 배쉬 스크립트를 작성하고, 배쉬를 자연스럽게 사용하고, 표준 배쉬 문서를 읽어서 지식을 보충할 수 있는 경지에 이르게 될 것이다. 이제 시작해보자.
배쉬를 비롯하여 거의 모든 쉘에서 사용자는 환경변수를 정의할 수 있는데 이것은 ASCII 스트링으로서 내부에 저장된다. 환경 변수에 있어서 가장 편리한 것 중 하나는 그들이 유닉스 프로세스 모델 표준이라는 점이다. 다시말해서, 환경변수는 쉘 스크립트만의 독점적인 것일 뿐 아니라 표준 컴파일 프로그램에 의해 사용될 수 있다. 배쉬에서 환경 변수를 "익스포트(export)"할 때 우리가 실행하는 모든 연속적인 프로그램은 설정을 읽을 수 있다. 이것이 쉘 스크립트이든 아니든 상관이 없다. 좋은 예로 vipw 명령어가 있는데 이것은 root가 시스템 패스워드 파일을 편집할 수 있도록 한다. 좋아하는 텍스트 에디터 이름에 EDITOR 환경 변수를 설정함으로서 vipw를 설정하여 vi 대신 사용할 수 있다.
다음은 배쉬에서 환경 변수를 정의하는 표준 방법이다:
$ myvar='This is my environment variable!' |
위 명령어에는 "myvar" 이라는 환경 변수가 정의되어 있고 "This is my environment variable!"라는 스트링이 포함되었다. 주의 깊게 봐야 할 부분이 많다: 우선, "=" 부호의 양 쪽에 공간이 없다. 공간이 있다면 에러가 생길것이다. 두 번째로 하나의 단어를 정의한다면 쿼트를 제거해야 했을 것이다. 환경 변수의 값이 하나의 단어 이상일 때에는 (스페이스 또는 탭 포함) 쿼트가 필요하다.
세 번째, 일반적으로 싱글 쿼트 대신 더블 쿼트를 사용할 수 있지만 위 예제에 적용한다면 에러가 발생한다. 싱글 쿼트를 사용하면, 특수 문자와 문자 시퀀스가 값으로 대체되는 곳에서 확장(expansion)이라고 하는 배쉬 기능을 사용할 수 없기 때문이다.
이제부터 환경 변수를 실제로 사용하는 법에 대해 알아보자. 다음은 예제이다:
$ echo $myvar This is my environment variable! |
환경 변수 앞에 $를 붙임으로서, 배쉬가 이것을 myvar의 값으로 대체할 수 있도록 할 수 있다. 배쉬에서는, 이것을 "변수 확장(variable expansion)"이라고 한다. 다음을 보자:
$ echo foo$myvarbar foo |
"fooThis is my environment variable!bar"가 에코(echo)되기를 기대했지만 이것은 작동하지 않았다. 무엇이 잘못되었는가? 간단히 말해서, 배쉬의 변수 확장 기능은 혼란스럽다. $m, $my, $myvar, $myvarbar 변수 등을 확장하기 원하는지 여부를 명령할 수가 없었다. 명료하고 정확하게, 배쉬에게 어떤 변수들을 우리가 레퍼링 해야 하는지를 명령할 수 있을까? 다음과 같이 해보자:
$ echo foo${myvar}bar
fooThis is my environment variable!bar
|
환경 변수가 주변 텍스트에서 완전히 분리되지 않을 때 중괄호로 환경 변수를 묶었다. $myvar이 좀 더 빨리 타이핑되고 대부분의 시간 작동하는 반면, ${myvar}는 거의 모든 상황에서 정확히 파싱될 수 있다. 무엇보다도, 두 개 모두 같은 일을 수행한다. 환경 변수가 공백(스페이스 또는 탭)으로 주변 텍스트와 분리되지 않을 때 좀더 명료한 중괄호 형식을 사용해야 한다는 것을 기억하라.
변수를 "익스포트(export)" 할 수 있다는 것을 기억하는가? 환경 변수를 익스포트할 때 부차적으로 실행되는 모든 스크립트 또는 실행파일 환경에서도 환경 변수를 사용할 수 있다. 쉘 스크립트는 쉘의 빌트인 환경 변수 지원을 사용하여 환경 변수에 "도달"할 수 있다. 반면 C 프로그램은 getenv() 함수 호출을 사용할 수 있다. 다음은 C 코드 예제이다. 여러분이 타이핑 하고 컴파일 해야 한다. 이것은 C의 관점에서 환경 변수를 이해하는데 도움이 될 것이다:
myvar.c; 샘플 환경 변수 C 프로그램
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *myenvvar=getenv("EDITOR");
printf("The editor environment variable is set to %s\n",myenvvar);
} |
위 소스를 myenv.c 파일에 저장하고 명령어를 만들어 컴파일한다:
$ gcc myenv.c -o myenv |
현재, 디렉토리에는 실행할 때 EDITOR 환경 변수의 값을 프린트하는 실행 프로그램이 있을 것이다. 머신에서 이것을 실행할 때 어떤 일이 발생하는지 보자:
$ ./myenv The editor environment variable is set to (null) |
EDITOR 환경 변수가 설정되지 않았기 때문에 C 프로그램은 null 스트링이 된다. 이것을 특정 값으로 설정해보자:
$ EDITOR=xemacs $ ./myenv The editor environment variable is set to (null) |
myenv가 "xemacs" 값을 프린트 할 것으로 기대했겠지만 그렇게 되지 않았다. EDITOR 환경 변수를 익스포트 하지 않았기 때문이다. 이를 실행시켜 보자:
$ export EDITOR $ ./myenv The editor environment variable is set to xemacs |
다른 프로세스에서도 마찬가지로 환경 변수가 익스포트 되기 전까지는 환경 변수를 볼 수 없다는 것을 확인했다. 다음과 같이 한 줄로 환경 변수를 정의하고 익스포트 할 수 있다:
$ export EDITOR=xemacs |
이 것은 두 줄일 때와 동일하게 작동한다. unset을 사용하여 환경변수를 지우는 방법을 보자:
$ unset EDITOR $ ./myenv The editor environment variable is set to (null) |
스트링을 자르는 것, 다시말해서 원래 스트링을 더 작은 스트링으로 쪼개는 것, 청크(chunk)들을 나누는 것은 쉘 스크립트에서 매일매일 수행되는 태스크 중 하나이다. 쉘 스크립트는 완벽히 제한된 경로를 취하고 종료 파일이나 디렉토리를 찾는다. 이것을 배쉬에서 코딩하는 것이 가능하고, 표준 basename 유닉스 실행파일은 이것을 완벽하게 수행한다:
$ basename /usr/local/share/doc/foo/foo.txt foo.txt $ basename /usr/home/drobbins drobbins |
basename은 스트링을 자르는데 있어서 매우 편리한 툴이다. 이것은 dirname과 짝을 이루며, basename이 버린 "다른" 부분의 경로를 리턴한다:
$ dirname /usr/local/share/doc/foo/foo.txt /usr/local/share/doc/foo $ dirname /usr/home/drobbins/ /usr/home |
실행 명령어의 결과를 포함하고 있는 환경 변수를 만드는 방법은 쉽다:
$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt` $ echo $MYDIR /usr/local/share/doc/foo |
위에서 수행했던 것을 "명령어 치환"이라고 한다. 이 예제에도 주목할 만한 가치가 있는 것이 많다. 첫 번째 라인에서, 실행하기 원하는 명령어를 '백 쿼트(back quote)'로 묶었다. 이들은 표준 싱글 쿼트는 아니지만 키보드 상의 탭키 위에 있는 키이다. 배쉬의 명령어 치환 신택스를 이용하여 이와 똑같은 것을 수행할 수 있다:
$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt) $ echo $MYDIR /usr/local/share/doc/foo |
배쉬는 똑같은 일을 수행하는 데 있어서 다중의 방식을 제공한다. 명령어 치환을 사용하여 ` ` 또는 $( ) 사이에 명령어나 명령어의 파이프라인을 놓을 수 있고, 이것을 환경변수로 지정할 수 있다. 너무나도 편리하다! 다음은 명령어 치환을 이용하여 파이프라인을 사용하는 방법이다:
MYFILES=$(ls /etc | grep pa) bash-2.03$ echo $MYFILES pam.d passwd |
basename과 dirname이 훌륭한 툴이지만, 단순한 표준 경로이름 조작보다 좀더 세련된 스트링 "자르기" 작동을 수행하려면 시간이 필요하다. 더욱 강력한 것이 필요하다면, 배쉬의 향상된 빌트인 변수 확장 기능을 사용할 수 있다. 우리는 이미 표준의 변수 확장을 사용했다.(${MYVAR}). 하지만 배쉬는 그 자체에서 손쉬운 스트링 자르기를 수행할 수도 있다:
$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg
|
첫 번째 예제에서, ${MYVAR##*fo}를 타이핑했다. ${ } 안에, 우리는 환경 변수의 이름, ##, 와일드카드 ("*fo")를 타이핑했다. 그런다음 배쉬는 MYVAR를 취해서 "*fo" 와일드카드와 매치되는 "foodforthought.jpg" 스트링에서 가장 긴 부분 문자열(substring)을 찾고 이것을 스트링의 시작에서 자른다. 언뜻 보기에는 이해하기 힘들다. 그래서 "##" 옵션이 어떻게 작동하는지를 단계별로 살펴보자. 우선, "*fo" 와일드카드와 매치하는 "foodforthought.jpg" 의 앞부분 부터 부분 문자열을 찾는것으로 시작한다. 다음은 체크되는 부분 문자열이다:
f fo MATCHES *fo foo food foodf foodfo MATCHES *fo foodfor foodfort foodforth foodfortho foodforthou foodforthoug foodforthought foodforthought.j foodforthought.jp foodforthought.jpg |
매치(match)를 위한 스트링을 찾은 후에, 배쉬가 두 개를 찾았다는 것을 알 수 있다. 가장 긴 매치를 찾고 이것을 원래 스트링의 앞부분에서 제거하고 그런다음 결과를 리턴한다.
위 예제에서 두 번째 형식의 변수 확장은 첫 번째와 동일하다. 이것이 단지 하나의 "#" 을 사용한다는 것을 제외하고는 배쉬는 거의 같은 프로세스를 수행한다. 이것은 첫 번째 예제가 수행했던 것과 같이 같은 세트의 부분 문자열을 체크한다. 배쉬는 가장 짧은 매치를 원래 스트링에서 제거한다는 것을 제외하고 결과를 리턴한다. 따라서 이것이 "fo" 부분 문자열을 체크하자마자, 스트링에서 "fo" 를 제거하고 "odforthought.jpg"을 리턴한다.
$ MYFOO="chickensoup.tar.gz"
$ echo ${MYFOO%%.*}
chickensoup
$ echo ${MYFOO%.*}
chickensoup.tar
|
%와 %% 변수 확장 옵션은 #과 ##과 동일하게 작용한다. 스트링의 끝에서 매칭 와일드카드를 제거한다는 것만 다르다. 끝에서 부터 특정 부분 문자열을 제거하려면 "*" 문자를 사용할 필요가 없다:
MYFOOD="chickensoup"
$ echo ${MYFOOD%%soup}
chicken
|
이러한 형식의 스트링 자르기는 매우 편리하다; 시작할 문자와 부분 문자열의 길이를 지정한다. 모두 콜론으로 구분한다.
지금까지 스트링 자르기에 대한 모든 것을 배웠다. 간단한 쉘 스크립트를 작성해보자. 우리의 스크립트는 인자로서 하나의 파일을 받아들이고 이것이 tarball이 되는지의 여부를 프린트 할 것이다. 이것이 tarball인지를 결정하기 위해서는, 파일의 끝에서 ".tar" 패턴을 찾을 것이다:
#!/bin/bash
if [ "${1##*.}" = "tar" ]
then
echo This appears to be a tarball.
else
echo At first glance, this does not appear to be a tarball.
fi
|
이 스크립트를 실행하기 위해서는, mytar.sh 파일에서 이것이 실행될 수 있도록 "chmod 755 mytar.sh"를 타이핑한다:
$ ./mytar.sh thisfile.tar This appears to be a tarball. $ ./mytar.sh thatfile.gz At first glance, this does not appear to be a tarball. |
이제 작동한다. 하지만 기능적이지 않다. 이것을 좀 더 유용하게 만들기 전에, 위에서 사용된 "if" 문을 보자. 이 안에 부울 수식이 있다. 배쉬에서, "=" 비교연산자는 스트링 등질성을 검사한다. 배쉬의 모든 부울 수식들은 대괄호로 묶인다. 하지만 부울 수식이 정확히 무엇을 테스트하는가? 왼쪽을 보자. 스트링 자르기에 대해 배운 것을 토대로, "${1##*.}" 는 환경 변수 "1"에 포함된 스트링의 시작에서 "*." 의 가장 긴 매치를 제거하고 결과를 리턴할 것이다. 이것은 파일에서 마지막 "." 다음의 모든것이 리턴되도록 할 것이다. 파일이 ".tar"로 끝나면, 결과로서 "tar"를 얻고 조건은 'true'가 된다.
환경 변수 "1"이 무엇인지 궁금한가? $1은 스크립트에 대한 첫 번째 명령행 인자이다. 따라서 $2는 두 번째 인자이다.
대부분의 언어들과 마찬가지로 배쉬도 고유의 조건 형식이 있다. 그들을 사용할 때, 위 포맷을 엄수해야 한다. 이렇게 하면 코드 판독이 쉬워지고 디버그도 쉬워진다. "if,else"형식 뿐 아니라, "if" 문에는 여러 다른 형식들이 있다:
if [ condition ] then action fi |
이것은 condition이 'true'일 때만 액션을 수행한다. 그렇지 않으면 어떤 액션도 수행하지
않고 "fi"에 뒤따르는 모든 라인을 계속하여 실행한다.
if [ condition ] then action elif [ condition2 ] then action2 . . . elif [ condition3 ] then else actionx fi |
위의 "elif" 형식은 각 조건을 연속적으로 테스트하고 첫 번째 true 조건에 상응하는 액션을 실행한다. 어떤 조건도 true가 아니라면, "else" 액션을 실행하고, 하나가 나타나고 그런 다음 전체 "if,elif,else" 문에 뒤따르는 라인들을 실행한다.
가장 기본적인 배쉬 함수를 살펴보았다. 실제 스크립트 작성을 시작할 때이다. 다음에는, 루핑 구조체, 함수, 네임스페이스 등을 설명하겠다.
- developerWorks worldwide 사이트에서 이 기사에 관한 영어원문.
- GNU의 배쉬 홈페이지
- 배쉬 온라인 레퍼런스 매뉴얼
Daniel Robbins는 Gentoo Technologies, inc.의 사장/CEO이다. 또 PC용 고급 Linux인 Gentoo Linux의 창설자이자, 차세대 Linux 포트 시스템인 Portage 시스템의 창시자이다. 또한 Macmillan사에서 출판하는 Caldera OpenLinux Unleashed, SuSE Linux Unleashed, Samba Unleashed에 집필활동을 하고 있다.