Perl 문법설명

다음의 기사는 월간 프로그램 세계 1997년 6월호 특집기사로 실렸던 것입니다. 특집기사는 총 3부로 이루어져 있는데, 1부는 Perl의 소개, 2부는 Perl의 문법, 3부는 Perl을 이용한 CGI 예제 프로그램 작성으로 구성되어 있습니다. 제가 맡은 부분은 2부였고 총 분량의 절반에 해당합니다. 1부와 3부는 김대신(웹데이타뱅크)씨가 맡으셨습니다. 다음의 글은 제가 초안으로 작성한 텍스트 파일을 HTML문서로 약간 손을 본 것입니다.

차례

  1. 소개
  2. Perl의 문법
    1. 변수(Variable)
      1. 수치(number)
      2. 문자열(string)
      3. scalar variable
      4. vector variable
      5. 특수한 변수(Special variable)
      6. 레퍼런스(reference)
      7. 배열의 배열, 해시의 배열, 배열의 해시
    2. 식(Expression)
      1. 기본 연산자
      2. 추가 연산자
      3. 기본 입출력 연산자
      4. 비교 연산자
    3. Control Structure(제어 구조)
      1. if
      2. unless
      3. while/until
      4. for
      5. foreach
      6. do/while, do/until
      7. goto
      8. next, last, redo
    4. 서브루틴(Subroutine)
      1. 정의와 사용
      2. my와 local
    5. 패턴 매칭(Pattern Matching)
      1. 정규 표현식(regular expression)
      2. match, substitute, translate
      3. 문자열 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로 생각하는 것이다. 여러 줄에 걸쳐진 출력에 대해서 문서를 쓰듯이 출력할 수 있다는 게 장점이다.

print OUTPUT <
다만 이 HERE DOC방법에서 주의할 것은, 1) << 뒤에는 스페이스가 없어야 한다는 점, 2) 첫번째 delimiter 뒤에는 ;가 따라와야 한다는 점, 3) 두번째 delimiter 앞뒤에는 공백이 없어야 한다는 점이다.
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;

작성자 : 조영일, 서울대학교 전산과학과