BLOG ARTICLE Development | 28 ARTICLE FOUND
- 2011.03.07 MySQL UTF-8 인식 문제
- 2011.02.22 HTML 에서 PHP 인식문제
- 2009.09.29 MySQL 데이터 타입별 크기
- 2008.10.30 Vito audio note 자동녹음된 파일 자동삭제 3
- 2007.10.29 익스플로러(Internet Explorer)에서 PNG의 알파채널사용하기
- 2007.09.30 문자열 처리 전문가 Perl
- 2007.09.29 Perl 기초 문법
- 2007.05.25 GIF Loading Image
- 2007.05.24 MS Word 2007을 이용한 Blog에 글 게시하기
- 2007.05.17 [응용] Makefile 작성법
AddType application/x-httpd-php .php .php3 .inc .ph .htm .html
</IfModule>
완료후,
TINYINT (-128 ~ 127)
SMALLINT (-32768 ~ 32767)
MEDIUMINT (-8388608 ~ 8388607)
INT (-2147483648 ~ 2147483647)
BIGINT (-9223372036854775808 ~ 9223372036854775807)
FLOAT
DOUBLE
DECIMAL(NUMERIC)
DATE (1000-01-01 ~ 9999-12-31, YYYY-MM-DD)
DATETIME (1000-01-01 00:00:00 ~ 9999-12-31 23:59:59, YYYY-MM-DD HH:MM:SS)
TIMESTAMP (1970-01-01 00:00:00 ~ 2106..., YYYYMMDD[HH[MM[SS]]]) O
TIME (-838:59:59 ~ 838:59:59, HH:MM:SS)
YEAR (1901 ~ 2155, 0000, YYYY)
CHAR
VARCHAR
TINYTEXT (0 ~ 255)
TEXT (0 ~ 65535)
MEDIUMTEXT (0 ~ 16777215)
LONGTEXT (0 ~ 4294967295)
TINYBLOB (0 ~ 255)
BLOB (0 ~ 65535)
MEDIUMBLOB (0 ~ 16777215)
LONGBLOB (0 ~ 4294967295)
Vito audio note는 제 미라지를 일상의 필수품으로 만들어 놓았습니다. 아이디어 회의에서 중요한 부분들을 놓치지 않게 해 주었으며, 녹음 내용을 외장 메모리로 선택하여 회의 내용을 시간별로 분류해놓아 녹음 내용을 컴퓨터로 손쉽게 이동 및 정리 할 수 있게 해주었습니다.
또다른 강력한 기능으로는 전화통화 내용을 자동으로 녹음하는 기능이 있습니다. 얼마전에 결혼기념일이었는데요, XX카드 제휴회사에서 결혼기념일 선물을 무료로 보내준다고 연락이 왔었습니다. 그 다음날 집으로 택배상자가 배달되었고, 그 회사 안내원으로부터 돈내라는 전화가 왔습니다. 이때 전날 자동녹음된 안내원과의 통화내용 중에 무료라는 말을 찾아 증거로 들려 주고, 제품을 반품하였습니다.
사설이 길었네요. 이제 본론으로... --;
어찌되었던 간에, 이 유용한 프로그램을 2주정도 사용하다보니, 자동녹음된 통화 내용이 계속 누적되어 외장 메모리 공간이 부족한 상황이 왔습니다. 이 녹음자료들을 매번 확인하여 지워주기는 귀찮고, 일주일이 지난 녹음내용은 자동으로 지워주는 기능이 아쉬웠습니다. 결국 Mortscript를 사용하여 이 기능을 수행하는 스크립트를 만들었습니다.
※ 11월 8일 오후 4:20 이전에 받으신 분들은 새로 받아주세요. "cleanVACallData.mscr"의 버그 수정되었습니다. cleanVACallData.mscr 파일의 39번째 줄에
fullPathFile = OutgoingFileArray \ fileName --> fullPathFile = pathOutgoingDir \ fileName
변수를 잘못 사용했네요. 이 문제로 Outgoing 폴더의 파일은 지워지지 않았습니다. incoming 폴더 삭제하는 것만 테스트한 저의 불찰입니다. 죄송합니다.
※ 첨부 파일을 실행시키려면 반드시 MortScript 4.11b 버전이 설치되어 있어야 합니다. (4.11b 버전 밑으로는 정상작동 안합니다. 스크립트 내부에 DirContents() 라는 함수를 사용하는데, 이함수는 4.11b 버전 이상부터 사용 가능합니다.)
첨부된 "cleanVAC.zip" 파일에는 아래와 같은 두가지 파일이 들어있습니다.
1. cleanVACallData.mscr | 오래된 녹음파일을 자동으로 삭제해주는 스크립트 |
2. timer.mscr | cleanVACallData.mscr 를 매일 정해진 시간에 자동으로 실행시켜주는 스크립트 |
이 스크립트들을 사용하는 방법은 두가지가 있습니다.
A. 장치를 소프트 리셑할때 마다 자동으로 cleanVACallData.mscr 파일이 실행되게 한다. |
A 방법을 사용하면, 2번 파일 "timer.mscr"을 사용하지 않으셔도 됩니다.
B 방법을 사용하려면, 1번 "cleanVACallData.mscr" 파일과 2번 "timer.mscr" 파일 둘다 사용하셔야 합니다.
위 두 파일을 원하는 방법에 따라 미라지의 특정 폴더에 저장합니다. 저의 경우에는 다음과 같습니다.
1번 파일 저장 위치 : "\Nate Store\My Scripts\cleanVAC\cleanVACallData.mscr" 2번 파일 저장 위치 : "\Nate Store\My Scripts\cleanVAC\timer.mscr" |
A 방법을 사용하려면 미라지의 특정 폴더에 저장된 "cleanVACallData.mscr" 파일의 바로가기 아이콘을 "\Windows\시작\" 폴더 안에 넣으시면 됩니다.
B 방법을 사용하려면 미라지의 특정 폴더에 저장된 "timer.mscr" 파일의 바로가기 아이콘을 "\Windows\시작\" 폴더 안에 넣으시면 됩니다.
cleanVACallData.mscr 스크립트를 사용하시려면 아래 와 같은 두가지 설정을 확인하셔야 합니다.
cleanVACallData.mscr 파일을 텍스트 편집기로 열면 아래와 같은 입력값이 있습니다.
dateRange = 7 |
여기서 7은 7일이 지난 통화녹음된 파일을 지운다는 말입니다. 3일 지난 통화녹음을 자동으로 삭제하시려면, 7을 3으로 고쳐주시면 됩니다. 그 아래로 3줄 더 내려가면 아래 경로들이 설정되어 있습니다.
pathIncomingDir = "\Storage Card\My Documents\My Audio Notes\Incoming\" |
위 경로들은 통화 내용이 저장되는 경로입니다. 본인의 PDA 상에 Vito audio note를 통해 통화내용이 저장되는 폴더가 위와 다르다면 적절하게 수정해주셔야 합니다. 여기까지만 하시면 cleanVACallData.mscr을 사용해 오래된 녹음파일을 자동으로 삭제할 수 있습니다. 그러나 B 방법( cleanVACallData.mscr 를 매일 정해진 시간에 자동으로 실행시켜주기 )을 사용하려면, 2번 파일인 "timer.mscr"을 약간 수정해주어야 합니다.
timer.mscr 파일을 텍스트 편집기로 열면 아래와 같은 입력값이 있습니다.
paramHour = 23 |
여기서 23은 23시 즉 오후 11시를 의미하며, 1은 1분을 의미합니다. 위 두 입력값은 오후 11시 1분에 "cleanVACallData.mscr"을 자동 실행시키라는 의미입니다. 여기서 시간을 절대로 24로 설정하시면 안됩니다!
2줄 더내려가면 아래 경로들이 설정되어 있습니다. "pathTargetProg "는 "cleanVACallData.mscr" 이 저장된 경로를 의미하며, "pathTimerProg "는 "timer.mscr"이 저장된 경로를 의미합니다. ( "timer.mscr"의 경로가 필요한 이유는 timer.mscr이 실행되어 cleanVACallData.mscr를 Windows mobile의 notification queue에 등록을 하면서 자기 자신도 내일 다시 호출 할 수 있도록 함께 등록하기 위함입니다.)
pathTargetProg = "\Nate Store\My Scripts\cleanVAC\cleanVACallData.mscr" |
여기까지 본인의 설정에 맞게 수정하시면 하루에 정해진 시간에 한번 오래된 통화녹음 파일을 자동으로 삭제할 수 있습니다.
사실 "cleanVACallData.mscr" 파일을 무한루프 돌리며 딜레이 시간을 설정하면 "timer.mscr" 파일 없이 자동으로 실행 시킬 수 있습니다만, 소스코드의 효율적인 관리 차원에서 두 파일로 분리하였습니다. 원하신다면, 두 파일을 구미에 맞게 수정하시어 배포하셔도 상관없습니다. 간혹가다가, 저작권 이야기로 소스코드 수정을 금지하시는 분들도 있으시지만, 간단한 스크립트인 만큼 더 좋은 방향으로 응용하여 개선시켜 나가면 좋겠습니다.
한때 GIF파일을 웹에서 자주 사용했었다.
바로 투명(transparency)을 지원했기 때문이다.
그런데 저작권과 관련하여 여기저기에서 말들이 많이 나와 PNG라는 새로운 형식이 나왔다.
반갑게도 점차 많은 웹편집기에서 사용하는 이미지로 jpg, gif 외에 png를 지원하고 있다.
알파채널이 포함된 png24 형식은 모질라 파이어폭스에서는 제대로 보이는데 인터넷익스플로러에서는 불투명하게 보여 사용자의 의도대로 웹에 보이지 않는다. 익스플로러7에서는 제대로 보인다고 한다.
익스플로러5나 익스플로러6에서도 제대로 보이게 하는 방법이다.
1. <head>와 </head>사이에 다음 스크립트를 삽입한다.
<script language="javascript">
<!--
function setPng24(obj) {
obj.width=obj.height=1;
obj.className=obj.className.replace(/\bpng24\b/i,'');
obj.style.filter =
"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+ obj.src +"',sizingMethod='image');"
obj.src='';
return '';
}
//-->
</script>
2. <head>와 </head> 사이에 다음의 스타일을
<style type="text/css">
.png24 {
tmp:expression(setPng24(this));
}
</style>
3. 표시하고자 하는 png 이미지의 링크에 다음을 추가한다.
<img src="./images/dap_100.png" class="png24">
출처 : http://blog.naver.com/prettyjii/100037708588
문자열 처리 전문가 Perl
오치환*
*동명정보대학교 컴퓨터공학과
Specialist Perl at String Handling
Chy-Hwan Oh*
*Department of Computer Engineering, Tong-Myong University, Busan , Korea
요 약 : 본 논문에서는 Perl에 관하여 논한다. 특히 Perl은 문자열을 다루는데 최고의 언어라고 할수 있다. Perl에 관하여 알아보고, 사용 되는 분야에 대해서 논할 것이다. 그 중 문자열 처리에 관한 부분에 집중하여 논한다.
핵심용어 : 문자열 처리, perl
ABSTRACT : In this paper, We describe about Perl. Especially perl is the best language for string handling. We inquire out perl and talk about where it using. Among them, We describe about String Handling
KEY WORDS : String handling, perl
1.서론
Perl(Practical Extraction and Reporting Language)은 임의의 형태를 갖춘 텍스트 파일을 읽고, 이 파일에서 의미있는 정보를 추출하여 이 정보를 근거한 레포트를 출력하는 제반 작업에 최적화된 언어이며, 시스템 관리에도 매우 적절한 언어이다.
또 perl은 아름다운 언어보다는 실용적인 언어를 지향한다. perl은 (이 언어의 개발자가 말하기를) C, sed, awk 그리고 sh와 같은 언어나 프로그램들의 가장 좋은점들을 취합한 언어이므로, 이 언어, 프로그램들에 익숙한 사람들이라면 perl 사용에도 역시 큰 어려움을 느끼지 않을 것이다. (컴퓨터언어 사학자라면 perl의 문법이 아마 csh이나 Pascal 혹은 BASIC-PLUS와도 닮았다고 생각할 것 같다.)
문장구조는 C 언어와 상당히 흡사하다. 대개의 Unix용 프로그램들과는 달리, Perl은 우리가 사용할 데이터 크기에 한계를 두지 않는다. 메모리만 충분하다면 한 파일의 내용 전체를 변수하나에 넣을수 있다.. 재귀(Recursion)는 무제한 반복될 수 있다. 해쉬(혹은 조합배열)에서 사용하는 테이블은 성능저하를 일으키지 않으면서 무한대로 커질 수 있다. Perl은 섬세한 패턴일치(pattern matching) 기술을 이용하여 많은 양의 데이터를 빠르게 검색할 수 있다. 비록 텍스트 검색에 최적화되어 있긴하지만 이진데이터도 다룰 수 있으며 해쉬와 같은 형태의 dbm 파일도 만들어낼 수 있다. 또 setuid를 사용하는 스크립트의 경우 C 프로그램에서 사용하는 것보다 안전한데, 데이터 흐름을 추적하는 메커니즘이 말도 않되는 대부분의 보안상의 빈틈을 막아주기 때문이다.
만일 sed나 awk, sh (혹은 bash)를 사용한 프로그램을 만들어 문제를 풀어 나갈 때, 그 문제가 이런 프로그램의 능력으로는 극복하기 어렵다거나, 혹은 프로그램이 좀 더 빨랐으면 하는데, C언어를 사용해서 어리석게도 시간낭비를 하고 싶지 않다면, Perl이 해결책을 줄 것이다. 참고로, sed나 awk 스크립트를 Perl 스크립트로 바꿔주는 변환기들을 (인터넷에서) 찾을 수 있다.
Perl은 더구나 HTML이나 사용자입력과 같은 텍스트를 다루는데 아주 강력한 기능을 가지고 있으므로 문자열 위주의 데이터를 다루기에는 최적의 언어라 할 수 있으며, UN*X, MVS, VMS, MS-DOS, Macintosh, OS/2, Amiga, Windows NT 등 여러 가지 Operating system에 Porting되어 있으므로 소스파일의 이식성이 뛰어나다.
또 Perl은 강력한 사용자층을 가지고 있다. GURU급의 사용자, 개발자로부터 초보자에 이르기까지 Perl의 확장과 사용에 많은 시간과 정렬을 제공하는 사용자들이 CPAN의 많은 모듈을 만들고, 사용하고 고쳐나가면서 Perl의 발전에 힘쓰고 있다. 이 개발자, 사용자층이 있는 이상, Perl은 계속해서 발전할 것이다.
C, C++같은 언어를 써보면 그 언어에서 문자열과 메모리의 관리가 얼마나 짜증나는 일인지 안다. Perl은 그런 복잡한 것이 없다. 즉, 언어의 관리 보다는, 언어의 유용성에 초점을 맞춘 언어이다.
Perl은 무료이다. Perl을 빛내주는 모듈들도 99.9999%가 무료이다. 그리고 많은 프로그램들이 무료로 제공되고 있다.
1993년부터 시작해서 Perl 버젼 5는 거의 완전히 새로 만들어졌고, 그로 인하여 다음과 같은 내용들이 추가되었다.
- 수많은 모듈들을 이용한 모듈화(modularity)와 재사용성(reusability) : 이는 라이브러리를 좀 더 효율적으로 사용할 수 있는 방법이다.
- C 언어의 한부분으로 사용하거나, C 언어를 이용하여 확장 가능.(embeddable and extensible)
- 다른 데이터(DBM같은)를 변수에 통합하여 변수를 조작하는 것으로 원래의 데이터를 조작할 수 있는 기능. (roll-your-own magic variables (including multiple simultaneous DBM implementations))
- 서브루틴으로 내장 함수 재선언(override)하기. 서브루틴의 동적인 선언(autoload). 프로토타이핑.
- 단순한 scalar, 배열, 해쉬이상의 복잡한 데이터 구조와 익명의 함수. (arbitrarily nested data structures and anonymous functions)
- 객체지향적 프로그래밍(object-oriented programming)
- C 코드 또는 Perl 바이트코드로의 변환(compilability into C code or Perl bytecode)
- 쓰레드 지원(support for light-weight processes (threads))
- 국제화, 지역화, 유니코드(support for internationalization, localization, and Unicode)
- 변수의 사전적 범위지정(lexical scoping) - my 연산자를 이용한 변수의 접근범위 제한.
- 향상된 정규식(regular expression enhancements)
- 향상된 디버거와 내장 편집기를 사용하여 사용자와 상호작용 하는 환경 (enhanced debugger and interactive Perl environment with integrated editor support)
- POSIX 1003.1 호환 라이브러리(POSIX 1003.1 compliant library)
2. 사용분야
perl은 한 때 HTTP, HTML이 부상하면서 CGI를 위한 언어로 각광 받았다. 하지만, CGI로서의 역할은 PHP와 같은 언어나, JSP, ASP 등의 등장으로 미미해진다.
그러나, 웹의 부상과 CGI로서의 역할이라는 다소 표면적이고 번지르르한 허명뒤에는 또 다른 역할이 있었다.(물론 아직도 꼭 CGI가 필요한 곳에서는 막강한 힘을 발휘하고 있다. Apache 서버의 mod_perl이 바로 그 중 하나일 것이다.)
그 것이 바로 "관리"를 위한 역할이다. 관리는 시스템 관리만을 위한 것이 아니다. 시스템 관리는 물론이고, 데이터 관리, 웹 관련 관리 등 다양한 관리를 포함한다.
웹 관련 관리에 관해 간단히 말해 보자면, 실제적인 사용자와의 인터페이스는 다른 언어에 맡긴다지만, 가장 쉽게 생각할 수 있는 perl의 또 다른 역할이 웹사이트의 방문기록을 분석하는 것이다. 웹의 방문기록을 counter 프로그램을 이용하여 저장한다면, 그것은 약간 비효율적이다. 그 보다는 Log 파일에 저장된 내용을 추후에 분석하는 것이 대개는 좋다. 이럴 때 perl을 사용하면 일이 매우 쉬워진다. 다른 언어로 하기는 비효율적일 것이다. 왜냐하면 문자열을 다루는 데에는 perl이 최고이기 때문이다. 또, 만일 어떤 페이지의 내용이 30분이나 1시간에 한 번씩 바뀌어야 한다면(날씨관련 사이트처럼), 혹은 하루에 1, 2번 바뀌어야 한다면, perl을 이용하는 것이 가장 좋을 것이다. 주기적으로 어떤 조건에 따라서 새로운 페이지를 생성하여 HTML로 저장한 다음 사용하는 것이, 매번 php나 jsp를 이용하여 생성하는 것보다는 훨씬 경제적이고 속도도 많이 빠를 것이다. 서버가 아닌 클라이언트의 입장이 될 수도 있다. 어떤 페이지를 주기적으로 다운로드 받아서 분석한 후 데이터로 저장해야 한다면, 최고의 문자열처리 언어인 perl을 이용하는 것이 좋을 것이다. 게다가 대상 페이지가 자주 변하는 것이라면 융통성있는 perl이 막강한 힘을 부여해 줄 것이다.
시스템 개발에서도 prototype 언어로 활용할 수도 있고(예를 들어 메일 서버를 구현하기 전에 perl로 실제로 구현해 보고, 이를 C와 같은 언어로 porting 하는 경우), 다른 언어의 code 생성에 template 기반의 tool로 사용할 수도 있는 등 그 쓰임새가 엄청나다.
3. Perl as language
본 장에서는 perl 의 기초문법에 대해 알아본다.
3.1. 펄의 데이터 타입 및 특수변수
3.1.1 Perl
1986년 Unix 프로그래머인 Larry Wall이라는 사람이 자신이 맡은 업무를 보다 쉽게 처리할려고 하는 과정에서, 자신의 일을 해결할수 있는 가장 간결하고 적합한 유틸리티를 만들게 되었다, 그래서 그는 새로운 프로그래밍 언어를 발명하게 되었고, 그후 펄이란 이름으로 빠르게 번져나갔다고 한다.
3.1.2 Date Type
펄의 데이터 타입은 스칼라(scalars), 배열(arrays), 결합형 배열(associative arrays) 이렇게 세가지가 있다.
그리고, 모든 변수명을 대소문자를 구별한다. $temp와 $Temp는 서로 다른 변수가 된다.
3.1.2.1 스칼라(Scalars)
펄은 다른 언어와는 달리 모든 수와 문자열을 이 스칼라로 표현한다.
스칼라 변수명은 '$'표로 시작한다.
$a = 2 ; - 숫자
$b = "test" ; - 문자
$temp = "My name is Soo" ; - 문자열
3.1.2.2 배열(Array)
스칼라의 집합을 배열이라 하며, '@'표시로 시작한다. 다음과 같이 괄호안에 ',(쉽표)'로 각 변수는 구별된다.
@name = ("chanho", "unicorn", "jongbum", "Hongsun") ;
@id = (1234, "unisoo", 0987) ;
또한 배열안에 또 다른 배열이 정의 될수 있다.
@a = (1, 2) ;
@b = (3, 4) ;
@c = (@a, @b) ;
이렇게 하면 @c에는 1, 2, 3, 4의 값을 가지게 된다.
여기서 주의할 것은 배열을 스칼라에 할당 했을 경우이다.
@a = (1, 2) ;
@b = @a ; <-- @b는 당연히 1, 2의 값을 가지게 된다.
$c = @a ; <-- $c는 1, 2 의 값이 아닌 2의 값을 가지게 된다.
$c는 array내의 값을 가지는 것이 아니라, array 내의 값들의 개수를 가진다.
만일 배열내의 임의의 값에 접근하고자 할경우는,
@a = ("name", "school", 4, "Korea") ;
$temp1 = $a[0] <-- name
$temp2 = $a[3] <-- Korea 의 값을 가지게 된다.
배열은 0번째부터 값을 가지게 된다. 그러니깐 위 경우는 값이 4개이므로 $a[0]은 'name'이 되고, $a[1]은 'school'이 되고, $a[2]는 '4'가 되고, $a[3]은 'Korea'의 값을 가지게 된다.
3.1.2.3 결합형 배열(Associative Arrays)
이 결합형 배열은 위의 배열과는 조금 다른 모습을 가진다. 위의 배열은 그냥 값만 가지는것에 비해, 결합형 배열은 키(key)와 이 키에 대응하는 값을 가진다. 결합형 배열은 '%'표시로 시작되며, 다음과 같다.
%animal = ("name", "ppoby", "number", "two", "race", "jabjong") ;
이렇게 표현되어 지며, 배열의 각 요소들에 접근하기 위해서는
$temp = $animal{"name"} ; <-- 'ppoby'
$temp_1 = $animal{"number"} ; <-- 'two'
$temp_2 = $animal{"race"} ; <-- 'jabjong' 의 값이 할당되어 진다.
즉 위의 예에서, 'name은 ppoby를 number는 two를 race는 jabjong' 과 쌍을 이루게 된다. 다시 말해서 위의 배열에서 키는 'name, number, race'가 되고, 각 키의 값으로는 'ppoby, two, jabjong'이 되는 것이다.
3.1.3 특수 변수(Special Variables)
여기서는 펄에서 대단히 많이 쓰이는 변수 몇가지만 소개하기로 한다.
3.1.3.1 환경 변수(Environment Variables) - %ENV
이 변수는 환경 변수를 가지고 있는 결합 배열이다. 이 변수를 이용해서 아주 쉽게 환경 변수의 내용을 얻을 수 있다. 폼으로 부터 보내어진 데이터나, 접속한 사용자의 브라우져 정보, 클라이언트의 IP 주소, 자신의 서버가 사용하고 있는 웹 프로토콜의 버젼 등 쉽게 그 정보를 얻을수 있다.
사용 방법은
$ENV{'원하는 환경변수'} ;
$temp = $ENV{'REMOTE_ADDR'} ; 접속한 사람의 IP주소를 알수 있다.
자주 쓰이는 변수를 정리해 보았다.
CONTENT_LENGTH
폼과 함께 제출된 데이터 내용의 바이트 수
CONTENT_TYPE
폼과 함께 제출된 데이터 내용의 타입
HTTP_USER_AGENT
웹 브라우져 소프트웨의 버젼
PATH_INFO
요청과 함께 웹 서버로 보내어진 경로
QUERY_STRING
폼 제출로 부터 데이터를 담고 있는 문자열
REMOTE_ADDR
클라이언트의 IP주소
REMOTE_HOST
클라이언트의 호스트 이름
REQUEST_METHOD
스크립트를 호출하는 방법(GET, POST)
3.1.3.2 프로그램 인자 - @ARGV
펄 명령 라인 내에서 지정된 인자들은 특수 배열인 @ARGV를 통해 펄 스크립트 내로 전달 되어 진다.
3.1.3.3 현재 라인 - $_
특수 변수 '$_'는 입력의 현재 라인을 저장한다.
3.1.3.4 $(숫자) - $1, $2, $3 .....
특수 변수 $n는 패턴에 의해 매칭된 변수를 저장할 때 사용 되어진다. 첫번째 매칭된 문자열은 $1에, 두번째 매칭된 문자열은 $2에... n 번째 매칭된 문자열은 $n 에 기록 된다.
3.2. 제어문, 파일제어
3.2.1 연산자(Operator)
우선 Perl에서 쓰이는 대표적인 논리적 연산자에 대해서 알아보겠다. 논리적 연산자로는 ||(or) 와 &&(and)가 있다.
이 두개의 연산자는 양쪽에 오는 값에 따라 참, 또는 거짓의 값을 되돌린다.
||(or)
|| 연산자는 '또는' 이란 뜻으로, 'A || B' 일 경우 'A 또는 B 가 ...' 라는 뜻이 되며, A,B 두 값중 하나만 참이 되더라도 전체의 값은 참이 된다. 두개의 값이 모두 거짓일 경우 전체의 값이 거짓이 된다.
&&(and)
&& 연산자는 '그리고' 이란 뜻으로, 'A && B' 일 경우 'A 그리고 B 가 ...' 라는 뜻이 되며, A,B 두 값중 하나만 거짓이 되더라도 전체의 값은 거짓이 된다. 두개의 값이 모두 참일 경우 전체의 값이 참이 된다.
그리고 이 두개의 연산자는 이런 비교의 경우가 아닌, 펄 문장들을 결합시키는 역할 또한 합니다. 이때 '||'연산자는 '그렇지 않다면..' 이 되고 '&&' 연산자는 '그렇다면..' 으로 해석될수 있다.
$value > 5 || print "값이 너무 작습니다.";
$value < 5 && print "값이 너무 작습니다.";
이 두 문장 모두 같은 역할을 하는데,
위의 예는 'value의 값이 5보다 크지 않다면...' 이 되고,
아래의 예는 'value의 값이 5보다 작다면....' 이 된다.
3.2.2 제어문(Flow Control)
Perl 에서 쓰이는 대표적인 제어문은 while, foreach, if등이 있고, 그 외에 until, for, unless등이 있다.
3.2.2.1 while
while()은 ()내의 조건문이 거짓이 될때 까지 문장을 반복 수행한다.
$n=100;
while ($n > 0) {
print "$n\n";
}
3.2.2.2 foreach
foreach 문은 배열을 처리할때, 아주 유용하다. foreach 다음에 오는 배열이 널값(아무런 값도 없을때)일때 까지 문장을 수행한다.
@name = ("Kim", "young", "soo");
foreach $temp(@name) {
print "$temp\n";
}
위 문장에서 $temp 변수는 처음에는 'kim'의 값을 가지며, 두번째는 'young'의 값을, 세번째는 'soo'의 값을 가지며, 네번째에서 $temp는 널값을 가지게 되므로 루프를 종료하게 된다.
@name 배열 값의 개수가 1000개라 하더라도, 위 3줄의 예로써 모든 @name 의 값을 출력할수 있다.
3.2.2.3 if... elsif.... else
if 문은 '만약 조건문이 참이라면' 의 뜻이 된다.
if... elseif.... else 가 모두 쓰인 상태라면, if(만약 이것이 참이라면...) elsif(그렇지 않다면....) else(위의 두 개 모두 아니라면...) 이런 식으로 쓰인다.
$temp = 5;
if ($temp == 5) { print "temp is $temp"; }
elsif ($temp == 3) { print "temp is $temp"; }
else { print "Not found temp"; }
3.2.2.4 조건부 표현 (Conditional Expression)
그럼 여기서 펄에서 쓰이는 관계형 연산자에 대해서 알아보겠다.
같 음 == eq
다 름 != ne
부호를 가지는 다름 <=> cmp
보다 큼 > gt
보다 크거나 같음 >= ge
보다 작음 < lt
보다 작거나 같음 <= le
3.2.3 파일 제어
여기서는 펄에서 파일의 접근에 대한 방법을 알아 보겠다. 펄에서 파일을 열기 위해 쓰는 명령어는 open()이 있다. 물론 사용하신 다음에는 close()로 닫아야한다. 여기선 텍스트 파일에 대한 이야기를 하겠지만, dbm 파일을 이용할수 있는 dbmopen()과 dbmclose()도 있다.
3.2.3.1 open(), close()
open() 함수는 새로운 파일 핸들을 어떤 파일에 연계시키기 위해서 사용한다.
open(FILE, "> /etc/guest.txt");
close(FILE);
open(FILE, ">> /etc/file.log");
close(FILE);
이렇게 사용 될수 있다. 위 예에서 'FILE'은 새로운 파일 핸들이고, '.../filename'은 사용 할려고 하는 파일명이다.
그리고 `` '' 안의 파일명 앞에 여러가지 기호가 쓰이는데, 이것은 파일에 접근 방법을 지정할수 있다.
'<' 읽기를 위한 파일 열기(기본값)
'>' 쓰기를 위한 파일 열기
'>>' 이어쓰기를 위한 파일 열기
'+C<' 읽기와 쓰기를 위한 파일 열기
'+>' 읽기와 쓰기를 위한 파일 열기
'|' (파일 이름 앞) 파일을 명령(펄에서 파일로 전송)처럼 다룬다
'|' (파일 이름 뒤) 파일을 명령(파일에서 펄로 전송)처럼 다룬다
open() 함수로 파일을 열었으면, 반드시 close() 함수로 닫아야 한다. 그리고 open (파일핸들, ``파일제어기호 파일명(경로포함)''); 에서 파일제어기호 와 파일명에는 콤마등의 기호가 없다.
3.3. 정규표현, 패턴매칭
3.3.1 정규 표현(Regular expression)
정규 표현(regular expression)은 일반화된 문자열을 설명하기 위한 규칙의 집합이다.
어떤 문자열이 정규 표현의 규칙을 따르고 있다면, 그 정규 표현은 그 문자열을 매치(match)한다고 한다.
예를 들면, 정규표현 'b.'는 문자열 'observe, body, abc'와는 매치를 이루고, 'b, Bell, Bob' 와는 매치하지 않다. 이 표현 'b.'는 '소문자 b가 문자열 내에 있어야 하며, 다른 문자가 뒤에 한 개 이상 있어야 하기(단,\n는 예외)' 때문이다.
펄에서는 많은 정규 표현들이 있는데, 그것들을 정리해 보았다.
3.3.1.1 정규표현 어써션(Assertions)
기호 매치 영역 매치 매치함 매치안함
---------------------------------------------------------------------------- '^' 문자열의 시작부분 ^fool foolish tomfoolery
'$' 문자열의 끝 부분 fool$ April fool foolish
'\b' 단어 영역 be\bside be side beside
'\B' 비단어 영역 be\Bside beside be side
3.3.1.2 정규표현 아톰 퀀티파이어(Quantifiers)
기호 매치 영역 예 매치함 매치안함
---------------------------------------------------------------------
'*' 없거나 하나 이상 ab*c ac, abc abb
'+' 하나 이상 ab+c abc ac
'?' 없거나 하나 ab?c ac, abc abbc
'{n}' n개 만큼 ab{2}c abbc abbbc
'{n, }' 적어도 n개 만큼 ab{2,}c abbc, abbbc abc
'{nm}' 적어도 n개, 많아야 m개 ab{2,3}c abbc, abbbc abbbbc
3.3.1.4 특수문자(Special Characters)
기호 매치 영역 예 매치함 매치안함
----------------------------------------------------------------------------
'\d' 숫자 b\dd b4d bab
'\D' 비숫자 b\Dd bad b4d
'\n' 새 라인(줄바꿈)
'\r' 캐리지 리턴
'\t' 탭
'\f' 폼 피드
'\s' 공백 문자
'\S' 비공백 문자
'\w' 알파벳이나 숫자 a\wb a2b a^b
'\W' 알파벳이나 숫자를 제외한 문자 a\Wb aa^b aabb
3.3.2 매 칭(Matching)
위의 표현들은 펄 프로그래밍시 매우 많이, 그리고 매우 유용하게 쓰인다. 보통 매치 연산자는 두개의 '/'로 둘러싸인 정규표현으로 구성된다. 그리고 특수연산자 '=~'는 첫번째 값과 두번째 값이 매치하는지에 따라 참과 거짓으로 평가 한다.
예) $filename =~ /dat$/ && die "Can't use. dat files. \n";
이 예의 뜻은 만약 $filename의 끝 문자열이 'dat'로 끝나지 않는다면 'die ~ '해라라는 뜻이 된다.
연산자 '!~'는 '=~'의 반대개념이다.
예) $ENV{'PATH'} !~ /perl/ && warn "Not sure if perl is in your path...";
perl의 경로가 $ENV{'PATH'}와 매치되지 않는다면.... 의 뜻이 된다.
3.3.3 대 치(Substitution)
대치는 쉬우면서 강력한 기능을 제공한다. 대치 연산자는 '/.../.../' 를 사용한다. 첫번째와 두번째 '/'에 대치될 패턴이 오게되고, 두번째와 세번째 '/'에 대치할 패턴이 오게 된다.
예)$name = "Kimyoungsoo";
$name =~ s/Kim/Unicorn/;
$name의 'Kim'을 'Unicorn'으로 대치하게 된다.
4. 문자열 처리에 관한 Perl 사용 예제
본 장에서는 perl을 이용 하여 문자열을 처리하는 방법들에 대해서 자세히 알아 본다.
4.1. 다른 프로그램을 실행시켜 출력내용 읽기
다른 프로그램을 실행시키고 그 출력 내용을 읽는 방법은 여러가지가 있다.
4.1.1. Backtick
역따옴표(`...`)는 그 안에 들어있는 내용을 shell을 통해 실행하고, 표준출력(STDOUT)으로 출력되는 내용을 반환한다. 따라서,
$content = `cat /etc/passwd`;
는 passwd 파일을 간단히 읽어들인다.
물론, 단순한 파일을 읽는데는 직접 open을 해서 읽어들이는 것이 좋다. 역따옴표를 사용하게 되면, 그 내부의 프로그램을 실행하기 위해서 별도의 프로세스가 생성되어야 하기 때문에 그만큼 시스템의 자원을 소모하게된다.
4.1.2. open 함수를 이용하는 방법
open 함수에서 파일이름분에 > 대신 | (파이프)기호를 뒤에 붙이면 해당 파일이름을 실행시키고 그 출력내용을 읽을 수 있다.
#!/usr/bin/perl
# prog.pl
print "$$\n";
warn "Exiting\n";
#!/usr/bin/perl
# opener.pl
$p = open(RUN, "./prog.pl |") || die $!;
@out = <RUN>;
close RUN;
print "opener: $p\n";
print map { "opener: $_" } @out;
opener.pl에서 prog.pl을 "./prog.pl |"와 같이 하여 열었다.(open)
그 후 일반 파일 읽기에서 하는 것처럼 <RUN>를 이용하여 출력내용을 읽는다. 참고로, open 함수가 반환하는 것은, 프로그램이 제대로 실행되었을 때, 새로운 프로세스의 프로세스 아이디이다.
만일 이 프로그램이 오랜시간동안 살아 있는 것(웹서버처럼)이라면 close를 하지 않을 경우 문제가 될 수 있다. 예를 들어, open은 계속해야 하는데, close를 해 주지 않는다면, 해당 프로세스가 죽지않고 계속 남아서 시스템자원을 차지하고 있어서 결국 시스템이 멈추는 상황이 발생할 수도 있게된다.
따라서 반드시 close를 해 주어야 합니다.
만일 표준에러(STDERR)까지 같이 읽어야 한다면
#!/usr/bin/perl
# prog.pl
print "$$\n";
warn "Exiting\n";
#!/usr/bin/perl
# opener.pl
$p = open(RUN, "./prog.pl 2>&1 |") || die $!;
@out = <RUN>;
close RUN; #
print "opener: $p\n";
print map { "opener: $_" } @out;
와 같이 2>&1를 추가합니다.
4.2. 문자열 안에 실행문 넣기
문자열 내에 실행문을 직접 삽입할 수 있다.
보통의 print 문에서는
print "수익대 비용 차액 : " . ($income - $outcome) . "\n";
와 같은 식이다.
만일 print문 안에 이런 계산식이 많이 들어가야 한다면, 조금 귀찮을 수도 있다. 이런 경우 문자열 내에 계산식을 직접 삽입하는 것이 간편하겠다고 생각되면 다음과 같은 형식을 사용한다.
print "수익대 비용 차액 : ${ \($income - $outcome) }\n";
문자열 내에서 ${스칼라변수리퍼런스}는 그 리퍼런스가 가리키는 변수의 값을 나타낸다. 예를 들면 다음과 같은 경우이다.
$var = 365;
$rvar = \$var;
print "var = ${$rvar}\n"; # 이 경우라면 "var = $$rvar\n"도 같은 결과이지만, 설명을 위해 위 ${ \($income - $outcome) }는 ${ }의 안에 \($income - $outcome)을 가지고 있다. 목록(list)을 나타내는 괄호앞에 \(백슬래쉬)를 두면 목록의 각 요소에 대해 리퍼런스를 만드는 것과 같다. 그래서
$what = \( $a, $b, $c )
는
$what = ( \$a, \$b, \$c );
와 같게 되고, 스칼라 문맥에서 목록이 사용되면 맨 뒤의 것만 사용되게 되어 맨 마지막 요소인 $income - $outcome의 결과 값에 대한 리퍼런스가 ${ }와 함께 사용되어 계산값이 출력 되는 것이다. 맨 마지막 값이 사용되는 것을 직접 확인하려면 괄호의 앞부분에 다른 값을 넣고 컴마로 구분해 보면 된다.
print "수익대 비용 비율과 차액 : ${ \($outcome / $income, $income - $outcome) }\n";
이 경우 $outcome / $income 부분은 사라져 알 수 없다.
배열 역시 문자열 안에 넣을 수 있다. 문자열 내에서 배열리퍼런스의 사용은 @{배열의리퍼런스}의 형식이다. 물론 "@$array_ref"와 같은 맥락이다.
print "수익대 비용 비율과 차액 : @{ [ $outcome / $income, $income - $outcome ] }\n";
이 경우에는 \() 대신 []를 사용한다. []는 이름없는 배열(anonymous array)의 레퍼런스를 나타낸다.
4.3. 첫 글자는 대문자로, 나머지는 소문자로 바꾸기
영문 문자열의 첫글자만 대문자로 만들고, 나머지는 소문자로 만드는 법이다. 사용자의 입력을 받았을 때, 다양한 사용자의 입력 습관을 무시하고, 적절히 바꾸는 방법중 하나라고 생각된다.
쌍따옴표를 사용할 때, \n이 개행문자(new line)로, \t가 탭문자로 사용된다는 것을 알 것이다. 이 외에도 다양한 특수문자들이 있다. 그 중에는
\U : 문자열의 끝 또는 \E가 나올 때 까지 모든 문자를 대문자로 바꿈
\L : 문자열의 끝 또는 \E가 나올 때 까지 모든 문자를 소문자로 바꿈
\u : 바로 다음의 문자만을 대문자로 바꿈
\l : 바로 다음의 문자만을 소문자로 바꿈
과 같은 특수문자들이 있다. 이 특수문자들을 이용하면, 원하는 바를 이룰 수 있다.
$str = "what A wAnderful Day!";
$badstr = "\u$str"; # What A wAnderful Day!
$goodstr = "\u\L$str"; # What a wanderful day!
$badstr의 경우는 굳이 설명이 필요없지만, 자칫하면 위와 같이 생각할 수 있다. (사실 프로그래머는 사용자의 입력을 ``예단''하는 수가 상당히 많다)
$goodstr은 일단 \L로 모든 문자들을 소문자로 바꾼후에 \u로 첫 문자만 대문자로 바꿔주었다.
4.4. 문자열의 맨 처음과 맨 뒤의 공백 없애기
파일에서 텍스트를 읽어 들이거나 사용자의 입력을 받을 때 때로 반드시 처리해 주어야 하는 것이 문자열의 맨 앞이나 맨 뒤의 공백문자들을 지우는 것이다. 다음의
$str =~ s/^\s+|\s+$//g;
한 줄이면 맨 앞의 공백과 맨 뒤의 공백을 한 번에 없앨 수 있다. 물론 습관에 따라
$str =~ s/^\s+//;
$str =~ s/\s+$//;
해도 무관하다.
4.5. 한글을 깨끗하게 자르기
어떤 데이터를 받거나 얻은 후, 데이터베이스에 넣거나 웹상에 표출할 때, 그 것이 너무 길어서 데이터베이스에서 받아들이지 않거나, 웹 페이지의 모양이 이상한 경우가 있다.
이 때는 받은 데이터의 일부를 불가피하게 잘라내야 하는 데, 영문이라면 큰 문제가 없지만 한글의 경우에는 문제가 된다.
이유는, 한글 한 글자를 나타내는 데 2바이트를 사용하기 때문이다.
예를 들어 "우리나라 만세"를 10 바이트로 잘라내려면 "우리나라" 8바이트, 공백 1바이트, '만'자의 앞부분 1바이트가 남아서 출력될 때는 깨진 글자가 보여지게 된다.
이 때는 다음과 같은 코드를 사용하시면 된다.
if( length($text) > 60 ) {
$text = substr($text,0,60);
$text =~ s/(([\x80-\xff].)*)[\x80-\xff]?$/$1/;
}
일단 문자열을 원하는 길이만큼만 남기고 잘라낸다(substr).
남은 문자열의 끝부분에 대해서 정규식을 적용하는데, 한글의 첫 바이트는 항상 \x7f 보다 크므로 [\x80-\xff]., 즉 한글의 첫바이트일 수 있는 [\x80-\xff] 한 바이트와 '.' 한 바이트가 합쳐진 것을 '*' 하면 한글 0 또는 그 이상의 글자가 된다. 그리고 한글의 첫바이트인 [\x80-\xff] 하나만 남는다면 온전한 한글 문자열부분인 (([\x80-\xff].)*) 부분이 $1 이라는 변수에 남아있으므로 그것을 일치된 전체 텍스트에 대해서 치환한다.
4.6. 날짜 패턴검사와 후참조(Backreference)
날짜 입력값의 패턴 검사를 해 보겠다.
날짜 입력값이 yyyy-mm-dd나 yyyy/mm/dd는 인정하고 그 외의 패턴은 인정하지 않는다고 한다면 다음과 같이 간단한 정규식을 사용하면 된다.
if( $dateinput =~ m|^\d{4}([-/])\d{1,2}\1\d{1,2}$| ) {
print "Good\n";
} else {
print "Not good\n";
}
여기서 상세한 날짜 범위에 대한 검증은 생략하겠다.
얼핏생각하면 패턴 검사를
$dateinput =~ m|^\d{4}([-/])\d{1,2}[-/]\d{1,2}$|
와 같이 하면 될 것이라고 생각할 수 있다. 하지만, 이 경우는 yyyy-mm/dd나 yyyy/mm-dd도 옳은 것으로 인정해 주기 때문에, 썩 좋은 해법이라고 보기 어렵다. 그래서
$dateinput =~ m|^\d{4}-\d{1,2}-\d{1,2}$| or $dateinput =~ m|^\d{4}/\d{1,2}/\d{1,2}$|
라고 할 수도 있으나, 그 보다는 맨 위의
$dateinput =~ m|^\d{4}([-/])\d{1,2}\1\d{1,2}$|
가 훨씬 간결하다. \1은 후참조(backreference)라고 한다. 정규식 패턴 내에서 앞의 괄호가 일치할 때, 패턴 내부에서 다시 참조할 수 있도록 별도로 저장해둔 내용을 참조하는 것이다.
이 것은 패턴 외부에서 $1, $2, $3...를 사용하는 것과는 달리, 패턴 내부에서 사용한다. 그래서 년과 월의 사이에 사용되는 구분자가 똑같이 월과 일의 사이에서 사용된 경우에만 올바른 패턴으로 결정할 수 있게 된다.
후참조 역시 $1, $2, $3...와 같이 \1, \2, \3...로 사용된다. 만일 날짜 + 시간 패턴을 검사한다면, 다음과 같이 사용할 수 있다.
m|^\d{4}([-/])\d{1,2}\1\d{1,2} \d\d([-:])\d\d\2\d\d$|
5. 결 론
Perl 을 사용 하는 많은 사람들의 인식 속에는 perl 은 그저 인터넷 분야에서 널리 활용되고 있다. 그래서 “오직 CGI 만을 위한 언어다”라고 생각 할지도 모른다. 펄을 창시한 래리 월이 TMTOWTDI, 즉 “There is more than one way to do it" 이라는 슬로건을 내 걸었듯이, 인터넷 프로그래밍은 물론이고 유닉스 시스템 관리, 네트워크, 기타 컴퓨터 분야의 전반에 걸쳐 활용될 수 있는 Perl 의 풍부한 활용성을 알리고자 이렇게 이글을 쓰게 되었다. 특히 문자열 처리에 관한한 최고임을 자부하는 perl 을 사용자 입맛에 맞게 사용 하였으면 한다.
참고문헌
[1] 톰 크리스잔센, 네이던 토킹턴 저, 안계용 역, “Perl Cookbook" 2000.
[2] "http://www.perlmania.or.kr" 문서 모음 게시판
[3] “http://www.perl.or.kr” About perl, Tips 게시판
[4] “http://www.softbrain.co.kr/perl/” Perl 이야기 게시판
[5] “http://www.activestate.com/” 윈도우용 perl 자료
[6] “http://perl.sshel.com/” Docs 게시판
[7] “http://seoul.pm.org/”
다음의 기사는 월간 프로그램 세계 1997년 6월호 특집기사로 실렸던 것입니다. 특집기사는 총 3부로 이루어져 있는데, 1부는 Perl의 소개, 2부는 Perl의 문법, 3부는 Perl을 이용한 CGI 예제 프로그램 작성으로 구성되어 있습니다. 제가 맡은 부분은 2부였고 총 분량의 절반에 해당합니다. 1부와 3부는 김대신(웹데이타뱅크)씨가 맡으셨습니다. 다음의 글은 제가 초안으로 작성한 텍스트 파일을 HTML문서로 약간 손을 본 것입니다.
차례
- 소개
- Perl의 문법
- 변수(Variable)
- 수치(number)
- 문자열(string)
- scalar variable
- vector variable
- 특수한 변수(Special variable)
- 레퍼런스(reference)
- 배열의 배열, 해시의 배열, 배열의 해시
- 수치(number)
- 식(Expression)
- 기본 연산자
- 추가 연산자
- 기본 입출력 연산자
- 비교 연산자
- 기본 연산자
- Control Structure(제어 구조)
- if
- unless
- while/until
- for
- foreach
- do/while, do/until
- goto
- next, last, redo
- if
- 서브루틴(Subroutine)
- 정의와 사용
- my와 local
- 정의와 사용
- 패턴 매칭(Pattern Matching)
- 정규 표현식(regular expression)
- match, substitute, translate
- 문자열 pattern 조작
소개
펄은 임의의 텍스트 파일들을 검색하고 그 파일에서 정보를 추출하며, 그 정보에 따라 보고서를 작성하는데 최적화된 언어이다. 또한 많은 시스템 관리 작업에 좋은 언어이다. 이 언어는 실용적일 수 있도록 만들어졌기 때문에 크기가 아주 작거나 아름답게 만들어지지지 는 않았지만 사용하기 쉽고 효율적이며 완벽하다고 말할 수 있다. 펄은 C와 sed와 awk와 sh 등의 사람들이 친숙하게 생각하는 언어들의 장점을 모아서 만든 것이기 때문에 배우기 어렵지 않다. 표현식은 거의 C와 비슷하며, 다른 유닉스 유틸리티와는 달리 용량 등의 제한이 없다. 주로 text를 처리하는데 뛰어난 능력을 보여주지만, 바이너리 자료를 다루는데 있어서도 데이터베이스를 만드는 기능이 있을 정도이다. sed나 awk를 사용하는데 문제가 발생한다면 C로 짜는 대신에 펄로 작성하면 스크립트를 거의 바꾸지 않아도 된다.
2부. 펄의 문법
Perl의 저자, Larry Wall이 말했던 것처럼 Perl은 한 가지 일을 하기 위해 10가지 방법을 제시하는 언어이다. 어떻게(how) 할 것인지가 중요한 게 아니라 무엇을(what) 할 것인지를 먼저 생각하고 프로그래밍을 해야 할 것이다. 그러나 다양한 표현 방식들을 많이 익히면 익힐수록 프로그래머에게 더 많은 가능성이 열리는 셈이다. 다음의 글은 독자들이 기본적인 C프로그래밍을 이해할 수 있고 유닉스의 shell기반의 작업들에 무지하지 않음을 가정하고 쓰여진 것이다. 그러나 가능하면 초보자들도 이해할 수 있도록 쉬운 표현과 예제를 이용하였다. 다음의 설명은 Perl 5를 기준으로 쓰여졌으나, Perl 4에서도 무난하게 사용 가능할 것이다.
1. 변수(Variable)
기본적으로 Perl은 변수에 대해, C와 같이 강력한 type checking을 하지 않는다. 다시 말해서 '값이 어떤 방식으로 저장되어있는가'는 '어떻게 사용할 것인가'와는 별개의 문제라는 것이다. C프로그래밍에 익숙한 사용자라면 int c = 'x'; 으로 정의된 변수 c가 문자형으로도 정수형으로도 사용 가능한 것을 떠올릴 수 있을 것이다. 그런 것을 확장해서 생각해보면 Perl의 변수들은 여러 형태로 사용할 수 있다. 그것은 Perl 인터프리터(interpreter;해석기)가 내부적으로 자동 변환해 주기 때문에 가능한 것이다.
1) 수치(number)
Perl에는 정수형 수치는 존재하지 않는다. 모든 수치는 double-precision floating point형 수치로 존재한다. 다음과 같은 형태의 수치는 float literal(소수형 상수)이다.
111.25 23.25e91 -12.5e29 -12e-34 3.2E-23
다음과 같은 형태의 수치는 integer-literal(정수형 상수)이다. 숫자 앞에 0이나 0x를 붙여쓰게 되면 각각 8진수와 16진수를 의미하게 된다.
342 -2423 0377 -0xf0
2) 문자열(string)
문자열은 이름이 뜻하듯이 연속된 문자들의 순열이다. 일반적인 ASCII 문자는 128개로 표현이 되지만, Perl에서는 특수문자가 표현되어야 하기 때문에 extended ASCII를 지원하여 문자열 내부의 각각의 글자는 0에서 255까지의 ASCII값을 가지게 된다. 문자열은 여러 형태의 두 quotation mark로 감싸서 표현할 수 있는데, 대표적인 케이스는 ''(single-quote)와 ""(double-quote), ``(back-quote)이다. single-quote사이에 들어가는 문자열은 interpolation을 하지 않고, double-quote사이에 들어가는 문자열은 interpolation을 하게 되는 차이점이 있다. 그러면 interpolation이란 무엇인가 예를 보면서 이해하도록 하자.
$str1 = 'hello\n\neveryone'; $str2 = "hello\n\neveryone"; print $str1 . "\n"; print $str2 . "\n";
두 문장은 quotation mark가 다르다는 점을 제외하고는 차이점이 없다. 그런데 출력결과는 다음과 같이 다르게 된다.
hello\n\neveryone # 이것은 str1의 출력 결과이다. hello # 이것은 str2의 출력 결과이다. everyone
위와 같은 escape character뿐만 아니라 이미 정의되어진 변수들을 문자열 안에서 사용하는 경우에도 interpolation의 가능 여부에 따라 그 변수가 해석되기도 하고 그렇지 않기도 한다.
$str0 = "I'm a boy."; $str1 = 'hello $str0'; $str2 = "hello $str0"; print $str1 . "\n"; print $str2 . "\n";
결과는 다음과 같다.
hello $str0 # str1의 출력 결과 hello I'm a boy. # str2의 출력 결과
single-quote 문자열에서 single-quote(')를 표현하는 방법은 backslash뒤에 single-quote를 쓰는 것이다.
print 'I\'m a boy.'; # 출력 결과 I'm a boy.
다음은 double-quote안에서 특별한 효과를 가지는 escape character의 종류와 그 의미에 관한 표이다.
\n | newline 다음 줄로 |
\r | return 줄의 맨 앞으로 |
\t | tab 탭 |
\f | formfeed |
\b | backspace |
\v | vertical tab |
\a | alarm bell |
\e | ESC |
\073 | octal value 8진수(여기서는 73(8) = 7*8+3(10) = 59(10)) |
\x7f | hexadecimal value 16진수(여기서는 7f(16) = 7*16+15(10) = 127(10)) |
\cC | 제어문자 C |
\\ | backslash |
\" | double-quote |
\l | 다음 글자를 대문자로 |
\L | 다음 글자를 소문자로 |
\u | 뒤에 오는 모든 글자를 대문자로 |
\U | 뒤에 오는 모든 글자를 소문자로 |
\Q | 뒤에 오는 모든 특수문자에(non-alphanumeric) \를 붙여서 |
\E | \U, \L, \Q의 끝을 표시 |
마지막으로 back-quote가 있는데, 이것은 문자열이라기보다는 문자열을 shell에서 실행한 결과 값을 저장하게 된다. back-quote는 back-tick이라고 부르기도 한다.
$time_of_today = `date`; print $time_of_today; # 출력 결과 Mon Apr 27 20:59:04 KST 1998
위와 같은 코드에서는 Perl이 date라는 프로그램을 실행하여 나오는 값이 변수에 저장되게 된다. back-quote문자열에서는 double-quote문자열에서와 마찬가지로 interpolation이 되어진다. 다음 코드는 shell에서 "ls | grep doc"를 실행한 것과 같은 결과를 출력하는 것이다.
$keyword = 'doc'; print `ls | grep $keyword`;
출력결과
jdbcprog.doc vi.doc xprog.doc
다음의 표는 quotation의 여러 가지 방법에 대해 정리한 것이다.
관습적 사용 | 일반적 사용 | 의미 | interpolation여부 |
'' | q// | 문자열상수 | x |
"" | qq// | 문자열상수 | o |
`` | qx// | 실행명령 | o |
() | qw// | list | x |
// | m// | pattern match | o |
s/// | s/// | substitution | o |
y/// | tr/// | translation | x |
일반적 사용에서 q 다음에 나와있는 backslash(/)는 다른 문자로 바꾸어 사용할 수 있다. 자세한 것은 뒤에 나오는 소단원을 참고하도록 하고 간단한 예제를 통해 그 사용법을 알아보도록 하자.
$abc = q$I'm a boy. You're a girl.$; $def = qq#Hello,\neveryone#; $ghi = q& while (test != 0) { i++; printf("%d", i); } &;
변수 abc는 $를 single-quote(') 대신으로 사용하였고, def는 #을 double-quote(") 대신으로 사용하였다. ghi의 경우, multi-line string을 지정하는데, single-quote(') 대신에 &기호를 사용하였다.
3) scalar variable
scalar variable은 하나의 값을 가지는 변수를 뜻하고 다음에서 살펴 볼 vector variable과는 상대되는 표현이다. scalar variable은 앞에서 보았던 수치나 문자열과 같은 값을 오로지 하나만 가지는 값이다. 일반적으로 scalar variable은 $기호를 변수 이름 앞에 붙여 vector variable과 구분한다. Perl에서는 C에서와 마찬가지로 변수의 이름은 대소문자가 달라지면 전혀 다른 변수로 인식된다. 다음의 변수들은 모두 다르게 구분되는 scalar 변수들이다.
$abc $ABC $account_number_of_bank1 $account_number_of_bank2 $xyz001
scalar variable에 대해서 산술 연산자 등의 일반적인 연산자를 수행할 수 있다.
4) vector variable
vector variable은 크게 세 가지 종류로 나눌 수 있는데, array와 associative array가 그것이다. Perl에서는 array는 list라는 이름으로, associative array는 hash라는 이름으로 일컬어지기도 한다.
list는 변수 이름 앞에 @표시를 하여 구분하고, hash는 %기호를 사용한다. 다음의 변수들은 각각 list와 hash의 예이다.
@certain_list = ("abc", "def", "123"); @quoted_list = qw( 1, 2, 3, 4, 5, 6, 7, 8, ); %a_special_hash = (('test', "hello"), ('verify', 123)); %list_like_hash = ("red", 0xf00, "green", 0x0f0, "blue", 0x00f); %another_special_hash = ( red => 0xf00, green => 0xf00, blue => 0xf00, );
vector variable은 다른 vector 형태의 variable을 포함하여 지정할 수 있다.
@next_list = ("abc", "def", "123", @previous_list); %general_hash = (%a_special_hash, %list_like_hash);
vector variable은 scalar variable과는 달리 주의해서 사용해야 한다. quotation을 하는 경우와 아닌 경우가 다르고, 그 변수를 vector차원에서 다루는 경우와 그의 원소인 scalar차원에서 다루는 경우가 다르다. 다음 예제를 참고하여 차이점을 알아보도록 하자.
print "@certain_list"; print @certain_list;
두 문장의 출력 결과는 다음과 같다. print는 첫 번째의 경우를 list를 quotation한 것으로 간주하여 space를 출력해주고, 두 번째의 경우를 list로 간주하여 space없이 붙여서 출력해준다.
abc def 123 abcdef123
다음의 hash에 관련된 코드는 list와 같을까?
print "%a_special_hash"; print %a_special_hash;
list와는 달리 hash의 경우 interpolation이 일어나지 않음을 알 수 있다.
testhelloverify123 %a_special_hash
일반적인 의미에서의 "hash"란 두개의 값을 한 쌍으로 하여 저장하는 방식을 의미하는데, 여기서도 마찬가지로 hash는 key와 value를 구분하여 사용하게 된다. 앞에서 제시한 예에서는 "test"와 "verify", "red", "green", "blue"등이 key가 되고, "hello", 123, 0xf00, 0x0f0, 0x00f등이 그 key로 찾을 수 있는 value가 된다.
print $a_special_hash{'test'}; $key = 'verify'; print $a_special_hash{$key};
list의 경우에는 각각의 value를 처리하기 위해 index(subscript)를 사용한다. 이런 경우에는 list 중의 하나의 scalar variable에 대한 연산이 된다. C에서의 index와는 달리 Perl에서는 ..를 이용해서 range(범위)에 대한 연산도 가능하다.
print $colors[0]; $id_list[3]++; print @month[8..11]; # @month[8..11]는 @month[8, 9, 10, 11]과 같다. $b = $namelist[$number];
list 단위의 연산도 가능하다. 대표적인 것으로 list assignment가 있다.
($a, $b, $c) = (3, 4, 5); @array = ('a', 'b', 'c', 'd');
vector variable 중에는 어떤 값도 가지지 않는 변수가 존재할 수 있는데, 이런 것을 null list라고 하며 ()로 표현한다.
@a = ();
특히 null list는 0, "0"과 함께 조건문에서 false expression을 의미하는 것으로 사용된다.
list의 마지막 인덱스를 알기 위해서는 $#기호를 변수이름 앞에 붙이면 된다. list 중에 포함되어 있는 원소의 개수를 알기 위해서는 scalar()함수를 사용한다. 인덱스는 0부터 매겨지기 때문에 일반적으로 $#list + 1 == scalar(@list)가 성립한다.
5) 특수한 변수(Special variable)
a) Regular expression variable
우선 $*라는 특수변수가 multi-line mode를 위해 Perl 4까지 사용되었는데, Perl 5부터는 regular expression을 사용할 때 modifier를 사용하는 방식으로 바뀌면서 더 이상 사용되지 않게 되었다. 이것은 패턴 매칭의 regular expression부분을 참고하도록 한다.
special variable들은 기억하기 쉽도록 mnemonic name을 가지고 있는데, 그러한 기능을 사용하려면 Perl 프로그램의 앞부분에서
use English;
로 지정해주어야 한다. 다음은 각각의 special variable과 그 역할에 대해 정리해놓은 표이다. pattern match와 관련된 변수들은 지역변수(local special variable)임을 기억하도록 하자.
$digit | $1, $2, $3, ... | 패턴 바깥에서, 매치(match)가 일어난 괄호 안의 substring을 지정할 때 사용되는 변수로 왼쪽 괄호'('의 순서대로 digit위치에 숫자가 사용된다. |
\digit | \1, \2, \3, ... | 패턴 안에서 사용되는 $digit형 변수와 같은 역할을 한다. |
$& | $MATCH | 가장 마지막에 매치가 일어난 substring을 지정하는 변수 |
$` | $PREMATCH | $MATCH 앞쪽의 substring을 지정하는 변수 |
$' | $POSTMATCH | $MATCH 뒤쪽의 substring을 지정하는 변수 |
$+ | $LAST_PAREN_MATCH | 패턴 내의 가장 뒤쪽에 위치한 괄호 안의 substring을 지정하는 변수로 alternative matching 사용시 편리하다. |
$* | $MULTILINE_MATCHING | default로 0의 값을 가지면서 single-line matching mode를 지정하고, 1의 값을 가지게 되면 multi-line matching mode를 지정한다. |
여기서 제시된 여러 special variable 중에서 $*만이 값을 바꿀 수 있는 변수이고, 나머지 변수들은 모두 read-only의 속성을 가지는 변수이다.
다음은 local special variable을 사용한 예제로서, default input variable인 $_에서 pattern matching을 수행하고 match가 일어난 부분과 바로 앞부분, 바로 뒷부분이 $`, $&, $'에 저장됨을 보여준다.
$_ = 'abcdefghi'; /def/; print "$`:$&:$'\n";
출력결과는 abc:def:ghi 이다.
다음은 위치상으로 가장 뒤쪽에서 매치가 일어나는 부분은 Revision: 뒤쪽의 문자열이며, 이 문자열은 $+에 저장됨을 알 수 있는 예제이다. pattern matching이 성공적으로 수행되면(match가 일어나면) $rev에 revision값을 저장하게 된다.
/Version: (.*)|Revision: (.*)/ && ($rev = $+);
b) Per-Filehandle variable
Filehanle이란 사용자가 작성한 Perl program과 외부에 존재하는 파일사이의 I/O 연결을 위한 Perl program의 이름이다. Unix의 표준 file-descriptor를 지원하기 위한 filehandle로는 STDIN(표준입력)와 STDOUT(표준출력), STDERR(표준에러출력)가 있다. 이외에도 사용자가 특정한 파일을 다루기 위해 filehandle을 지정할 수 있다. 자세한 내용은 파일조작에 관련된 절을 참고하도록 하자.
method HANDLE EXPR 또는 HANDLE->method(EXPR)과 같은 형식으로 지정한 다음에 다음과 같은 코드를 사용하여 다음의 변수들을 지정 또는 변경할 수 있다. HANDLE위치에는 사용할 filehandle의 이름을 지정하면 되고, EXPR위치에는 현재 filehandle의 속성의 값을 지정할 수 있다. 사용가능한 method는 아래 정리되어 있다.
use FileHandle;
$| | $OUTPUT_FLUSH_NUMBER | autoflush HANDLE EXPR | 값을 0이 아닌 값으로 지정하면, write또는 print문장 후에 fflush(3)함수를 호출하여 현재 선택된 output channel로 출력을 강제한다. |
$% | $FORMAT_PAGE_NUMBER | format_page_nubmer HANDLE EXPR | 현재 선택된 output channel의 현재 page 번호이다. |
$= | $FORMAT_LINES_PER_PAGE | format_lines_per_page HANDLE EXPR | 현재 선택된 output channel의 현재 page 길이(프린트 가능한 line의 수)이다. |
$- | $FORMAT_LINES_LEFT | format_lines_left HANDLE EXPR | 현재 선택된 output channel의 남아있는 line의 수이다. |
$~ | $FORMAT_NAME | format_name HANDLE EXPR | 현재 선택된 output channel의 현재 report format의 이름이다. |
$~ | $FORMAT_TOP_NAME | format_top_name HANDLE EXPR | 현재 선택된 output channel의 현재 top-of-page format의 이름이다. |
다음은 관련된 특수한 변수들의 사용 예제이다.
use FileHandle; use English; print '$| = '."$|\n"; print '$% = '."$%\n"; print '$- = '."$-\n"; print '$~ = '."$~\n"; print '$^ = '."$^ \n"; print '$= = '."$=\n";< print '$= = '."$FORMAT_LINES_PER_PAGE\n"; format_lines_per_page STDOUT 30; print '$= = '."$FORMAT_LINES_PER_PAGE\n"; print '$= = '."$=\n";
특정 filehandle인 HANDLE에 per-filehandle special variable을 지정해주는 예제는 다음과 같다.
select((select(HANDLE), $| = 1, $^ = 'mytop')[0]);
c) Global Special variable
$_ | $ARG | default input string, standard input이나 첫 번째 argument로 넘겨진 파일을 읽을 때 넘겨져 오는 string을 의미한다. |
$. | $INPUT_LINE_NUMBER $NR | 마지막으로 읽힌 filehandle의 현재 input line number이다. 여러 argument로 넘겨진 파일들을 구분하지 않으므로, 다른 파일로 바뀌더라도 line number가 계속 증가하게 된다. |
$/ | $INPUT_RECORD_SEPARATOR $RS | input record를 구분해주는 string을 의미하며 default값은 newline character이다. |
$, | $OUTPUT_FIELD_SEPARATOR $OFS | output field를 구분해주는 string이며, print 연산자에서 사용된다. 일반적으로는 comma(,)가 separator로 사용된다. |
$\ | $OUTPUT_RECORD_SEPARATOR $ORS | output record를 구분해주는 string으로서 보통은 print 연산자가 record뒤에 newline내지는 record separator을 찍어주지 않기 때문에 필요할 경우, 지정하여야 한다. |
$" | $LIST_SEPARATOR | list의 구분자로 사용되는 string을 지정하는 변수이다. 기본 값으로 space가 지정되어 있다. |
$; | $SUBSCRIPT_SEPARATOR $SUBSEP | 다차원 배열을 만들기 위해 list내에서 변수들을 나열할 경우, 그 변수들이 하나의 record로 인식되어야 하는데, 그것을 위해서 list내의 각 변수들을 join하는데 사용하는 변수이다. |
$^L | $FORMAT_FORMFEED format_formfeed HANDLE EXPR |
print할 때, output의 format에서 formfeed로 사용될 string을 지정하는 변수로서, 기본 값은 "\f"이다. |
$: | $FORMAT_LINE_BREAK_CHARACTERS format_line_break_characters HANDLE EXPR |
연속적인 field를 잘라야 하는 경우 그 기준이 되는 character의 집합을 정의하는 변수이다. default로 "\n-", 다시 말해서 newline과 hyphen이 사용된다. |
$^A | $ACCUMULATOR | format line을 위한 write accumulator의 현재 값을 지정하는 변수이다. |
$# | $OFMT | Perl5에서는 사용되지 않는, 이전 버전과의 호환성을 위해 제공되는 변수로, 숫자를 출력하기 위한 output format을 지정하는 변수이다. 초기 값은 %.14g 이다. |
$? | $CHILD_ERROR | 마지막 실행 문에서 return되는 status값이다. 상위 8비트는 child process의 exit value이고, 하위 8비트는 어떤 signal을 받았는지와 core dump가 일어났는지의 여부에 대한 정보를 포함한다. |
$! | $OS_ERROR $ERRNO | error에 관한 정보를 포함하는 변수로서, 사용되는 문맥에 따라 error 번호 또는 error string을 보여주게 된다. |
$@ | $EVAL_ERROR | eval 명령으로부터 발생하는 error message를 담는 변수이다. null로 값이 지정될 경우 성공적으로 실행되었음을 의미한다. |
$$ | $PROCESS_ID $PID | 현재 script를 실행하고 있는 Perl프로그램의 process id(번호)를 지니고 있는 변수이다. |
$< | $REAL_USER_ID $UID | 현재 process의 real user id(uid)를 지닌 변수로서, process를 실행시킨 사용자의 id를 의미한다. |
$> | $EFFECTIVE_USER_ID $EUID | 현재 process의 effective user id(euid)를 지닌 변수로서, process의 원래 소유자의 id를 의미한다. 일반적인 process의 경우, uid와 같은 값을 가지지만, setuid bit가 켜져 있는 실행파일의 경우, uid는 실행자의 id로, euid는 파일 소유자의 것으로 지정된다. |
$( | $REAL_GROUP_ID $GID | 현재 process의 real group id(gid)를 지정하는 변수이다. |
$) | $EFFECTIVE_GROUP_ID $EGID | 현재 process의 effective group id(egid)를 지정하는 변수이다. |
$0 | $PROGRAM_NAME | 현재 실행중인 Perl script의 이름을 지정하는 변수이다. |
$[ | 배열의 첫 번째 원소의 index, 또는 substring의 첫 번째 글자의 index를 지정하는 변수이다. 기본적으로는 0번 원소부터 배열이 시작하지만, 1번 원소부터 배열이 시작하도록 1로 값을 바꿀 수 있다. | |
$] | $PERL_VERSION | Perl의 version과 patch level을 알려주는 변수로서, 5.004는 5번째 version에 4번째 patch level임을 의미한다. |
$^D | $DEBUGGING | debugging flag(-D switch)가 켜져 있는지에 관한 정보를 담고 있는 변수이다. |
$^F | $SYSTEM_FD_MAX | 현재 open되어 사용되고 있는 file descriptor의 최대 번호를 지정하는 변수이다. 보통 프로그램이 시작하게 되면 자동으로 0, 1, 2번이 STDIN, STDOUT, STDERR로 설정된다. |
$^H | Perl compiler의 내부 컴파일 힌트에 관한 정보를 담는 변수이다. | |
$^I | $INPLACE_EDIT | inplace-edit extension의 값을 저장하는 변수이다. 이 값은 -i switch에 의해 지정된다. |
$^O | $OSNAME | 현재 사용되고 있는 Operating System의 이름을 저장하는 변수이다. |
$^P | $PERLDB | Perl debugger가 자신을 debug하지 않도록 꺼주는 내부 flag 변수이다. |
$^T | $BASETIME | script가 실행되기 시작한 시각을 지정하는 변수이다. Unix system에서는 70년 1월 1일 0시 0분 0초(epoch)로부터의, 초단위 시간이다. |
$^W | $WARNING | -w switch에 의해 지정되는 warning(경고) 여부의 값을 저장하는 변수이다. |
$^X | $EXECUTABLE_NAME | Perl 바이너리가 실행 시에 가지게 되는 이름을 지정하는 변수이다. |
$ARGV | argument로 넘겨져 들어온 파일의 이름이다. |
다음의 예제는 동일한 의미를 가지는 코드이다.
$foo{$a, $b, $c} $foo{join($;, $a, $b, $c)}
d) Global special array
@ARGV | 넘겨져 들어온 command-line argument의 list를 저장하는 변수이다. |
@INC | do, require, use등의 operator를 사용할 때 필요한 Perl script를 찾는 디렉토리를 지정하는 변수이다. -I switch를 사용하여 지정된다. lib module을 사용하여도 지정 가능하다. |
@F | -a switch를 사용한 경우에, input line을 분리해 넣어줄 변수의 array를 지정하는 list 변수이다. |
%INC | module로 사용될 Perl script의 이름과 그 script의 절대경로를 저장해놓은 hash 변수이다. |
%ENV | shell에서 넘겨져 온 hash type의 환경변수이다. 환경변수의 이름과 그 값을 hash해 놓았다. |
%SIG | signal과 그에 해당하는 signal handler를 지정하는 hash 변수이다. |
다음은 환경 변수를 지정하는 예제이다.
$ENV{'PATH'} = "/bin:/usr/bin:/usr/local/bin";
signal handler를 사용하는 예제를 참고하도록 하라. Ctrl-C를 눌러 interrupt를 걸게 되면 프로그램이 중단되게 되는데 INT signal을 가로채서 특정한 함수(signal handler)를 실행시키도록 할 수 있다.
sub sighandler { local($sig) = @_; print "Closing log file...\n"; close(LOG); exit(0); } $SIG{'INT'} = 'handler';
e) Global special Filehandles
ARGV | @ARGV에서 저장된 argument로 넘겨진 file들을 다루는 filehandle이다. |
STDERR | standard error에 대한 filehandle이다. |
STDIN | standard input에 대한 filehandle이다. |
STDOUT | standard output에 대한 filehandle이다. |
DATA | Perl script에서 __END__라는 token뒤쪽에 나오는 모든 자료에 대한 filehandle이다. |
_(underline) | 마지막으로 다루었던 파일에 대한 정보를 cache로 저장하고 있는 filehandle이다. |
6) 레퍼런스(reference)
'레퍼런스'란 우리말로 옮기자면 '참조'라고 할 수 있다. 어떤 변수가 존재하고 그 이름을 저장함으로써 원래의 변수를 '참조'하는 다른 변수가 존재한다면 그 변수를 symbolic reference라고 한다. 반면에 hard reference라는 것도 있는데, 이것은 이름을 통해서가 아니라 실제의 값을 통해서 참조하게 된다. 여기에서는 hard reference에 더 중점을 두어 자세히 설명하려고 한다.
reference와 관련된 용어들에 대해서 잠깐 설명을 하고 넘어가자. reference를 만드는 것을 referece한다고 하면, 반대의 과정, 즉 참조 대상이 되는 변수나 상수의 값을 꺼내오는 것을 dereference한다고 한다. reference의 대상이 되는 것을 thingy(referent)라고 부른다. Perl의 저자인 Larry Wall이 thingy라는 표현을 고집하므로 여기서도 thingy라는 표현을 사용하도록 하겠다.
a) hard reference
hard reference가 가리키는 대상을 Perl에서는 흔히 thingy라고 지칭하는데, 이 thingy에 따라, hard reference를 만드는 방법은 조금씩 달라진다. 대상은 어떤 값이라도 가능한데, 여기서는 이름 있는 변수(named variable)와 서브루틴(named subroutine), 이름 없는 배열(anonymous array), 이름 없는 hash(anonymous hash), 이름 없는 서브루틴(anonymous subroutine), 파일핸들(filehandle)에 대해서 살펴보기로 하자.
우선 대개의 변수와 서브루틴은 이름을 가진다. 그러므로 이러한 일반적인 변수나 서브루틴에 대한 reference가 많이 필요하게 되며, hard reference는 \(backslash)연산자를 사용함으로써 만들 수 있다.
$scalarref = \$foo; # $foo는 일반변수, \는 reference하는 연산자 $constref = \3.24; # hard reference는 값에 대한 reference이다. $arrayref = \@array1; $hashref = \%hash2; $globref = \*STDOUT; # *는 typeglob이며, 모든 타입을 가리킨다. $subref = \&subroutine3;
간단히 일반화해보면, 어떤 형태의 변수나 상수에 대해서 그 앞에 \연산자를 붙여주면 hard reference가 되는 것이다. reference를 통해서 그 값을 사용하기 위해서는 $, &, @, % 연산자 중의 하나를 reference변수 앞에 붙여주면 된다. reference는 그 대상(thingy)가 어떤 타입인지를 신경 쓰지 않기 때문에, 사용자가 타입 캐스팅을 하듯이 타입연산자인 $, %, &, @ 등의 연산자를 지정하는 것이 필수적이다.
print $$scalarref; # print $foo;와 동일하다. $$constref # 3.24와 동일하다. $$hashref{'key1'} # $hash2{'key1'}과 동일하다. &$subref; # subroutine3를 호출하게 된다. shift(@$arrayref); # @array1에 대해 shift연산을 하는 것과 같다.
reference변수를 다시 reference할 수 있는데, 이럴 때에는 필요한 만큼 \연산을 붙여 줄 수 있고, 값을 꺼낼 때에는 타입연산자의 수가 \연산자의 수보다 하나 더 많은가를 확인해야 한다.
$multiref = \\\\123; # 123에 대해 \연산을 4번 시행 print $$$$$multiref; # $multiref에 대해서 $연산을 4번 시행
이번에는 이름 없는 배열과 해시와 서브루틴을 참조하는 hard reference를 다루는 법에 대해서 알아보도록 하자. 여기서 이름이 없다는 표현은, 이미 만들어져서 이름을 가지고 있는 변수나 상수의 값을 참조하는 게 아니라 reference를 만들면서 그것이 참조하는 thingy를 만들기 때문에 이름을 지어주지 못했다는 의미이다.
$arrayref = [1, 2, [3, 4, 5]]; # anonymous array reference $hashref = { 'Meg' => 'Ryan', # anonymous hash reference 'Billy' => 'Christal' };
$subref = sub { print "When Harry met Sally\n"; # anonymous subroutine reference };
위의 예제를 살펴보면, reference는 보이지 않고, 보통 variable에 값을 대입해 준 것처럼 보인다. 그러나 실제 assignment는 다음과 같다. 잘 비교해보도록 하자. 연산자로 사용되는 기호들이 약간씩 다르다는 것을 알게 될 것이다.
@array = (1, 2, (3, 4, 5)); %hash = ( 'Meg' => 'Ryan', # anonymous hash reference 'Billy' => 'Christal' ); &subroutine1; sub subroutine1 { print "Sleepless in Seattle\n"; # anonymous subroutine reference };
두 예제를 비교해 본 바와 같이, anonymous array reference는 [ ]을, anonymous hash reference는 { }를, anonymous subroutine reference는 sub { }를 연산자로 사용한다는 것을 알 수 있다.
b) hard reference in nested data structure
$연산자로 dereference하는 것 이외에도, ->연산자(arrow operator)를 사용해서 array나 hash의 값을 dereference하는 방법도 있는데, 이러한 방법들은 '배열의 배열'을 구현할 때 긴요하게 쓰인다. 다음은 같은 효과를 내는 연산자 사용에 대한 예이다.
$$arrayref[0] = "first"; ${$arrayref}[0] = "first"; $arrayref->[0] = "first"; $$hashref{'key2'} = "McLean"; ${$hashref}{'key2'} = "McLean"; $hashref->{'key2'} = "McLean";
->연산자는 dereference의 기능을 $연산자보다 더 직관적으로 제시하기 때문에 C프로그래밍에 익숙한 사용자에게 유용할 것이다. 다음은 ->연산자를 이용한 1차원 배열의 원소를 dereference하고, '배열의 배열'내의 원소를 dereference하는 방법을 보인 예이다.
$arrayref->[3] = "list-item1"; $arrayref->[3]->[4] = "multi-dimensional-item2"; $arrayref->[3][4] = "multi-dimensional-item2"; $$arrayref[3][4] = "multi-dimensional-item2";
C프로그래머에게는 '배열의 배열'을 나타내는 위의 표현 중에서 마지막 것이 친숙해 보일 수 있으나, Perl에서는 []연산자가 우선 순위(precedence)가 낮아서 reference가 어떻게 사용되고 있는지를 알아보기가 어렵다는 이유로 권하지 않는 방법이다. 그 위의 두 가지 방법을 이용하여 다중 배열을 나타내기로 한다.
$ref_list_of_list = [ ["separator", "delimiter", "terminator"], ["long", "short", "int", "signed", "unsigned"], ["physics", "chemistry", "computer"], ]; print $ref_list_of_list[0][2]; # terminator가 출력된다. print $ref_list_of_list->[2][1]; # electronics가 출력된다. print $ref_list_of_list->[1]->[3]; # signed가 출력된다.
만약 for loop를 사용해서 모든 원소를 출력하기를 원한다면, 각 sub list가 가지고 있는 원소의 개수를 알아야 하는데, length()함수를 써서 원소의 개수를 구해 내거나, $#연산자를 사용하여 리스트변수의 마지막 인덱스를 얻을 수 있다.
b) symbolic reference
symbolic reference는 reference가 가리키고 있는(여기서는 thingy가 아님을 유의할 것) 변수의 이름을 문자열로 해서 저장하게 된다. 다시 말해서 변수의 이름을 가진다는 것은 symbolic reference를 만드는 것이다. hard reference가 thingy를 직접 참고하는 것에 반해서 symbolic reference는 변수의 이름을 통해서 그 값에 접근하게 된다. 그러므로 항상 named variable에 대해서만 symbolic reference를 만들 수 있는 것이다.
symbolic reference를 만드는 것은 단순히 변수의 이름을 지정하기만 하면 되며, 그 사용법도 hard reference와 다르지 않다. 타입연산자를 앞에 붙여주면 그 값을 취할 수 있게 된다.
$var1 = "test"; $sr = "var1"; $$sr = "verify"; # $var1 eq "verify" @var2 = ("hello", "program", "world"); $sr = "var2"; push(@$sr, "perl"); # @var2 eq ('hello', 'program', 'world', 'perl')
7) 배열의 배열, 해시의 배열, 배열의 해시
배열의 배열은 사실상 Perl에서 제공하려고 의도하는 것은 아니다. Perl은 단순히 1차원의 배열만을 제공하는데, C와는 달리 scalar값이 놓일 위치에 vector형 변수가 놓일 수 있으므로 자연스럽게 배열의 배열, 해시의 배열, 배열의 해시 등과 같은 복합적인 data structure를 만들 수 있다.
a) 배열의 배열
다음과 같은 코드를 실행시켜 보자. 아마 아무 출력도 없을 것이다. 그 이유는 Perl이 1차원 배열만을 제공하기 때문에 $list_of_list[0][2]와 같은 형식은 무시되기 때문이다.(-w 스위치를 사용하면 warning을 볼 수 있다.)
@list_of_list = ( ("separator", "delimiter", "terminator"), ("long", "short", "int", "signed", "unsigned"), ("physics", "chemistry", "computer"), );
print $list_of_list[0][2]; print $list_of_list[2][1]; print $list_of_list[1][3];
2차원 배열을 구현하기 위해서는 reference를 이용해야 한다.
@list_of_list = ( ["separator", "delimiter", "terminator"], ["long", "short", "int", "signed", "unsigned"], ["physics", "chemistry", "computer"], ); print $list_of_list[0][2]; # terminator가 출력된다. print $list_of_list[2][1]; # electronics가 출력된다. print $list_of_list[1][3]; # signed가 출력된다.
b) 배열의 해시
리스트를 해시 값으로 사용하여 해시를 만드는 것도 비슷한 방법에 의해 가능하다.
%hash_of_list = ( token => ["separator", "delimiter", "terminator"], type => ["long", "short", "int", "signed", "unsigned"], science => ["physics", "chemistry", "computer"], ); print $hash_of_list{'token'}->[1]; # delimiter가 출력된다. print $hash_of_list{'type'}[0]; # long이 출력된다. print $hash_of_list{science}; # ARRAY(0xb2074)같은 정보가 출력된다. print $hash_of_list{science}->[2]; # computer이 출력된다. print @$hash_of_list{science}; # 아무 것도 출력되지 않는다. print @{$hash_of_list{science}}; # physicschemistrycomputer 출력
c) 해시의 해시
%hash_of_hash = ( token => { s => "separator", d => "delimiter", t => "terminator" }, type => { l => "long", s => "short", i => "int" }, science => { e => "chemistry", c => "computer", }, ); print $hash_of_hash{token}->{s}; # separator 출력 print $hash_of_hash{type}{i}; # int 출력 print $hash_of_hash{science}; # HASH(0xb205c)같은 정보가 출력된다.
다만 주의할 것은 해시나 리스트는 해시의 키(key)로 사용할 수 없다는 것이다. 이유는 key는 단순한 문자열로 취급이 되기 때문이다. 리스트나 해시를 넣는다고 해도 문자열 이상의 의미를 가지지는 못하게 된다.
2. 식(Expression)
1) 기본 연산자
a) 수치 연산자
Perl은 수치에 대해서 사칙연산을 기본적으로 제공한다. +, -, *, /가 그에 해당하는 연산자이다. 그리고 FORTRAN과 같이 **에 의한 거듭제곱 연산도 가능하다. 나머지를 구하는 연산도 있는데, %를 연산자(operator)로 사용하며, 피연산자(operand) 모두를 정수로 취급한다. 반면에 나누기 연산자인 /는 피연산자 모두를 실수로 취급한다. 논리연산자인 <, <=, ==, >=, >, !도 사용 가능하다.
다음은 수치 연산자의 사용 예이다.
2 + 3 # 5 5.1 - 2.4 # 2.7 3 * 12 # 36 14 / 2 # 7 10.2 / 0.3 # 34 10 / 3 # 3.33333... if (3 > 4) { print "3 is greater than 4.\n"; } else { print "3 is not greater than 4.\n"; # 비교문이 거짓이므로 여기가 실행됨 }
b) 문자열 연산자
문자열 연산자에는 C에서는 볼 수 없는 연산자들이 많이 등장한다. 우선은 문자열들을 붙이는 연산자인 .(dot)이 있고, 문자열을 정수번 반복해서 붙여주는 x연산자도 있다. 다음의 예제를 참고하도록 하자.
$b = "Hello, "; $c = "world"; $a = $b . $c; # $a = "Hello, world" $a = "boy"; $b = $a x 3; # $b = "boyboyboy" $c = $a x 4.2; # $b = "boyboyboyboy", 4.2는 4로 cast된다.
c) 대입연산자(assignment operator)
대입연산자에는 C와 마찬가지로 =이 기본적인 연산자이다. 이밖에도 C에서 사용하는 대입연산자들은 거의 Perl에서 채택되어져 있다. +=, -=, *=, /=, &=, |=. ^=, %=가 그 예이다. 그 밖에도 .=, x=, ||=, **=, <<=, >>=의 연산자들이 있다. 이와 같은 대입연산자들은 %연산, .연산, x연산, ||연산을 수행한 후에 그 결과를 대입하는 것이므로 보충 설명이 없어도 이해에 어려움이 없을 것이다.
$a *= 3; # $a = $a * 3; $b .= "\n"; # $b = $b . "\n" $c ||= 2; # $c = $c || 2, $c가 2가 아니면 2의 값을 대입한다.
d) 연산자 우선 순위와 결합법칙
여러 연산자들의 결합순위와 우선 순위를 다음의 표로 정리해놓았다. 위쪽에 있는 연산자일수록 아래쪽 연산자들보다 우선 순위가 높다. 같은 줄에 있는 연산자들은 같은 우선 순위 가지게 되고 결합순위에 따라 연산의 순서가 정해진다.
결합순위 | 연산자 |
없음 | ++ -- |
우 | ! ~ -(단항연산) |
우 | ** |
좌 | =~ !~ |
좌 | * / % x |
좌 | + -(이항연산) . |
좌 | << >> |
없음 | file test operator |
없음 | named unary operator |
없음 | < <= > >= lt le gt ge |
없음 | == != <=> eq ne cmp |
좌 | & |
좌 | | ^ |
좌 | && |
좌 | || |
없음 | .. |
우 | ?:(삼항연산) |
우 | 대입 연산자 (+= -= 등) |
좌 | , |
없음 | list 연산자 |
e) 수치와 문자열 상호변환
Perl은 수치와 문자열, 정수와 실수 사이의 변환을 자동으로 해준다. 수치 앞 뒤쪽에 오는 쓸데없는 문자들을 자동으로 제거해주므로 다음과 같은 연산이 가능하다.
$a = " 3.5abcd" * 4; print $a; # 3.5 * 4 = 14가 출력된다. $b = "3" + "4"; # $b = 7
Perl이 자동으로 변환해 주는 것은 문자열 또는 수치가 이중적인 의미를 가질 때, 앞쪽이나 뒤쪽에 위치하는 연산자가 필요로 하는 피연산자가 수치인가 문자열인가를 결정할 수 있기 때문이다.
print "boy" . (3 * 2); # boy6가 출력된다. print "boy" x (3 * 2); # boyboyboyboyboyboy가 출력된다.
괄호 안의 3 * 2이 먼저 수치로서 계산이 된다. 수치로 인식되는 이유는 *이 수치를 피연산자로 가지기 때문이다. 다음에는 문자열을 결합시켜주는 . 연산자를 만나게 되므로 (3 * 2)의 결과인 6을 문자열로 변환시켜서 인식하게 된다. 이러한 방식으로 자동변환이 일어나게 되므로 프로그래머는 사소한 것에 신경 쓰지 않아도 된다.
2) 추가 연산자
또 하나 프로그래머가 매달릴 필요가 없는 것은 연산자를 연산자로 볼 것인가 함수로 볼 것인가의 문제인데, Perl에서 제공되는 대개의 연산자는 연산자이면서 함수로 볼 수 있기 때문에 C처럼 ()를 반드시 써 줄 필요가 없다.
a) scalar operator
rand $a | 0과 $a 사이의 임의의 값(random number)을 반환한다. 0이상 $a미만의 범위의 소수를 구하게 된다. |
srand $a | random number를 만들어 낼 때 사용하는 seed값을 지정한다. srand(time^$$)처럼 현재시간을 알려주는 time연산자와 현재 프로세스의 번호를 지정하는 $$변수를 넣어서 사용하기도 한다. |
substr $a, $b, $c | $a라는 문자열에서 $b의 offset위치에 $c개만큼의 원소를 지정하게 된다. substr을 이용해서 $a에 새로운 값을 넣어주거나 $a에서 원소를 꺼내올 수 있다. $c부분은 생략 가능하다. |
index $a, $b, $c | 문자열 $a에서 $b가 나타나는 위치(offset)을 알려준다. $c부분은 찾기 시작하는 위치를 지정하는 것인데, 생략되면 0의 값이 대신 사용된다. |
chop $a | chop은 $a의 마지막 글자를 제거하는 연산자이며 Perl 5에서는 다음의 chomp을 권장한다. $a는 리스트라도 처리할 수 있다. |
chomp $a | chomp는 chop의 안전한 버전이며, $a의 마지막 글자가 $/변수($INPUT_RECORD_SEPARATOR, $RS)의 값을 가지면 제거하는 역할을 한다. 대개는 입력에 붙어서 들어오는 newline character를 제거할 때 사용한다. |
b) vector operator
shift @a | 리스트 @a의 가장 앞쪽에 있는 원소를 꺼내준다. |
unshift @a, $b | $b를 @a의 가장 앞쪽에 쑤셔 넣어 준다. |
pop @a | @a의 가장 뒤쪽에 있는 원소를 꺼내준다. |
push(@a, $b) | @a의 가장 뒤쪽에 $b를 추가해준다. |
splice @a, $b, $c, @d; | @a의 offset이 $b되는 곳에서 $c개의 원소를 지정할 때 사용한다. @d가 사용되는 경우에는 @d를 $c개의 원소와 바꾸어준다. @d가 생략되면 $c개의 원소를 반환한다. |
keys %a | 해시 변수 %a의 key를 모아서 리스트 형태로 반환한다. |
$a .. $b | ..은 범위연산자(range operator)라고 하는데, $a에서 $b까지의 값을 리스트 형태로 반환한다. $a와 $b에 들어갈 수 있는 값은 정수나 문자이다. 문자열을 사용하면 그 사이에 존재할 수 있는 모든 문자열 조합을 만들어 주기도 하는데, 시스템의 자원을 낭비하기 쉬우므로 조심해야 한다. |
, | comma(,) 연산자는 리스트에서 separator로 사용되는 comma와는 다른 의미를 가진다. comma 연산자는 ,뒤쪽의 값을 반환한다. ,로 여러 개의 값이 나열되면 마지막 값이 리턴 된다. |
sort @a | 리스트변수 @a를 정렬해준다. sort의 기준을 제시하는 비교함수나 블럭을 지정할 수도 있는데, 리스트 앞에 지정해준다. 이 때 비교함수나 블럭에서는 <=>, cmp 연산자와 논리연산자등을 사용하여 +1, -1, 0등의 값을 반환하도록 한다. |
reverse @a | 리스트의 원소의 순서를 반대 방향으로 바꾸어준다. |
grep PATTERN, @a grep FILETEST, @a |
리스트변수 @a를 읽어들여 패턴매치나 파일 테스트를 해서 성공할 경우, 그 리스트의 원소인 line이나 string을 반환한다. |
join $b, @a | @a의 원소들을 $b를 delimiter로 하여 묶어 하나의 문자열로 반환한다. |
split PATTERN, $a, $b | 문자열 $a를 PATTERN에 나오는 character를 delimiter로 해서 나누어 리스트형태로 반환한다. $b를 사용하게 되면 그 값에 해당하는 개수까지 원소들을 생성해준다. |
c) file test operator
다음은 파일에 대한 조건식을 만드는 operator을 정리한 표이다. 다음의 테스트는 참/거짓의 값을 가지거나 특정한 결과 값을 return해 준다.
-r | 파일을 읽을 수(readable) 있는가? |
-w | 파일을 쓸 수(writable) 있는가? |
-x | 파일을 실행시킬 수(executable) 있는가? |
-o | 파일이 $euid의 사용자 소유인가? |
-R | 파일이 $uid의 사용자에 의해 읽혀질 수 있는가? |
-W | 파일이 $uid의 사용자에 의해 쓰여질 수 있는가? |
-X | 파일이 $uid의 사용자에 의해 실행가능한가? |
-O | 파일이 $uid의 사용자 소유인가? |
-e | 파일이 존재하는가? |
-z | 파일의 크기가 0인가? |
-s | 파일의 크기(size) |
-f | 파일이 정규파일(디렉토리가 아닌)인가? |
-d | 파일이 디렉토리인가? |
-l | 파일이 symbolic link인가? |
-p | 파일이 FIFO와 같은 named pipe인가? |
-S | 파일이 socket인가? |
-b | 파일이 block special file인가? |
-c | 파일이 character special file인가? |
-t | filehandle이 tty(terminal)에 열려 있는가? |
-u | setuid bit이 켜져 있는 파일인가? |
-g | setgid bit이 켜져 있는 파일인가? |
-k | sticky bit이 켜져 있는 파일인가? |
-T | 파일이 텍스트(text) 파일인가? |
-B | 이진(binary) 파일인가? |
-M | file의 최종 수정 시간(modification time, mtime) |
-A | file의 최종 접근 시간(last access time, atime) |
-C | file의 최종 변경 시간(inode change time, ctime) |
다음은 file test를 이용한 조건식의 예이다.
while (<>) { chomp; # default input string의 마지막 \n을 제거한다. next unless -f $_; # 정규파일이 아닌 경우에는 다음으로 넘어간다. } stat($file); print "Readable\n" if -r _; # _는 마지막으로 사용된 filehandle이다. print "Writable\n" if -w _; # readable, writable, executable한지 체크한다. print "Executable\n" if -x _; &newfile if -M $file < 5; # mtime이 5일 이내라면 서브루틴을 호출한다.
3) 기본 입출력 연산자
입출력이란 프로그램이 자료를 외부세계에서 넘겨받고 그 자료를 처리한 결과를 외부세계에 넘겨주는 과정을 의미한다. Perl에서 가능한 방법은 파일을 이용한 것이고, 프로그래머는 filehandle이라는 것을 가지고 파일을 다룰 수 있게 된다. 파일을 열어서 filehandle을 지정하는 것은 open함수를 사용하면 되고, 더 이상 사용하지 않게 된 파일을 닫고 filehandle의 사용을 포기하는 것은 close함수를 이용하면 된다.
그러나 입출력 과정에 filehandle을 매번 사용한다는 것은 프로그램 작성에 상당한 불편을 안겨줄 것이다. 그리하여 Perl에서는 Unix시스템과 마찬가지로 표준입력(STDIN), 표준출력(STDOUT), 표준에러(STDERR)를 제공한다. 표준입력이란 키보드를 통해 자료를 입력받거나, 다른 프로그램에서 파이프나 redirection을 통해 자료를 넘겨받는 것이다. 반면에 표준출력은 print 연산이나 printf를 통해서 화면으로 결과를 출력하는 것이고, 표준에러는 화면에 에러메시지를 보여주는 것이다.
a) 입력
일반적으로 키보드로부터의 입력은
$input = <STDIN>; @input = <STDIN>;
과 같은 형태로 받아들일 수 있다. $input이나 @input에는 사용자가 키보드를 통해 눌렀던 모든 키 입력이 문자열 또는 리스트의 형태로 저장된다.
Perl에서 즐겨 사용되는 입력방식에는 다음과 같은 것들이 있다. 짧은 코드에서 긴 코드까지 모두 같은 효과를 가지며, 문맥적으로 같은 의미를 가진다.
print while <STDIN> print $_ while <STDIN>; print $_ while definded($_ = <STDIN>); for (;<STDIN>;) { print; } for (;<STDIN>;) { print $_; } while (<STDIN>) { print; } while (<STDIN>) { print $_; } while (defined($_ = <STDIN>)) { print $_ };
$_는 default input string을 저장하는 변수로서 대개의 연산자에서 생략 가능하다. 거꾸로 말해서, 연산자만 나오는 경우는 대개 $_를 염두에 둔 것이라고 볼 수 있다.
STDIN같은 표준입출력에 관련된 filehandle은 굳이 open의 과정이 필요 없으나, 일반적인 파일에 대한 filehandle은 open의 과정이 명시되어야 한다.
open(FILE, "test.c"); $_ = <FILE>; print;
이 예제는 test.c라는 파일을 FILE이라는 filehandle을 통해서 다루게 되고 default input string에 한 줄 입력을 받아서 그것을 다시 출력해주는 작업을 하게 된다. 그러나 대개는 한 줄만 입력을 받는 게 아니라 파일 전체에 대해서 여러 줄의 입력을 받아야 하므로 loop안에서 사용하거나 리스트에 저장했다가 shift연산자를 사용하여 꺼내어 사용할 수 있다. 그러나 리스트에 파일의 큰 내용이 저장되는 것은 시스템에 부하를 주게 되므로 loop를 사용하는 방법을 익히도록 하자.
while ($input = <FILE>) { print "/* $input */\n"; }
이 예제는 FILE을 통해 $input에 입력을 받고 그 line을 comment기호로 감싸는 작업을 수행하는 것이다.
Perl 프로그램이 filter로 사용되는 게 아니라면 대개는 프로그램의 argument로 파일 이름을 넘겨받아 사용하게 되는데, 이것은 아주 간단하다. 다음은 argument로 넘겨진 모든 파일을 열어서 한 줄씩 입력을 받아서 출력하는 예이다.
while (<>) { print; }
이 코드는 다음과 동일한 의미를 가진다.
@ARGV = ('-') unless @ARGV; while ($ARGV = shift) { open(ARGV, $ARGV) or warn "Can't open $ARGV: $!\n"; while () { print; } }
argument로 넘겨받는 파일이름에 대해서는 신경쓸 필요 없이 <>만 사용하면 각 파일에 대해서 open하고 읽어들이는 과정이 모두 자동적으로 실행되는 것이다.
b) 출력
print연산을 사용하는 경우에는 default로 출력의 방향이 STDOUT으로 정해진다. 그러므로 다음의 세 문장은 같은 의미를 가진다.
print "Hello world\n"; print STDOUT "Hello world\n"; print STDOUT Hello, " ", world, "\n";
STDOUT뒤에 ,를 찍지 않도록 주의하자. STDOUT은 출력 대상이 아니라 출력의 방향이기 때문이다.
특정 파일에 대해서 출력을 하기 위해서는 미리 쓰기 mode로 open을 해야 하고 그 이후에 print 연산에 출력 방향으로 그 파일의 filehandle을 지정하면 된다.
open(OUTPUT, "> output.log"); print OUTPUT "Result : All men are alive.\n");
shell에서 사용하던 방식의 token을 이용한 출력방법도 있다. 이것을 'HERE DOCUMENT' 문법이라고 하며 token을 일종의 quote로 생각하는 것이다. 여러 줄에 걸쳐진 출력에 대해서 문서를 쓰듯이 출력할 수 있다는 게 장점이다.
다만 이 HERE DOC방법에서 주의할 것은, 1) << 뒤에는 스페이스가 없어야 한다는 점, 2) 첫번째 delimiter 뒤에는 ;가 따라와야 한다는 점, 3) 두번째 delimiter 앞뒤에는 공백이 없어야 한다는 점이다.print OUTPUT << EOF carried. being still are loss of Estimation lost. payload most But>
print 연산자는 EOF라는 토큰을 quote로 간주하고 두개 사이의 텍스트를 지정된 출력방향으로 내보내게 된다. 토큰에 따라서 보다 정교한 출력 방법이 있는데, 여기서는 몇 가지만 더 살펴보기로 하겠다.
print << "" x 10 Hello, world!
이 예제는 quote가 null string이므로(그러므로 ""조차 생략 가능하다.) 다음 줄만을 출력 대상으로 삼게 되고 print 연산을 10번 수행하게 된다.
print << `ANYTOKEN` id echo hello ANYTOKEN
token을 감싸는 것이 back-quote(backtick)임에 유의하라. back-quote는 명령을 실행시켜주는 역할을 한다. 그러므로 두 토큰 사이에 존재하는 id와 echo hello라는 명령이 실행된 결과가 출력되게 된다.
c) 에러 출력
에러는 파일로 보낼 수도 있지만, STDERR로 모아서 보내는 것이 일반적이다. 그러므로 print의 출력방향을 STDERR로 명시하면 된다.
print STDERR "Can't find such a file!\n";
4) 비교 연산자
Perl에서는 문자열에서 사용되는 논리연산자와 수치에서 사용되는 논리연산자가 따로 제공된다. 다음의 표를 참조하라.
비교 표현식 | 수치연산자 | 문자열연산자 |
같은가? | == | eq |
같지 않은가? | != | neq |
보다 작은가? | < | lt |
보다 큰가? | > | gt |
같거나 작은가? | <= | le |
같거나 큰가? | >= | ge |
같지 않은가? | <=> | cmp |
<=> 연산자와 != 연산자의 차이점은 != 연산자의 결과는 1(참) 또는 0(거짓)인데 반해서, <=> 연산자의 결과는 좌측 피연산자에서 우측 피연산자를 뺀 결과로 -1의 값을 가질 수 있다.
수치에 관한 비교연산자는 굳이 설명하지 않더라도 C프로그래밍을 조금이라도 해 본 사람이라면 누구나 알 수 있으리라 생각해서 설명을 생략하였다. 그러나 문자열에 관한 비교연산자는 다소 생소할 것이다. string을 비교하는 것은, 두 문자열이 사전에 쓰여져 있다면 어떤 문자열이 상대적으로 앞쪽에 위치하는가에 따라 결정된다. 사전에 쓰이지 않는 글자라면 ASCII코드 값에 따라 결정되게 된다. 다음은 참인 조건식이다.
"" lt "1" "1" lt "A" "A" lt "a"
숫자는 영문자보다 앞쪽에 위치하고 대문자가 소문자보다 앞쪽에 위치한다. null character가 다른 어떤 글자들보다 앞서기 때문에 다음과 같은 조건식도 참임을 알 수 있다.
"Korea" lt "Korean"
그리고 다음의 예제는 문자열에 관계되는 논리연산자를 사용한 예이다.
$a = "Jones"; # Clinton이라는 단어가 Jones라는 단어보다 사전에서 $b = "Clinton"; # 앞쪽에 위치하므로 ("Jones" lt "Clinton")은 거짓이다. if ($a lt $b) { print "$a is less than $b.\n"; } else { print "$a is greater than or equal to $b.\n"; }
5) 논리연산자(logical operator)
bitwise operator로는 C와 마찬가지로 &(bit AND), |(bit OR), ^(bit XOR)등의 연산자 등이 제공된다. 논리연산자 또한 C와 마찬가지로 &&와 ||가 사용되는데, short-circuit 연산자라는 특성을 활용한 기법이 Perl에서 자주 사용된다.
open(FILE, $filename) || die "Can't open $filename!\n"; open(FILE, $filename) && (@data =) && close(FILE) || die "Can't open $filename!\n";
이 문장은 file을 open하고 읽어들이고 close하는 모든 과정이 성공적으로 수행되지 못하고 3개의 피연산자인 open, read, close 문장 중의 어떤 하나라도 실패하게 되면 die 문장이 수행되는 것이다.
&&와 ||는 기호가 아니라 and 또는 or로 대신 사용할 수 있다.
open(FILE, $filename) or die "Can't open $filename!\n";
6) 이름 있는 단항 연산자(named unary operator)
다음의 named unary operator들은 함수처럼 각각의 이름으로 사용되는 단항 연산자이다. named unary operator는 어떤 binary operator(이항 연산자)보다는 우선 순위가 높다. 그래서 expression(표현식)을 쓸 때에는 피연산자에 대한 연산자들의 우선 순위를 충분히 고려하는 것이 바람직하다. 모호할 경우에는 반드시 ()를 사용하여 우선 순위를 명시하는 것이 안전하다.
alarm | caller | chdir |
chroot | cos | defined |
delete | do | eval |
exists | exit | exp |
gethostbyname | getnetbyname | getpgrp |
getprotobyname | glob | gmtime |
goto | hex | int |
lc | lcfirst | length |
local | localtime | log |
lstat | my | oct |
ord | quotemeta | rand |
readlink | ref | require |
reset | return | rmdir |
scalar | sin | sleep |
sqrt | srand | stat |
uc | ucfirst | umask |
undef |
3. Control Structure(제어 구조)
제어구조에서 가장 중요한 것은 조건식(conditional expression)이다. 조건식이 참일 때와 거짓일 때, 프로그램의 제어(control)이 분기해야 하기 때문이다. Perl에서 조건식이 거짓이 되는 경우는 계산의 결과가 다음과 같을 때이다.
0 # 수치 0 "" # null string undef # null string
이런 경우를 제외한 모든 경우는 참인 조건식으로 간주된다.
1) if
if/else 구문은 C의 문법과 비슷하다. 다만 조건식의 결과에 따르는 문장은 반드시 블럭 안에 위치해야 한다.
if (조건식) { # 참일 때 실행하는 문장들을 쓴다. } else { # 거짓일 때 실행하는 문장들을 쓴다. }
두개 이상의 조건식을 나열하기 위해서는 다음과 비슷한 형식으로 조건문을 구성해야 한다.
if (조건식1) { # 조건식1이 참일 때 실행되는 문장들 } elsif (조건식2) { # 조건식2가 참일 때 실행되는 문장들 } elsif (조건식3) { # 조건식3이 참일 때 실행되는 문장들 } else { # 모든 조건식이 참이지 않을 때 실행되는 문장들 }
다음은 if/else를 이용한 조건문의 예제이다.
print "How old are you? "; $a = <STDIN>; # 사용자에게 입력을 받는다. chop($a); # 입력의 마지막 글자(newline)을 떼어낸다. if ($a < 10) { print "You're a child.\n"; } elsif ($a < 20) { print "You're a teenager.\n"; } else { print "You're so old.\n"; }
실행할 문장이 하나일 때에는 매번 { }로 블럭을 지정하여 쓰기가 번거롭다. 이럴 때에는 if 조건식을 문장 뒤쪽에 위치시킬 수 있다.
print "$a is equal to $b.\n" if $a eq $b;
2) unless
unless는 조건식의 결과를 if와는 반대로 해석한다. 다음의 형식을 참고하라
unless (조건식) { # 조건식이 거짓일 때 실행되는 문장들 } else { # 조건식이 참일 때 실행되는 문장들 }
다음은 if에서 나왔던 예제와 같은 결과를 보여주는 예제이다.
print "How old are you? "; $a = <STDIN>; chop($a); unless ($a < 20) { print "You're so old.\n"; } elsif ($a >= 10) { print "You're a teenager.\n"; } else { print "You're a child.\n"; }
3) while/until
while문과 until문은 다음과 같은 형식을 가진다.
while (조건식) { # 조건식이 참인 동안은 계속 실행될 문장들 } until (조건식) { # 조건식이 거짓이 될 때까지 계속 실행될 문장들 }
while과 until의 관계는 if와 unless의 관계와 유사하다. 다음은 같은 결과를 보여주는 while/until구문의 예제이이다.
$a = 0; while ($a < 10) { $a++; print "$a\n"; # 1 2 3 4 5 6 7 8 9 10이 차례대로 출력된다. } $a = 0; until ($a >= 10) { $a++; print "$a\n"; # 1 2 3 4 5 6 7 8 9 10이 차례대로 출력된다. }
continue 블럭을 사용하여 loop를 돌 때마다 실행해주는 부분을 분리하여 사용할 수 있다. 다음은 while/continue 구문의 형식이다.
while (조건식) { # 조건식이 참일 때 실행되는 문장들 } continue { # 조건식에 관계되는 변수들의 증감식(auto-in/decrement) }
다음의 예제는 위쪽의 while/until의 예제와 같은 결과를 보여준다. 그러나 위쪽의 형식보다는 보다 안전한 구문 표기법으로 볼 수 있다. continue 블럭 안에서만 증감식이 구성되는 경우 변수의 추적이 용이하다.
$i = 1; while ($i <= 10) { print "Count-up: i = $i\n"; } continue { $i++; }
4) for
for구문의 기본적 형식은 C구문과 동일하다. 그러나 Perl만의 확장된 형식도 소개될 것이다.
for (초기화; 조건식; 증감식) { # 조건식이 참일 동안 실행될 문장들 }
가장 먼저 초기화 문장이 실행된다. 그리고는 조건식을 검사한 후에, 참이면 블럭내의 문장을 실행하고 블럭이 끝나면 증감식을 실행한다. 그리고 다시 조건식을 검사하는 일부터 다시 반복한다.
다음은 1부터 10까지의 합을 계산하는 예제이다.
$sum = 0; for ($i = 1; $i <= 10; $i++) { $sum += $i; # $sum = $sum + $i, $sum에 $i의 값을 더한다. } print "Sum is " . $sum . "\n";
이제 C를 뛰어넘는 Perl의 for 구문에 대해서 살펴보도록 하겠다. 범위연산자(range operator)인 ..를 사용한 형식은 다음과 같다.
for ($a .. $b) { # $a값부터 $b값까지 $b-$a+1번 수행되는 문장들 }
범위연산자 ..대신에 list연산자인 ,를 사용하여 원소를 나열하는 것도 가능하다. 이와 같은 list 형의 변수나 값을 사용하는 for구문은 단순히 정수번 수행되도록 하는 데만 사용되는 것은 아니다. 다음의 예제는 for문이 C의 for구문과는 다르게, 더 유연한 형식을 가짐을 보여준다.
for (10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) { print "Count-down: " . $_ . "\n"; # $_에는 10 .. 0이 각각 들어간다. } for (0 .. 10) { print "Count-up: " . $_ . "\n"; # $_에는 10 .. 0이 각각 들어간다. }
그러나 list가 아니라 범위 연산자를 사용한 경우에는, 수열이 줄어드는 조건식은 거짓으로 결정된다.
for (10 .. 1) { print "Count-down: " . $_ . "\n"; # 이 loop는 실행되지 않는다. }
이런 경우에는 reverse 연산자를 이용하여 원하는 결과를 얻을 수 있다.
for (reverse 1 .. 10) { print "Count-down: " . $_ . "\n"; }
5) foreach
foreach구문은 csh에서 사용되었던 구문인데 C프로그래머에게는 약간 생소할 수 있지만 vector형 자료를 많이 다루는 작업에서는 꼭 필요한 제어 구조이다. Perl에서는, foreach는 for의 동의어이다.
foreach scalar변수 (list형의_변수_또는_값) { # scalar변수에 대한 조작을 하는 문장들 }
list형의 변수 또는 값에서 하나씩 scalar변수에 대입하여 list내의 각 원소들에 대해서 각각 비슷한 유형의 처리를 해 줄 수 있다. 다음은 foreach를 사용하여 현재 디렉토리에 존재하는 파일들의 permission을 바꾸어주는 예제이다.
foreach $f (`ls`) { chmod 0755, $f; # chmod는 file의 permission mode를 바꾸어주는 연산자 }
다음은 hash 형 변수에서 keys 라는 연산자를 통해 array을 얻어내는 방식으로 환경변수의 이름과 값을 출력하는 예제이다. foreach구문은 이러한 방식으로 사용되기 때문에 index가 순차적이지 않은 vector형 변수를 다루는데 있어서 C의 for구문보다 효율적이고 직관적이다.
foreach $key (keys %ENV) { print "Key:$key, Value:$ENV{$key}\n"; }
6) do/while, do/until
do/while 또는 do/until구문은 위에서 설명했던 while이나 until구문을 참고하면 그다지 어려운 개념은 아닐 듯 싶다. 많은 C프로그래머들이 알고 있다시피 do로 시작하는 구문의 while구문(until구문)과의 차이점은 조건식이 loop보다 먼저 수행되는가 아니면 loop가 최소한 한 번 수행된 후에 수행되는가의 차이이다. do구문에서는 조건식이 loop 뒤쪽에 제시되므로 최소한 1번은 조건과는 상관없이 수행됨을 알 수 있다.
do구문의 형식은 다음과 같다.
do { # 조건식이 참인 동안에 실행될 문장들 } while (조건식); do { # 조건식이 참이 될 때까지(거짓인 동안에) 실행될 문장들 } until (조건식);
다음은 같은 결과를 보여주는 do/while, do/until구문의 예제이다.
$i = 0; do { print "Count-up: $i\n"; $i++; } while ($i <= 10); $i = 0; do { print "Count-up: $i\n"; $i++; } until ($i > 10);
7) goto
C프로그램과 마찬가지로 문장이나 블럭에 label을 지정할 수 있는데, label이 지정된 곳이 loop 내부가 아니라면 어디로든 제어(control)를 옮길 수 있다. 그러나 이러한 goto의 사용은 적합한 곳으로의 제어 이동이라 해도 바람직하지는 않다. Perl에서는 다른 언어들보다 더 풍부한 제어 구조(control structure)를 지원한다는 것을 유념하길 바란다. 다음은 goto의 형식이다.
goto LABEL;
예제를 참고하도록 하자. OUT이라는 label은 건너뛰고 IN이라는 label이 위치한 곳으로 제어가 옮겨진다.
goto IN; OUT: print "This message is printed to standard out.\n"; IN: print "Enter your input : "; $input = <STDIN>;
goto의 가장 큰 문제점은 LABEL이 goto의 앞쪽, 뒤쪽 어디에 존재하는지 알 방법이 없기 때문에, 프로그램의 전체를 검색하게 된다는 것이다. Perl 프로그램이 커짐에 따라 검색시간이 늘어날 것은 자명하다.
그러나 goto는 지정해주는 변수 값에 따라 동적으로 제어가 넘겨지는 label을 바꿀 수 있다. 다음의 예제는 $i의 값에 따라 goto가 실행될 label이 여러 개 나열될 수 있음을 보여준다.
goto ("LINE0", "LINE1", "LINE2")[$i];
일반적으로 goto의 피연산자로 따라오는 label은 0부터 번호가 매겨지지만, $[의 값을 1로 지정하면 label은 1번부터 번호가 매겨진다. (특수한 변수에 관련해서 살펴보았듯이, $[은 array의 index 시작 번호를 지정하는 global special variable이다. )
8) next, last, redo
loop 내에서 제어를 옮기는 방법이 존재한다. next, last, redo가 그것이며, C프로그램과 비교하자면, next는 continue, last는 break와 비슷한 기능을 가지고 있다.
a) next
next는 loop내의 제어를 다음 차례로 넘기게 된다. C의 continue문과 같은 기능을 한다. 그러나 Perl에서의 continue 블럭과 C의 continue문장은 다른 의미를 가지게 된다. 다음 예제를 통해 next의 사용법을 익히도록 하자.
LINE: while (<STDIN>) { next LINE if /^#/; # 주석은 건너뛴다. next LINE if /^$/; # null line도 건너뛴다. } continue { $count++; # next에 의해 제어가 옮겨져도 count는 증가한다. }
LABEL이 어디에 위치하든 간에 next문을 만나게 되면 continue블럭에서 증감식을 수행한 후에 그 label로 제어를 옮기게 됨을 알 수 있다. C의 continue와 같이 단순하게 작동시키는 것도 어려운 일이 아니다.
for ($i = 0; $i < 10; $i++) { if ($i == 5) { next; } print "$i\n"; }
b) last
last는 loop를 빠져나오는 기능을 수행한다. loop를 빠져나오는 것이기 때문에 continue블럭내의 문장들도 당연히 수행되지 않는다. label이 loop의 가장 앞쪽을 가리킨다고 해서 loop를 다시 반복하는 것이 아님에 유의하라.
LINE: while (<>) { last LINE if /^$/; # null string을 만나면 종료한다. print; # default input string인 $_를 출력한다. }
위의 예제에서는 LINE이라는 label을 명시하지 않아도 loop를 종료할 수 있다. 그러나 loop 밖의 label을 지정하는 경우에는 compile-time error가 발생하게 된다.
OUT: print "Hello\n"; LINE: while (<>) { last OUT if /^$/; # OUT를 찾을 수 없어서 에러가 발생한다. print; }
C의 break와 마찬가지로 last는 다중 loop에서 last 구문이 속해 있는 loop만을 빠져나올 뿐이다.
for ($i = 0; $i < 10; $i++) { for ($j = 0; $j < 10; $j++) { print "($i, $j)\n"; last if $j >= 5; } # loop내의 last에 의해 제어가 옮겨지는 곳은 바로 이곳이다. }
c) redo
redo문장은 loop를 새로 시작하는 기능을 가진다. C에서는 볼 수 없었던 기능이다. redo도 next나 last와 같은 형식을 가지고 비슷한 역할을 한다. 다음의 예제를 보고 이해하도록 하자.
while (<>) { chomp; if (s/\\$//) { $_ .= <>; redo; } print; }
label을 사용하는 다음 예제는 앞의 예제와 같은 의미를 가진다.
LINE: while ($line =) { chomp($line); if ($line =~ s/\\$//) { $line .= ; redo LINE; } print $line; }
이 두 예제는 backslash(\)로 끝나는 line을 만나면 다음 line을 이번 line에 붙여주는 작업을 수행한다.
4. 서브루틴(Subroutine)
1) 정의와 사용
서브루틴이란 C의 함수(function)와 같은 것으로 볼 수 있다. 서브루틴은 실행 가능한 문장들을 포함하기는 하나, 정의 자체는 실행가능한 문장은 아니고 script 어느 곳에나 위치할 수 있다. 서브루틴의 정의와 호출은 다음과 같은 형식을 가진다.
sub 서브루틴이름 서브루틴블럭 # 서브루틴의 정의 &서브루틴이름; # 서브루틴의 호출 do 서브루틴이름;
서브루틴을 호출하기 위해서 기본적으로 & 연산자를 사용한다. 그러나 서브루틴은 do, require, use, eval등의 연산자를 통해 사용될 수 있다. & 연산은 괄호를 사용하거나 이미 정의된(또는 이미 import된) 경우 생략 가능하다.
다음은 서브루틴의 사용 예이다.
sub sum { $sum = 0; for ($i = 0; $i < 10; $i++) { $sum += $i; } print $sum; } ∑
여기서 대부분의 눈치 빠른 프로그래머라면 C와는 달리 argument가 넘겨지는 것이 formal하게 정의되어 있지 않음을 깨달았을 것이다. Perl에서는 default input list로 argument를 주고받을 수 있다. 서브루틴 내부에서 argument를 받아서 사용할 parameter들은 다음에서 살펴 볼 local이나 my 연산자를 이용하여 초기화시킬 수 있다. 다음의 예제는 argument를 parameter로 넘겨받는 방법을 설명하고 있다.
sub sum { local($a, $b) = @_; # default input list인 @_에서 넘겨받아 $sum = $a + $b; # $a와 $b에 차례대로 저장한다. } print &sum(1, 5); # 마지막으로 대입된 변수가 return된다. sub listdouble { local(*list) = @_; # @_를 다시 list에 넣으려면 *var로 받아서 foreach $e (@list) { # @var의 형태로 사용하면 된다. *는 어떤 형태의 $e *= 2; # 변수로도 사용될 수 있는 변수기호이다. } } @array = (1, 2, 3, 4, 5); &listdouble(@array); # @array eq (2, 4, 6, 8, 10)
2) my와 local
my와 local은 변수의 사용범위(scope)를 지정하는 modifier이다. local은 변수를 dynamic variable로 만들고, my는 변수를 lexical variable로 만들어준다. dynamic variable의 경우 그 변수가 선언된 블럭 내에서 호출된 서브루틴에서 보이게 된다. 그러나 lexical variable은 그 블럭을 벗어나게 되면 전혀 사용할 수 없게 된다.(lexical variable은 C함수 내부의 일반적인 변수의 성질과 같다.)
sub power { my ($num, $count) = @_; my $result = 1; if ($count == 1) { $result = $num; } else { $result = power($num, --$count) * $num; } } $val = power(2, 7); print "value=$val\$";
이 예제는 my를 사용한 거듭제곱을 계산하는 서브루틴이다. 2의 7승을 계산하여 128을 돌려주기 위해 recursion을 사용하였다.
5. 패턴 매칭(Pattern Matching)
'패턴 매칭'(pattern matching)이란, 주어진 단락(paragraph) 속에서, 제시된 표현형인 pattern이 일치하는 경우, 일치하는 부분(line이나 string, substring등)을 찾아내는 작업을 일컫는다. Perl에서의 pattern은 '정규 표현식'(regular expression)으로 나타낼 수 있다. 다음 소단락들을 통해 regular expression을 익혀, 진정한 Perl 프로그래밍 실력을 쌓도록 하자.
1) 정규 표현식(regular expression)
다음은 regular expression에서 사용되는 기호들의 의미를 정리한 표이다. Perl에서의 pattern matching은 regular expression을 사용할 수 있으므로 확실하게 익혀두는 것이 바람직하다.
. | newline을 제외한 임의의 한 글자 |
[a-z0-9] | a-z까지의 영문자와 숫자 중의 한 글자 |
[^a-z0-9] | a-z까지의 영문자와 숫자가 아닌 한 글자 |
\a | Alarm, beep |
\d | 10진수 정수 한 글자, [0-9] |
\D | 10진수 정수가 아닌 한 글자, [^0-9] |
\w | 영문자 또는 숫자, _(underline) 중의 한 글자, [a-zA-Z0-9_] |
\W | \w가 아닌 한 글자, [^a-zA-Z0-9_] |
\s | 공백문자(whitespace) 한 글자 (space, tab, newline ...) |
\S | \s가 아닌 한 글자 |
\n | newline |
\r | carriage return |
\t | tab |
\f | formfeed |
\b | []내부에서 사용될 경우 backspace |
\0, \000 | null character |
\nnn | nnn이라는 8진수의 값을 가지는 ASCII 한 글자 |
\xnn | nn이라는 16진수의 값을 가지는 ASCII 한 글자 |
\cX | Ctrl+X에 해당하는 한 글자 |
\metachar | \|, \*, \\, \(, \), \[, \{, \^, \$, \+, \?, \.와 같이 표현되며 |, *, \, (, ), [, {, ^, $, +, ?, .의 의미를 가지는 한 글자 |
(abc) | 뒤에 가서 참조될(backreference) abc라는 문자열 |
\1 | 첫 번째 참조 문자열, open parenthesis, '('에 의해 순서가 결정됨 |
\2 | 두 번째 참조 문자열 |
\n | n번째 참조 문자열, 10이상일 경우 backreference될 문자열이 없으면, 8진수 값의 ASCII 한 글자의 의미를 가질 수 있다. |
x? | x가 나오지 않거나 1번 나오는 경우 |
x* | x가 0번 이상 반복되는 문자열 |
x+ | x가 1번 이상 반복되는 문자열 |
x{m,n} | x가 m번 이상, n번 미만으로 나오는 문자열 |
x{m,} | x가 m번 이상 나오는 문자열 |
x{m} | x가 m번 나오는 문자열 |
abc | a와 b와 c가 연속으로 붙어서 나오는 문자열 |
abc|def|ghi | 문자열 abc, def, ghi 중의 하나에 해당되는 경우 |
다음은 크기가 없는, 지정된 위치가 일치하도록 제시할 수 있는 위치 표시 조건(assertion)들을 나열한 표이다.
\b | []바깥에서 \w와 \W가 서로 바뀌고 있는 word boundary를 의미함 |
\B | \b가 아닌 경우 |
\A | string의 처음 위치 |
\Z | string의 마지막 위치 |
\G | 마지막으로 m//g에 의해 match가 된 위치 |
^ | string의 가장 처음 위치 표시로 /m modifier가 있으면 line의 처음을 의미한다. |
$ | string의 가장 마지막 위치 표시로 /m modifier가 있으면 line의 끝을 의미한다. |
(?=...) | ...위치에 놓일 문자열이 매치 된다는 조건 |
(?!...) | ...위치에 놓일 문자열이 매치 되지 않는다는 조건 |
다음 예문을 가지고 regular expression으로 pattern matching을 하는 간단한 예를 선보이기로 한다. 예문은 각 줄 끝마다 newline이 붙어있는 것으로 가정한다.
In France a man who has ruined himself for women is generally regarded with sympathy and admiration; there is a feeling that it was worth while, and the man who has done it feels even a certain pride in the fact; in England he will be thought and will think himself a damned fool. That is why Antony and Cleopatra has always been the least popular of Shakespeare's greater plays.
/France/; # 1번째 줄 /(women|there|That) is/; # 1, 2, 4번째 줄 /\we{2}\w/; # feel과 been, 3, 5번째 줄 /is$/; # 4번째 줄 /^England/; # 4번째 줄 if (/Engl(\w+)/) { print "Engl$1"; # English, England } print if /the/; # there, the, the, 2, 3, 5번째 줄 print if /\bthe\b/; # the, the, 3, 5번째 줄 print if /man|women/; # man, man, 1, 3번째 줄 if ( /(feel(s|ing))/ ) { print $1; # feeling, feels, 2, 3번째 줄 } if (/([A-Z]([a-z][a-z]+))/) { print $2; # rance, ngland, ntony }
주목할 것은 /man|women/에서 women이라는 단어가 단락 내에 존재함에도 불구하고 match가 일어나지는 않는다는 것이다. 자세한 이유는 modifier에 관한 설명을 참조하도록 하자. 다음은 단순한 기호들의 나열 같아서 약간 더 어려울 것 같은 예제를 골라보았다.
(0|0x)\d*\s\1\d* # 0x1234 0x4321같은 16진수 두 개에서 match된다. /.*foo/ # foo로 끝나는 단어에서 match된다. /^(\d+\.?\d*|\.\d+)$/; # 올바른 소수표현에서 match가 일어난다.
다음은 the를 관사로 가지는 명사들을 모두 출력하는 예제 프로그램이다.
while (<>) { if (/\bthe\b\s+(\w+)/g) { print "$1\n"; } }
2) match, substitute, translate
match는 지정되어 있는 regular expression으로 표현 가능한 substring이 존재하는가의 여부를 결정할 때 사용하는 연산이고, m이 그 operator이다. 이와 비슷한 종류의 연산자로서, regular expression으로 표현된 substring을 치환(substitute)해주는 s///연산자와 표현식의 각 글자를 해석(translate)하고 변환해주는 tr///연산자가 있다. 다음은 각 연산자들의 형식이다.
연산자//modifier 또는 연산자///modifier
문장 내에서 흔히 실수하기 쉬운, teh라고 잘못 친 부분을 the로 바꾸어 주는 연산을 다음과 같이 구현할 수 있다.
s/\b(teh)\b/the/g;
g가 바로 modifier인데, 각 연산마다 사용 가능한 modifier의 종류가 다르다.
m/PATTERN/gimosx /PATTERN/gimosx
match연산자는 흔히 생략해서 사용하였다. 앞에서 익혔던 패턴매치에서는 m연산자를 일부러 사용하지 않았던 것이다.
modifier | 의미 |
g | global, 문장 내에서 여러 번 match가 일어날 때마다 |
i | case-insensitive, 대소문자 구분 없이 |
m | multiple-line, 여러 line을 한꺼번에 사용함 |
o | compile once, compile시에 pattern을 딱 한번 해석함 |
s | single-line, string을 한 line으로 가정함 |
v | Extended Regular expression을 사용함 |
다음의 두 예제를 비교해 보도록 하자.
if (/(man|women)/g) { print $1; } if (/(man|women)/) { print $1; }
첫 번째 예제의 결과는 man, women, man이 되지만, 두 번째 예제의 결과는 man, man뿐이다. 결과가 달라지는 이유는 g modifier때문인데, 한 문장에 여러 번 match가 일어날 경우, 매번 처리를 할 것인지(g modifier사용), 아니면 다음 문장으로 넘어갈 것인지를 modifier를 보고 결정하기 때문이다.
substitute연산자는 생략가능하지도, 동의어 연산자도 존재하지도 않는다. 그러나 pattern match류의 연산자들 중에서 가장 빈번하게 사용되는 연산자이다.
s/PATTERN/REPLACEMENT/egimosx
e | expression, REPLACEMENT부분에도 expression을 사용함 |
g | 문장 내에서 match가 일어날 때마다 매번 처리 |
i | 대소문자 구분 없이 |
m | multi-line mode |
o | 단 한번만 compile함 |
s | single-line mode |
x | Extended regular expression을 사용함 |
다음은 substitute 연산자를 사용한 예이다.
s/^([^ ]+) +([^ ]+)/$2 $1/; # 두 단어의 순서를 바꾸어준다. s/red/green/g; # 문장 내의 모든 red를 green으로 대치한다. s/\bt(e)(h)\b/t$1$2/g; # teh를 the로 고쳐준다. $1과 $2는 backreference s/(\d)(\d\)\.(\d+)/$1\.$2$3/; # 42.9을 4.29로 바꾸는 식으로 소수점을 옮긴다.
translate연산은 위의 두 연산과는 약간 다르다. 첫 번째로는 string에 대한 조작이 아니라 표현식 내의 글자 하나 하나에 대한 해석과 변환에 관련된 조작이고, 두 번째로는 표현식에는 regular expression을 사용하지 않는다는 점이다.
tr/SEARCHLIST/REPLACEMENT/cds; y/SEARCHLIST/REPLACEMENT/cds;
y는 tr의 동의어로 사용된다. SEARCHLIST에 나오는 글자들은 REPLACEMENT에 나오는 글자들과 n대 n 또는 n대 1로 대응이 되어 변환이 이루어진다. 다음의 예제들을 참고하여 이해하도록 하자.
tr/ABC/XYZ/;
A는 X로, B는 Y로, C는 Z로 바뀐다. 그러므로 ABVWSDC라는 문자열은 XYVWSDZ로 바뀌게 되는 것이다.
y/0-9//; tr [A-Z] [a-z];
문자열 내의 숫자는 모두 제거된다. 문자열 ABC0123DEF는 ABCDEF로 바뀌게 된다. 모든 대문자가 소문자로 바뀐다. "I am a BoY"는 "i am a boy"가 될 것이다.
translate연산이 위의 두 연산과 많이 다른 만큼, modifier 또한 크게 다른 의미를 가진다.
c | complement, SEARCHLIST에 없는 글자가 처리된다. |
d | delete, 발견되었지만 처리되지 않은 글자를 지운다. |
s | squash, 동일하게 반복된 글자를 빼낸다. |
다음은 modifier를 사용한 translate연산의 예이다.
tr/a-zA-Z/ /c; # 알파벳이 아닌 글자가 space로 바뀐다. tr/a-zA-Z//s; # "I've been a fool."이 "I've ben a fol."로 바뀐다.
Perl 4까지는 multi-line/single-line mode를 사용하기 위해 global special variable 중의 $*를 이용하였다. pattern match 연산을 하기 전에 $*을 1로, $" = ""로 지정하면 multi-line mode로 바꿀 수 있었다. Perl 5의 global special variable에 $*가 언급되지 않는 것으로 미루어보아, 이 방법 대신에 위에서 밝힌 바와 같이 modifier를 사용하는 방법이 안전할 것이다.
3) 문자열 pattern 조작
문자열 변수에 대해서 pattern match 관련 연산을 하는 방법은 =~(pattern binding operator)연산자를 사용하는 것이다. 이 연산자는 지정된 변수를 pattern에 대해 match시켜보거나 substitute하거나 translate하도록 해준다. 마치 대입연산자 ~=와 비슷하게 보이지만 대입연산자로 사용될 수 없는 연산자이다.
($a = $b) =~ s/red/green/; $cnt = tr/*/*/; $cnt = $sky =~ tr/*/*/;
첫 번째 문장에서 괄호는 생략가능한데, 우선은 $b에 대해서 substitute연산이 일어나고 그 결과 값이 $a에 들어가게 된다. 다시 말해서 문자열 중에서 red가 모두 green으로 바뀌어진 문자열 전체가 $a에 대입되는 것이다. 두 번째 문장에서 tr연산자는 default input string에서 '*' character를 찾아서('*'는 interpolation과 관련 없다.) 그 개수를 세어준다.
vector형 자료구조인 배열이나 해시에 대해서는 pattern match를 할 수가 없고, 원소를 하나씩 꺼내어 그 scalar형 원소에 대해서 작업을 반복할 수 있다. 다음 예제에서 나오는 scalar연산은 vector형 변수의 원소의 수를 알려준다.
@list = (1, 2, 3, 4, 5, 6, 7); for ($i = 0; $i < scalar(@list); $i++) { $list[$i] =~ s/3/9/g; } print @list;
file에 대해서 pattern match 관련작업을 하는 것은 vector형 변수에 대해서 작업하는 것과 마찬가지의 방법이 필요하다. 한 문장씩 읽어 들여서 그 문장을 변수에 저장하고 그 변수에 pattern match 연산을 하는 것이다.
open(FILE1, "testfile.1") || die "Can't open such a file!\n"; while () { s/\r//g; print; }
DOS상에서 만들어진 파일은 리턴 키를 입력할 때마다 \r과 \n이 파일에 쓰여지는데 반해, Unix에서 리턴 키는 \n만을 써줄 뿐이다. 그래서 DOS파일을 Unix에 옮겨오는 경우, ^M이라는 이상한 글자(실제로는 \r이 터미널에 보이는 형태일 뿐이다.)가 매 줄 끝마다 보이게 된다. 이러한 글자를 제거해준 결과를 보여주는 예제를 제시한 것이다. Perl script로 쓰게 되면 더 간단하게 사용할 수 있는데, 다음의 코드를 d2u.pl이라는 이름으로 작성한 후에, 실행 가능하게 permission mode를 바꾸어주고(chmod a+x d2u.pl) 도스파일에 대해서 실행(d2u.pl dostext.txt)하여 보자.
#!/usr/local/bin/perl -pi s/\r//g;
작성자 : 조영일, 서울대학교 전산과학과
본인이 사용하는 Blog서버가 BlogAPI를 지원한다면, MS Word 2007의 blog 게시 기능을 사용하여 blog에 글을 게시 할 수 있다.
[출처:http://kelp.or.kr]
글쓴이 : 유영창 (2004년 09월 03일 오전 05:25) 읽은수: 1,905 [ 임베디드강좌/유영창 ]
ADK001 Makefile 작성법
===================================
1. 개요
이 문서는 ESP-NS에서 동작하는 응용 프로그램을 작성하기 위해서
Makefile을 만드는 방법을 정리한 문서입니다.
작성자 : 유영창 frog@falinux.com
작성일 : 2004년 9월 3일
관련된 ADK( Application Developer Kit ) 디렉토리
adk/sample/lib_Makefile
adk/sample/app_Makefile
2. Makefile 작성법
리눅스에서 동작하는 프로그램을 작성하기 위해서는 반드시 크로스 컴파일
과정을 거쳐야 합니다.
아주 간단한 hello.c 라는 프로그램이 다음과 같은 소스로 작성되었다고 한다면
#include <stdio.h>
int main( int argc, char **argv )
{
printf( "hello\n" );
}
이것을 컴파일 하기 위해서 다음과 같이 처리하면됩니다.
# armv5l-linux-gcc -o hello hello.c
프로그램 소스에 에러가 없어서 정상적으로 이 명령이 수행되었다면
수행 결과로서 hello 라는 프로그램 실행파일이 생성됩니다.
물론 이 실행파일은 PC 시스템에서는 동작하지 않습니다. arm 용 명령
코드를 가지는 실행파일이기 때문입니다. 그래서 이것을 실행해 보려면
ESP-NS 보드에 옮긴후에 수행해야 합니다.
이렇게 프로그램 소스가 하나일 경우에는 간단히 위와 같은 명령으로
처리해도 되지만 저 같은 게으른 사람에게는 위 명령을 컴파일을 할때
마다 매번 타이핑 하려면 죽음입니다. 물론 명령행에서 위 화살표키를
이용하는 방법도 있지만 그것도 여러가지 명령을 수행했다면 컴파일 명령만
따로 찾는것도 귀찮습니다.
더구나 여러가지 소스로 이루어진 프로그램을 컴파일한다면 그냥 때려치고
싶은 마음이 생깁니다.
이렇게 게으른 사람들에게는 Makefile을 만들어서 make 명령으로 하는 것이
아무래도 스트레스를 덜 받아서 수명연장에 도움이 됩니다.
Makefile을 만들때 딱 한번만 스트레스를 받으면 되기 때문입니다.
그.러.나.
Makefile을 어떻게 만들어야 하는가를 알아 보려고 인터넷을 뒤지거나
책을 사보면 그 방대한 양에 포기하고 싶은 마음이 굴뚝 같아 집니다.
도리어 그냥 타이핑 치는것이 마음이 편할수 있읍니다.
그.래.서
저는 아예 고정적으로 Makefile을 하나 만들어서 매번 수정해서 사용합니다.
그래서 여러분에게도 제가 사용하는 방법을 권장합니다. 여기서 소개하는
Makefile이 마음에 안 드신다면 직접 만들어서 사용해도 무방합니다. ㅜㅜ
제가 주로 사용하는 Makefile은 두가지 입니다.
하나는 자주 사용하는 루틴을 모아서 라이브러리로 만들기 위한 것과
응용 프로그램을 작성하기 위해서 사용하는 Makefile입니다.
2.1 라이브러리를 만드는 Makefile
제가 라이브러리를 만들기 위해서 사용하는 Mafefile은 다음과 같습니다.
==[Makefile]=====================================================================
# Compile Option
TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)
# --- 기본적으로 수정되는 내용들 ! -----------------------------------
TARGET = libsample.a
OBJS = func1.o
INCLUDEDIR =
#---------------------------------------------------------------------
# --- 크로스 컴파일과 관련된 내용은 여기를 수정할것 ! ----------------
CROSS_PREFIX = /usr/armv5l-linux
CC = armv5l-linux-gcc
CXX = armv5l-linux-gcc
AR = armv5l-linux-ar rc
AR2 = armv5l-linux-ranlib
RANLIB = armv5l-linux-ranlib
LD = armv5l-linux-ld
NM = armv5l-linux-nm
STRIP = armv5l-linux-strip
OBJCOPY = armv5l-linux-objcopy
#---------------------------------------------------------------------
SRCS = $(OBJS:.o=.c)
CFLAGS += $(INCLUDEDIRS)
CFLAGS += -Wall -O2 -g
CPPFLAGS +=
CXXFLAGS +=
INCLUDEDIRS += -I./ -I$(TOPDIR)/include -I$(CROSS_PREFIX)/include $(INCLUDEDIR)
#
# Compilation target for C files
#
%.o:%.c
@echo "Compiling $< ..."
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
#
# Compilation target for C++ files
#
%.o:%.cc
@echo "C++ compiling $< ..."
$(CXX) -c $(CFLAGS) $(CXXFLAGS) -o $@ $<
all : $(TARGET)
$(TARGET) : $(OBJS)
$(RM) $(TARGET)
$(AR) $(TARGET) $(OBJS)
$(AR2) $(TARGET)
dep :
$(CC) -M $(INCLUDEDIRS) $(SRCS) > .depend
clean :
rm -rf $(OBJS) $(TARGET) core
ifeq (.depend,$(wildcard .depend))
include .depend
endif
==[Makefile]=====================================================================
이 Makfile을 사용할때 주로 수정하는 부분은 다음과 같습니다.
TARGET = libsample.a
OBJS = func1.o
INCLUDEDIR =
TARGET은 최종적으로 만들려고 하는 라이브러리 파일명입니다
라이브러리 파일명은 일정한 규칙이 있읍니다. 처음에 시작하는 것은 lib로 시작해야 하고
끝날때는 .a 로 끝나야 합니다.
예를 sample 이라는 이름을 가진 라이브러리 파일을 만들려고 했다면 libsample.a 라는 이름을
지정해야합니다. 컴파일 결과로 만들어지는 파일명은 당연히 libsample.a 입니다 .
이렇게 지정하는 것은 나중에 응용 프로그램을 만들때 지정하는 명칭 때문입니다.
OBJS은 TARGET에 지정한 라이브러리에 포함되는 오브젝트 명을 나열합니다. 여러 파일로
구성될때는 빈칸으로 오브젝트 파일을 구분합니다.
위 예에서는 하나의 파일에 대한 라이브러리를 만들었지만 func1.c 와 func2.c 로
구성되는 라이브러리를 만든다면 다음과 같이 지정해야 합니다.
OBJS = func1.o func2.o
너무 많은 소스 파일로 구성된다면 아무래도 여러줄로 나누어서 쓰는 것이 좋지요
이럴때는 다음 처럼 하면됩니다.
OBJS = func1.o \
func2.o
이렇게 할때 중요한것은 '\' 뒤에 바로 리턴으로 나누어져야 한다는 겁니다
만약에 뒤에 빈 공백 문자나 다른 문자가 있으면 문제가 됩니다.
INCLUDEDIR은 소스가 참조하는 헤더파일 디렉토리를 추가 할때 사용합니다.
예를 들어 /test/include 라는 디렉토리를 추가 하고 싶으면
INCLUDEDIR = -I/test/include
라고 하면 됩니다. ( -I 의 영문자는 아이[I] 입니다. 엘[L]소문자와 무척 헷갈리므로 조심하십시오 )
기본적으로 이 Makefile은 다음과 같은 디렉토리의 헤더파일들이 참조됩니다.
1. Makefile 이 포함된 디렉토리에 있는 헤더파일들
2. Makefile 이 포함된 상위 디렉토리 밑에 있는 include 디렉토리
3. 크로스 컴파일러에서 제공하는 include 디렉토리
ESP-NS는 /usr/armv5l-linux/include 가 됩니다.
이 Makefile을 이용하여 컴파일한다면 다음과 같은 과정을 거치면 됩니다.
# make clean
# make dep
# make
make clean은 이전에 생성된 오브젝트 파일을 지우기 위해서 사용합니다.
make dep는 컴파일 대상이 되는 헤더파일들의 의존성 체크를 위한 파일 목록을 만들기 위해서
사용합니다. 이 명령의 수행결과로 .depend가 생깁니다.
make dep는 소스상에 새로운 헤더파일을 추가 했을때 수행해 주면됩니다. 물론 자신이 만들어서
수정될 수 있는 헤더파일을 추가했을 경우만 해주면됩니다.
보통 프로그램을 작성하다보면 헤더파일을 수정했는데 컴파일 타켓 대상이 되지 않아서 자신이
의도한 결과와 다른 결과를 만들수 있읍니다. 그래서 헤더파일이 변경되었을때 해당 헤더파일을
참조하는 모든 소스가 수정되도록 하기 위해서 이 명령을 사용합니다.
make clean은 보통 잘 사용하지 않아도 되지만 전체 소스를 재 컴파일 하고 싶을때 사용하면 됩니다.
한번 컴파일 되어서 수정되지 않는 소스의 오브젝트 파일은 컴파일 대상에서 제외 되기 때문에
가끔 의도하지 않는 일이 생길때가 있읍니다. 그럴때 확실하게 전체를 컴파일 하고 싶을때 사용하면
됩니다.
보통은 make 명령 하나로 끝낼수 있읍니다.
저는 생성된 라이브러리 파일과 라이브러리 파일을 사용하기 위한 헤더파일을
자동으로 목적지로 복사하는 기능도 Makefile에 포함시켜서 사용하는데 여기서는 제거했읍니다.
아무래도 자동보다는 수동이 확실합니다. ^^
위에서 제시한 Makefile 은 func1.c 소스를 컴파일 해서 libsample.a 라는 파일을 만들게 됩니다.
2.2 응용 프로그램을 만드는 Makefile
제가 응용 프로그램을 만들기 위해서 사용하는 Mafefile은 다음과 같습니다.
==[Makefile]=====================================================================
# Compile Option
TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)
# --- 기본적으로 수정되는 내용들 ! -----------------------------------
TARGET = test
OBJS = main.o
LIBS = -lsample
INCLUDEDIR =
LIBDIR =
#---------------------------------------------------------------------
# --- 크로스 컴파일과 관련된 내용은 여기를 수정할것 ! ----------------
CROSS_PREFIX = /usr/armv5l-linux
CC = armv5l-linux-gcc
CXX = armv5l-linux-gcc
AR = armv5l-linux-ar rc
AR2 = armv5l-linux-ranlib
RANLIB = armv5l-linux-ranlib
LD = armv5l-linux-ld
NM = armv5l-linux-nm
STRIP = armv5l-linux-strip
OBJCOPY = armv5l-linux-objcopy
#---------------------------------------------------------------------
SRCS = $(OBJS:.o=.c)
CFLAGS += $(INCLUDEDIRS)
CFLAGS += -Wall -O2 -g
CPPFLAGS +=
CXXFLAGS +=
INCLUDEDIRS += -I./ -I$(TOPDIR)/include -I$(CROSS_PREFIX)/include $(INCLUDEDIR)
LDFLAGS += -L./ -L$(TOPDIR)/lib -L$(CROSS_PREFIX)/lib $(LIBDIR)
#
# Compilation target for C files
#
%.o:%.c
@echo "Compiling $< ..."
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
#
# Compilation target for C++ files
#
%.o:%.cc
@echo "C++ compiling $< ..."
$(CXX) -c $(CFLAGS) $(CXXFLAGS) -o $@ $<
all : $(TARGET)
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LIBS)
cp $(TARGET) /nfsesp/fgl/
dep :
$(CC) -M $(INCLUDEDIRS) $(SRCS) > .depend
clean :
rm -rf $(OBJS) $(TARGET) core
ifeq (.depend,$(wildcard .depend))
include .depend
endif
==[Makefile]=====================================================================
이 Makefile 역시 라이브러리를 만드는 것과 무척 유사합니다.
이 Makfile을 사용할때 주로 수정하는 부분은 다음과 같습니다.
TARGET = test
OBJS = main.o
LIBS = -lsample
INCLUDEDIR =
LIBDIR =
TARGET은 최종적으로 만들려고 하는 실행파일명입니다. 여기서는 test 라는 실행파일명을 만듭니다.
윈도우 프로그램을 하시던 분들의 입장에서 보면 실행파일명에 확장자가 없는것이 이상하겠지만
리눅스에서는 확장자가 없는 파일이면 거의가 실행파일명이거나 실행가능한 스크립트일 가능성이
높습니다. 도리어 데이터 파일들에 확장자를 붙이는 것이 관례입니다.
OBJS은 TARGET에 지정한 응용프로그램을 구성하는 소스가 컴파일되어야 하는 오브젝트 명을 나열합니다.
이것 역시 라이브러리의 Makefile과 같은 방법으로 여러 파일로 지정합니다.
LIBS은 포함될 라이브러리를 파일을 지정할때 사용합니다.
위 예에서는 libsample.a 라는 라이브러리를 지정합것입니다.
라이브러리 파일은 지정한 것에 컴파일러가 앞에 'lib' 와 뒤에 '.a'를 자동으로 지정합니다.
이점을 주의해야 합니다.
이 예는 앞에서 만든 라이브러리 파일을 이용하기 때문에 -lsample만 지정하면 됩니다.
( -l 의 영문자는 엘[L]의 소문자로 옵션입니다 )
INCLUDEDIR은 소스가 참조하는 헤더파일 디렉토리를 추가 할때 사용합니다.
예를 들어 /test/include 라는 디렉토리를 추가 하고 싶으면
INCLUDEDIR = -I/test/include
라고 하면 됩니다. ( -I 의 영문자는 아이[I] 입니다. 엘[L]소문자와 무척 헷갈리므로 조심하십시오 )
LIBDIR은 라이브러리가 포함된 디렉토리를 추가 할때 사용합니다.
예를 들어 /test/lib 라는 디렉토리를 추가 하고 싶으면
LIBDIR = -L/test/lib
라고 하면 됩니다.
기본적으로 이 Makefile은 다음과 같은 라이브러리 디렉토리를 참조합니다.
1. Makefile 이 포함된 디렉토리에 있는 라이브러리
2. Makefile 이 포함된 상위 디렉토리 밑에 있는 lib 디렉토리
3. 크로스 컴파일러에서 제공하는 lib 디렉토리
ESP-NS는 /usr/armv5l-linux/lib가 됩니다.
이 Makefile을 이용하여 컴파일한다면 다음과 같은 과정을 거치면 됩니다.
# make clean
# make dep
# make
이것 역시 라이브러리 Makefile과 같습니다. 글이 많은 것 처럼 보이기 위해서
다시 설명합니다. ^^
make clean은 이전에 생성된 오브젝트 파일을 지우기 위해서 사용합니다.
make dep는 컴파일 대상이 되는 헤더파일들의 의존성 체크를 위한 파일 목록을 만들기 위해서
사용합니다. 이 명령의 수행결과로 .depend가 생깁니다.
make dep는 소스상에 새로운 헤더파일을 추가 했을때 수행해 주면됩니다. 물론 자신이 만들어서
수정될 수 있는 헤더파일을 추가했을 경우만 해주면됩니다.
보통 프로그램을 작성하다보면 헤더파일을 수정했는데 컴파일 타켓 대상이 되지 않아서 자신이
의도한 결과와 다른 결과를 만들수 있읍니다. 그래서 헤더파일이 변경되었을때 해당 헤더파일을
참조하는 모든 소스가 수정되도록 하기 위해서 이 명령을 사용합니다.
make clean은 보통 잘 사용하지 않아도 되지만 전체 소스를 재 컴파일 하고 싶을때 사용하면 됩니다.
한번 컴파일 되어서 수정되지 않는 소스의 오브젝트 파일은 컴파일 대상에서 제외 되기 때문에
가끔 의도하지 않는 일이 생길때가 있읍니다. 그럴때 확실하게 전체를 컴파일 하고 싶을때 사용하면
됩니다.
보통은 make 명령 하나로 끝낼수 있읍니다.
저는 생성된 라이브러리 파일과 라이브러리 파일을 사용하기 위한 헤더파일을
자동으로 목적지로 복사하는 기능도 Makefile에 포함시켜서 사용하는데 여기서는 제거했읍니다.
아무래도 자동보다는 수동이 확실합니다. ^^
위에서 제시한 Makefile 은 main.c 소스를 컴파일 하고 libsample.a 라는 라이브러리를
포함혀여 test 라는 실행파일을 만들게 됩니다.
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LIBS)
cp $(TARGET) /nfsesp/fgl/
부분에서
cp $(TARGET) /nfsesp/fgl/
은 nfs 환경을 구축해서 ESP 보드의 응용 프로그램을 디버깅한다면
자동으로 컴파일된 실행파일을 /nfsesp 라는 디렉토리에 옮기기 위해서
사용하는 것입니다
필요없다면 맨 앞에 '#'을 이용해서 주석처리하면 됩니다.
Makefile은 주석으로 사용되는 라인은 가장 처음 '#'이 포함되어야 합니다.
2.3 기타
잘 수정하지 않는 것중에
CFLAGS += -Wall -O2 -g
CPPFLAGS +=
CXXFLAGS +=
이 두가지가 있읍니다.
하지만 가끔 필요에 의해서 수정하는데 CFLAGS와 CPPFLAGS는 c나 c++ 소스에 대한 컴파일 조건에 대한 수정이 필요
할때 고치면 되고 CXXFLAGS 는 c++ 에 대한 컴파일 조건에 대한 수정이 필요할때 상용합니다.
잘 고치지 않는 부분이므로 여기서는 더이상의 설명을 하지 않으려 합니다. 자세한 것을 알고 싶은 분은
Makefile에 대하여 공부하시고 위 예를 분석하시면 답이 나올 겁니다. (우하하하)
참고로 -g 는 디버그 정보를 포함하라고 하는 것입니다
보통 이 옵션은 포함시키는 것이 좋습니다. 실행파일이 커지는 단점이 있지만 무시할만 하고
나중에 디버깅 단계에서 역 어셈블러를 한다든가 문제가 발생한 부분을 추적할때 좋으므로
가급적 그냥 두시기 바랍니다.
ESP 보드에 용량문제가 된다면 그때 제거하시면 됩니다.
마지막으로 주의 사항 한가지는 Makefile은 명령앞에 tab문자가 포함되어야 합니다.
혹시 이글의 예제를 복사하시다가 tab 문자가 제거 되는 에디터를 이용해서
편집하시면 낭패를 당하실수 있음을 엄중히 경고합니다.