|
펄은 언어학자가 만든 프로그래밍 언어이기 때문에 접근 방법이 여타 언어와 조금은 다릅니다. 물론 펄은 극도로 유연한 언어이기 때문에 전통적인 프로그래밍 언어와 거의 유사한 형태로 코딩을 할 수도 있습니다. 부드러운 흐름으로 잘 코딩된 C 프로그램과 똑같이 변수도 일일이 선언하고 깔끔하게 코딩할 수도 있죠. 한편 첫 시간에 말씀드린대로 펄 프로그램은 매우 난잡해 보이게도 짤 수 있습니다. 암묵적(implicit)으로 이뤄지는 작업이 많은데다가 어순에 있어서도 굉장한 유연성이 보장된 언어가 바로 펄이기 때문이죠. 그런 점들이 모두 펄을 만든 래리월 선생님이 자연어와 비슷한 프로그래밍 언어를 만들겠다는 생각을 갖고 있었기 때문입니다.
래리월 선생님이 직접 쓴 펄의 교과서 "Programming Perl"(캐멀북) 4장, "Statement and Declaration" 중 statement 부분을 정리해 볼까 합니다.
단문 (Simple Statements)
단문, 복문.
흡사 영어 문법책에서나 봤음직한 단어들이 나옵니다.
단문은 세미콜론; 으로 끝나며 그 문장 자체가 직접 어떤 기능을 하는 표현 (expression) 을 가르킵니다. 예를들면,
$a++;
$string = 'abc';
print "$myname is cool !";
이러한 단문은 그 뒤에 modifier가 붙어서 의미를 변화시킬 수 있습니다. 단문 뒤에 붙일 수 있는 modifier로는,
if EXPR
unless EXPR
while EXPR
until EXPR
foreach LIST
for LIST
등 입니다. EXPR는 expression의 약자죠. 예를들어 봅시다.
++$num while <MYFILE>;
push @even, $number if ($number % 2 == 0);
print $_ , "\n" foreach (@items);
이런 것들도 모두 단문인 것입니다. 그런데 여기서 잘 보세요. 두번째, 세번째 예는 이렇게 풀어 쓸 수도 있는 것이죠.
if ($number % 2 == 0) {
push @even, $number;
}
foreach (@items) {
print $_ , "\n";
}
바로 이런 형태가 C나 자바등의 언어에서 주로 쓰는 방식입니다. (이것이 뒤에 나올 복문의 형태입니다) 그런데 펄은 이것을 위에 나온 것처럼 한 줄로 줄일 수 있고, 또 그렇게 쓰는 것이 바로 펄의 스타일 입니다. 어순을 바꾸며 간결하게 쓰는 것이죠.
뭐 설명드릴 필요는 없겠습니다만 위의 첫번째 코드는 MYFILE
이라는 파일핸들로 연 파일이 몇 줄인지를 세는 코드입니다. 두번째 예는 $number
를 2로 나눠서 나머지가 0 이면 @even
이라는 배열에 $number
를 넣는 것이고, 세번째 예는 @items
라는 배열에 담긴 각각의 원소들을 줄을 바꿔가면서 출력해주는 코드 입니다.
더 예를들어 보면,
s/perl/java/ for @resumes;
$count-- until $fire;
첫번째 코드는 @resumes
라는 배열에 담긴 항목 하나 하나에 대해서 "perl"을 "java"로 바꿔주는 코드 입니다. s/somepattern/replacement/
는 레귤라익스프레션에서 자세하게 다룰 것입니다만 우선은 somepattern 을 replacement 로 바꿔주는 코드라고 아시면 됩니다. 두번째 코드는 $fire
가 참이될때까지 $count
값을 하나씩 줄여가는 코드입니다. 첫번째 코드는 수행하는 기능에 비해 코드가 굉장히 간소해 보이시죠? 위와 같은 것이 펄 코딩 스타일 입니다. for
루프에 대해서는 밑에서 설명이 나오므로 눈에 쉽게 들어오지 않더라도 우선은 넘어가시기 바랍니다.
이제 복문을 살펴보죠.
복문 (Compound Statements)
중괄호 {}
로 묶인 것을 BLOCK(대문자) 이라고 합니다. 이 중괄호는 변수의 scope을 정하는 역할을 하기도 하죠. 펄의 복문이라는 것은 이런 BLOCK이 있는 구문을 가르킵니다. 다음과 같은 것입니다.
if (EXPR) BLOCK
if (EXPR) BLOCK else BLOCK
if (EXPR) BLOCK elsif BLOCK ... else BLOCK
unless (EXPR) BLOCK
unless (EXPR) BLOCK else BLOCK
unless (EXPR) BLOCK elsif BLOCK ... else BLOCK
LABLE while (EXPR) BLOCK
LABLE while (EXPR) BLOCK continue BLOCK
LABLE until (EXPR) BLOCK
LABLE until (EXPR) BLOCK continue BLOCK
LABLE for (EXPR; EXPR; EXPR) BLOCK
LABLE foreach VAR (LIST) BLOCK
LABLE foreach VAR (LIST) BLOCK continue BLOCK
LABLE BLOCK
LABLE BLOCK continue BLOCK
복잡해 보이더라도 신경쓰지 마시고 가볍게 읽어보시면 됩니다.
우선 보시면 다들 BLOCK , 즉 { }
로 묶인 것이 부분이 있다는 것을 알 수 있죠. 위와 같은 것이 복문인 겁니다.
LABLE 은 어떤 BLOCK 을 문자그대로 '딱지 붙이는' 기능을 하는 것으로 있어도 되고 없어도 되는 것입니다.
위에 써놓은 것을 보시면 if
나 unless
의 경우는 EXPR이라는 스칼라 구문이 나오고 { }
이 나옵니다만 foreach 의 경우는 LIST 가 나오죠? 이런 것을 컨텍스트 라 한다고 했습니다. foreach
앞의 VAR, 즉 변수는 생략할 수 있다는 얘기도 이미 드렸습니다. 생략하는 경우엔 펄의 디폴트 변수인 $_
에 foreach
의 아이템 하나 하나가 담긴다고 했죠. 맨 마지막 2 개는 for, if, until
등이 나오지 않는, 순수한 { }
블락 입니다. 이런 것을 벌거벗은 블락(bare block) 이라고 합니다. 역시 자세한 것은 차차 나오므로 우선 형태만 구경해보세요.
재미있는 점은, 위의 if
문이나 until
문, for, foreach
문들 모두 앞의 단문에서 살펴본 대로 한 줄로 표현되기도 한다는 점입니다. 물론 { }
안에 담기는 구문이 하나 뿐일때만 가능한 것이지만 어쨌든 같은 기능을 하는 코드를 단문으로도 또 복문으로도 자유롭게 쓸 수 있다는 펄의 이 놀라운 유연성을 주목하시기 바랍니다. 구문이 한 줄인 경우엔 단문 형태로 쓰는 것이 훨씬 더 펄 다운 코딩 스타일이라는 점을 다시 말씀 드리구요. 실제 실행속도면에서도 다른 차이가 없다면 BLOCK 이 없는 경우가 있는 경우보다 더 빠릅니다. 즉, 속도를 생각한다면 복문보다는 단문형태로 쓰는것이 더 좋다는 것이죠.
이제 단문 복문에서 가볍게 살펴봤던 것들을 하나 하나 자세하게 알아 봅시다.
if , unless 구문
다른 프로그래밍 언어에도 다 있는 것이므로 그대로 이해하시면 됩니다. 예를들어,
if ($weather eq 'rain') {
$soju++;
}
elsif ($weather eq 'sunshine') {
$beer++;
}
else {
$water++;
}
아주 주의하셔야 할 점은 elsif
에 'e'가 없다는 점입니다. 주의하세요. 에러는 사소한 것 때문에 생기는 경우가 많습니다. 그리고 또 하나 주의하실 점은 if .. elsif .. else
중 참인것 하나만 실행이 된다는 것입니다. 위의 예에서 보면 만약 $weather
가 'rain' 이면 $soju
를 1 증가 시킨뒤 if..elsif..else
복문은 종료하는 것입니다.
여기서 조금 더 나가볼까요? 캐멀북의 24 장에 나오는 내용인데 아주 재밌습니다.
if ($a) { $foo = $a; }
elsif ($b) { $foo = $b; }
elsif ($c) { $foo = $c; }
이런 형태는 흔히 나올 수 있는 것이죠? 그런데 펄에서는 위와 같은 복문을 다음과 같은 단문으로 확~ 줄여쓸 수 있습니다.
$foo = $a || $b || $c;
놀랍죠? 비슷한 것으로 많이 사용되는 코드가 있습니다. 어떤 변수의 기본값(default value)을 설정하는 코드 입니다.
$pi ||= 3.14;
위 코드가 의미하는 것은 $pi
라는 변수에 특별한 값이 들어있지 않으면 3.14를 할당한다는 의미 입니다. 자세히 풀어서 보면 A || B
라는 것은 'A 또는 B' 입니다. A가 참이면 B는 실행되지 않는것이죠. A가 거짓이면 B가 실행됩니다. 따라서 $pi
가 참이면, 즉 $pi
라는 변수에 어떤 값이 담겨있으면 그대로 넘어가고 $pi
에 아무값도 담겨있지 않아서 거짓이 되면 $pi = 3.14
를 실행하게 되는 것이죠. 재밌지 않습니까? 꼭 외워두세요. 변수 기본값 설정 코드 입니다.
루프 구문 (Loop Statements)
루프(loop)는 아시는대로 어떤 작업을 반복적으로 수행하는 것입니다. 프로그래밍에 조금이라도 관심을 가져보셨던 분이라면 누구나 for , while
등과 같은 루프 구문에 대해 들어보셨을 겁니다. 펄에도 바로 그런 for, while
루프가 있고, 그 외에도 몇 가지 새로운 것이 있습니다. 그런데 루프는 반복적인 작업을 하는 것이기 때문에 '어떠 어떠한 경우에는 그 루프를 벗어난다', '어떠 어떠한 경우엔 진행하던 것을 멈추고 다음번 루프를 다시 시작한다'와 같은 흐름 조절이 필요하게 됩니다. 바로 이런 것을 loop control 이라 하고, 이와 같은 흐름 조절을 보다 더 정밀하게 하기 위해서 루프에 이름을 붙여줄 필요가 생깁니다. 예를들면 이런 것이죠. A라는 큰 루프 안에 B라는 작은 루프가 있는데 B루프를 돌다가 어떤 조건이 만족되면 다시 A 루프 처음으로 되돌아 가야하는 경우엔 어떻게 해야 할까요. A루프와 B루프를 구분지어줄 어떤 이름표 같은 것이 필요하지 않겠습니까? 바로 그런 이름표를 레이블(lable) 이라 합니다. 그리고 모든 루프에는 레이블을 붙일 수 있습니다. (물론 붙일 필요가 없으면 안 붙여도 됩니다)
그러면 대표적인 루프인 while
과 until
구문에 대해 알아보죠.
while , until 루프
while
구문은 어떤 조건이 참인 한 (while something is true..) 계속 루프를 돌게 됩니다. while
구문은 until
구문으로 바꿀 수 있는데 until
역시 문자 그대로 어떤 조건이 참이 될 때까지 (until something is true..) 루프를 돕니다.
while
루프는 주로 어떤 파일을 읽어들일 때 많이 쓰입니다. myfile.txt 라는 파일을 열어서 각 줄의 끝에 html 줄바꿈 태그 <br> 을 붙이는 코드는,
open MYFILE, "myfile.txt" or die ("can't open myfile.txt");
while (<MYFILE>) {
$_ .= "<br>"
}
느끼셨겠지만 위의 while 복문은 다음과 같이 쓰셔도 되죠.
$_ .= "<br>" while (<MYFILE>);
파일 읽어들이는 것은 사용자 입력 처리 에 이미 살펴본 내용이죠.
for 루프
for
루프는 for (A;B;C)
라는 형태로 괄호안에 3 개의 expression 을 넣어서 사용합니다. A에 해당하는 것은 초기화 (initialization), B 는 조건 (condition), C 는 재초기화 (reinitialization) 입니다.
for (my $i = 1; $i <= 10; $i++) {
...
}
$i에 초기화 값으로 1 을 넣고 루프를 시작, $i 가 10 보다 작거나 같은지를 테스트 해보고 그렇지 않으면 $i 를 1 증가시켜서 다시 초기화, 또 다시 루프를 시작하게 됩니다. 즉 그 다음엔 $i 가 2 로 할당된 다음 루프를 돌게 되는 것이죠.
for 루프 괄호 내의 세미콜론 사이에는 아무것도 넣지 않아도 됩니다. 그 경우 각각은 참으로 간주됩니다.
for(;;) {
print "-_-", "\n";
}
이것은 -_- 를 줄을 바꿔가며 계속 프린트 하게 됩니다. for ()
안이 계속 참이니깐 무한루프죠.
위의 코드는 while
구문으로 바꿀 수 있습니다. while
괄호안의 조건이 항상 참인 것을 써주면 되므로,
while(1) {
print "-_-", "\n";
}
입니다.
펄의 유연성은 for
구문에서도 예외가 아닙니다. 여러개의 변수를 동시에 변경해가면서 루프를 돌릴 수도 있습니다.
for ($i=65, $j=97; $i<=90, $j<=122; $i++, $j++) {
print chr($i), chr($j), "\n";
}
위 코드는 십진수 아스키값 65 (대문자 A) 와 십진수 아스키값 96 (소문자 a) 부터 Z, z 까지를 줄을 바꿔가며 출력해주는 코드 입니다. chr()
함수는 아스키값을 주면 문자로 바꿔주는 함수.
foreach 루프
foreach
루프는 for
루프와 항상 바꿔쓸 수 있는 거의 같은 것입니다. 둘 다 어떤 리스트(list)의 아이템 하나 하나를 갖고 루프를 도는 것이죠. 차이점이 있다면 foreach
의 경우엔 현재 몇 번째 아이템을 가지고 작업하는지를 알 수가 없다는 점이구요. 그런 경우엔 for
를 써야 합니다. 그리고 바로 그 점 때문에 foreach
루프가 for
루프보다 더 속도가 빠릅니다. foreach
는 리스트 아이템 하나 하나를 직접 접근하지만 for
의 경우는 순서를 나타내는 숫자를 통해서 접근하기 때문이죠. 실행속도를 생각하신다면 foreach
를 쓰시는게 더 유리합니다. foreach
는 이런 식으로 사용합니다.
$data = 'Perl:Python:PHP:ColdFusion:ASP';
foreach $value (split /:/, $data) {
print "$value", "\n";
}
위의 코드중 split /:/, $data
는 $data 에 담긴 값을 콜론: 별로 쪼개어서 그 값들을 리스트 형태로 되돌려주는 코드 입니다. 따라서 foreach
루프는 그렇게 반환된 리스트의 아이템 하나 하나를 $value 에 담아서 루프를 돌게 되는 것이죠. 위 코드를 실행해보시면 Perl, Python, .. 이 줄을 바꿔가면서 출력되는 것을 볼 수 있습니다. 위 코드에서 foreach 는 그대로 for 로 바꿔도 됩니다. for 나 foreach 모두 list context 를 끌고 다닌다고 했죠? for VAR (LIST), foreach VAR (LIST) 모두 가능한 것입니다.
그런데 펄에는 디폴트 변수 $_
가 있습니다. 즉 변수를 특별히 명시하지 않으면 알아서 $_
에 담기는 것이죠. 따라서 위의 코드중 foreach 루프는 다음과 같이 바꿀 수 있습니다.
foreach (split /:/, $data) {
print $_, "\n";
}
이 코드 역시 foreach
대신 for
를 써도 됩니다. for
나 foreach
모두 루프 변수를 명시하지 않으면 펄의 디폴트 변수인 $_
를 사용하게 됩니다. 그런데 위 코드는 앞에서 살펴본대로 블락 내부가 한 줄이므로 한 번 더 줄일 수 있을 것입니다.
print $_, "\n" foreach (split /:/, $data);
자.. 코드가 훨씬 더 펄 다워졌습니다. 이렇게 코딩하시는게 펄 스타일 입니다.
물론 C, Java 스타일로 차곡차곡 코딩하는것을 굳이 말리진 않겠습니다. ^_^
그러면 얘길 조금 더 진행시켜 보죠.
위의 루프 변수 $value 나 $_ 은 리스트의 아이템 하나 하나를 가르키는 일종의 alias (가상본, 바로가기) 입니다. 즉, 루프변수를 수정하면 그건 리스트내 아이템을 직접 수정하는 것과 같아지는 것이죠. 예를들어 위의 랭귀지 이름 뒤에 " language" 라는 단어를 모두 붙이고 싶다면 어떻게 하면 될까요.
print $_ . " language", "\n" foreach (split /:/, $data);
이렇게 하시면 되겠죠? 점 하나는 concatenation operator로 문자열을 합쳐주는 연산자라고 했습니다.
기왕 '펄 다운' 코드가 나온 김에 관용적으로 많이 쓰이는 것을 하나 더 말씀 드리겠습니다. 예를들어 1에서 100까지 숫자 중 홀수만 모으는 코드는 이렇게 할 수 있을 것입니다.
foreach (1..100) {
push @odd, $_ if ($_ % 2 == 1);
}
위의 코드도 사실 if (..)
구문을 단문 형태로 줄인 것입니다만, 펄은 여기서 멈추지 않습니다. 유닉스의 커맨드에도 있는 grep
이 펄에도 있거든요. 위의 코드는 다음과 같이 한 줄로 줄일 수 있습니다.
@odd = grep { $_ % 2 } 1..100 ;
아주 펄 다운 코드 입니다. 이 코드가 왜 위의 복문과 같은지 알아 보죠. 먼저 grep { .. } LIST
는 { }
뒤의 리스트에 있는 아이템 하나 하나를 $_
에 담아서 { }
안에 들어 있는 연산을 수행하는 것입니다. 그러므로 위의 코드는 1에서 100까지를 하나 하나씩 2로 나눠서 나머지가 '참' 이면, 즉 1 이면 @odd 에 결과를 담는 것이 됩니다. 결국 @odd 내에는 1에서 100까지 중 홀수들이 담겨 있게 되죠. 자주 나오는 것이니 잘 기억해 두세요.
루프 컨트롤 (Loop Control)
위에서 설명드린대로 루프란 것은 무한히 반복해서 도는 것이기 때문에 어떤 조건이 충족되면 루프를 탈출해라 라든지 어떤 값이 뭐가 되면 어디어디로 가라 등의 조절이 필요하게 됩니다. 이런 것을 담당해주는 것이 루프 컨트롤이며 '어디어디' 에 해당하는 것이 레이블 입니다. 루프 컨트롤에는 크게 3가지가 있습니다. next, last, redo
.
next
는 문자그대로 하던 작업을 멈추고 다음번 루프를 돌아라는 의미입니다.last
역시 문자그대로 루프의 마지막으로 가라는 의미입니다.redo
는 처음부터 다시 하라는 의미입니다.
실제 예를 보면 쉽게 이해가 되실 겁니다.
어떤 펄 파일을 열어서 주석문 (# 로 시작하는) 과 빈줄을 제외한 실제 내용이 있는 줄이 몇 줄인가를 세는 코드는 이런 형태가 됩니다.
open PERL, "myfile.pl" or die "can't open perl file";
while (<PERL>) {
# next 를 만나면 여기로 오게 됩니다
next if /^#/;
next if /^$/;
$count++;
}
print "총 $count 줄 입니다";
PERL 이라는 파일핸들로 펄 파일을 연 다음, 각각의 줄을 이용해서 루프를 도는데,
/^#/ , 즉 # 기호로 시작하는 줄이면 next
, 그러니깐 다시 다음 줄을 읽으러 가라는 얘기가 되죠. 주석문은 skip 하게 되는 것입니다. next if /^$/
도 마찬가지죠. 빈줄이면 다시 다음 줄을 읽으러 가라는 의미 입니다. next 는 하던 작업을 멈추고 다음번 루프를 돌려라는 뜻입니다. (/^#/ 등이 뭘 의미하는지 아직 모르시는 분은 나중에 레귤라익스프레션에서 자세히 다루므로 우선은 # 으로 시작하는지 여부를 매칭하는 코드다 정도로만 알고 계시면 됩니다) next if ..
일상 영어 그대로 읽히죠? 펄의 특징 입니다.
그런데 모든 BLOCK (중괄호로 묶여있는 코드) 은 이름표 (레이블) 을 붙일 수 있다고 했습니다. 즉 위의 코드는 이렇게도 쓸 수 있죠.
LINE: while (<PERL>) {
next LINE if /^#/;
next LINE if /^$/;
$count++;
}
"만약 # 로 시작하면 다음 줄" (next LINE if ..
)
아주 편안하게 읽힙니다. 물론 위의 코드에서는 굳이 레이블을 붙일 필요가 없습니다. 왜냐하면 BLOCK 이 한 단계 뿐이니깐요. next 나 last 등의 뒤에 레이블을 생략하면 가장 가까운 BLOCK 을 그 경계로 생각하게 됩니다. 그리고 레이블은 관례적으로 대문자로 씁니다. 마치 파일핸들 처럼요.
이번엔 last
예를 들어보죠. last
는 위에서 얘기한대로 루프를 끝내고 마지막으로 가라는 의미이므로 C 나 Java 등에서의 case
구문 같은 것을 만들 수 있습니다. (사실 펄에 switch
, case
구문이 없는 이유가 last
를 이용해서 표현가능하기 때문이라고 합니다. 펄 버전 6 에서는 case
구문이 추가될 예정이라고 합니다만)
SWITCH: {
if (/^abc/) { $abc = 1; last SWITCH }
if (/^def/) { $def = 1; last SWITCH }
$nothing = 1;
# last 를 만나면 여기로 오게 됩니다
}
쉽게 이해가 되시죠?
주의하실 점은 last
, next
등은 BLOCK 내에서만 유효하다는 것입니다. 즉 for
, foreach
, while
등과 같이 그것 자체가 BLOCK 을 이끄는 구문이 아닌 경우에는 따로 중괄호로 묶어서 BLOCK 을 만들어줄 필요가 있는 것입니다. 이렇게 의도적으로 중괄호를 앞뒤로 묶어서 일종의 루프처럼 만들어주는 것을 벌거벗은 블락, bare block 이라 합니다. 이것이 특히 많이 쓰이는 부분은 사용자 정의 함수 sub { }
에서 입니다. sub { }
는 루프가 아니기 때문에 그 내부에서 흐름조절이 필요한 경우 bare block 으로 묶어줘야 하는 경우가 많습니다.
sub add {
$url = shift;
{
last if ($url eq 'localhost'); # 내 컴퓨터에서 접속
last if ($url =~ /yahoo/i);
last if ($url =~ /google/i);
$url_list = $url . "\n";
# last 를 만나면 여기로 오게 됩니다
}
return $url_list;
}
위 코드는 add 라는 함수를 호출한 곳으로 부터 전달 받은 $url 이 localhost 거나 yahoo, google 이라는 단어가 들어가는 경우를 제외하고 따로 모아서 되돌려주는 코드 입니다. 보신대로 루프가 아닌 곳에서 last if .. 를 사용하기 위해서 bare block 을 사용했음을 알 수 있습니다.
또 하나 재미있는 것이 ? :
입니다. 이건 다른 언어에서도 많이 보셨을 겁니다. 펄도 가능합니다.
$grade =
($point < 10) ? "poor":
($point < 50) ? "room for improvement":
($point < 80) ? "great":
"excellent"; # 위에 해당되는 경우가 없으면
이 코드는 $point 의 크기에 따라서 $grade 에 "poor", "room for improvement", "great", "excellent" 를 담는 코드 입니다. (조건1)? a : (조건2)? b : (조건3)? c : d;
와 같은 형태로 사용하는 것입니다. 조건1을 만족하면 a, 조건2를 만족하면 b, .. 앞에 만족하는 조건이 없으면 d 라는 의미 입니다.
마지막으로 한 가지 부언을 하자면요.
BLOCK , 즉 중괄호로 감싸진 코드 중 마지막 줄에는 세미콜론을 사용하지 않아도 됩니다. 그렇지만 마지막 줄에도 세미콜론을 붙이는 습관을 들이시는게 좋은데요. 그건 나중에 그 BLOCK 내에 새로운 코드를 첨가할 일이 생기는 경우 그 전 마지막 줄 끝에 세미콜론을 붙이지 않았다는 사실을 잊고 있기가 쉽기 때문입니다. 세미콜론 하나 때문에 몇 시간 보내는 일이 드물지 않은 일이라는 것을 코딩을 조금만 해보셨어도 잘 알고 계실 겁니다.