Perl 함정 모음

Perl 함정 모음 (perltrap)

다른 언어에서 Perl로 넘어오면 '이건 왜 안 되지?' 하는 순간들이 꼭 생겨요. perltrap은 그런 함정들을 언어별로 정리한 문서예요. awk, C/C++, JavaScript, sed, shell에 익숙한 사람들이 Perl로 옮겨올 때 가장 자주 걸리는 지점을 콕 집어 알려줘요.

출처: perldoc - perltrap

가장 큰 함정들 (DESCRIPTION)

  • 가장 큰 함정use warnings 또는 -w 스위치를 잊어버리는 거예요 (warnings와 perlrun의 -w 참고).
  • 두 번째로 큰 함정은 프로그램 전체를 use strict 아래에서 돌릴 수 있게 만들지 않는 거예요.
  • 세 번째로 큰 함정은 이번 Perl 버전의 변경 목록을 읽지 않는 거예요 (perldelta 참고).

awk 사용자를 위한 함정 (Awk Traps)

awk에 익숙한 사용자라면 특히 다음을 주의하세요:

  • Perl 프로그램은 입력 줄마다가 아니라 딱 한 번 실행돼요. 암묵적 루프는 -n이나 -p로 만들 수 있어요.
  • use English;으로 불러온 English 모듈은 특수 변수(예: $/)를 이름(예: $RS)으로 참조할 수 있게 해줘요, 마치 awk처럼요 (자세한 건 perlvar).
  • Perl에서는 모든 단순 문장 뒤에 세미콜론이 필요해요 (블록 끝은 예외). 줄바꿈은 문장 구분자가 아니에요.
  • ifwhile에는 중괄호가 필수예요.
  • Perl에서 변수는 $, @, %로 시작해요.
  • 배열 인덱스는 0부터 시작해요. substr()index()의 문자열 위치도 마찬가지예요.
  • 배열이 숫자 인덱스를 쓸지 문자열 인덱스를 쓸지 결정해야 해요.
  • 해시 값은 단순히 참조한다고 저절로 생기지 않아요.
  • 문자열 비교를 쓸지 숫자 비교를 쓸지 결정해야 해요.
  • 입력 줄을 읽는다고 자동으로 쪼개주지는 않아요. 직접 배열로 split 해야 해요. 그리고 split() 연산자의 인자는 awk의 것과 달라요.
  • 현재 입력 줄은 보통 $0이 아니라 $_에 있어요. 그리고 대개 줄바꿈이 제거돼 있지 않아요. ($0은 실행된 프로그램의 이름이에요.) perlvar 참고.
  • $<digit>은 필드를 가리키지 않아요 — 마지막 매치 패턴이 잡아낸 부분 문자열을 가리켜요.
  • print()$,$\를 설정하지 않는 한 필드·레코드 구분자를 추가하지 않아요. English 모듈을 쓴다면 $OFS$ORS를 설정하면 돼요.
  • print 하기 전에 파일을 먼저 열어야 해요.
  • 범위 연산자는 ..이지 쉼표가 아니에요. 쉼표 연산자는 C와 동일하게 동작해요.
  • 매치 연산자는 =~이지 ~이 아니에요. (~는 C와 같이 1의 보수 연산자예요.)
  • 지수 연산자는 **이지 ^이 아니에요. ^는 C와 같이 XOR 연산자예요. (이쯤 되면 awk가 C와 근본적으로 호환되지 않는다는 느낌이 들죠.)
  • 결합(연결) 연산자는 .이지 널 문자열이 아니에요. (널 문자열을 쓰면 /pat/ /pat/을 파싱할 수 없게 돼요. 세 번째 슬래시가 나눗셈 연산자로 해석되거든요. 토크나이저는 사실 /, ?, > 같은 연산자에 대해 약간 문맥에 민감해요. 그리고 사실 . 자체도 숫자의 시작이 될 수 있어요.)
  • next, exit, continue 키워드는 다르게 동작해요.
  • 다음 변수들은 다르게 동작해요:
Awk	Perl
ARGC	scalar @ARGV (compare with $#ARGV)
ARGV[0]	$0
FILENAME	$ARGV
FNR	$. - something
FS	(whatever you like)
NF	$#Fld, or some such
NR	$.
OFMT	$#
OFS	$,
ORS	$\
RLENGTH	length($&)
RS	$/
RSTART	length($`)
SUBSEP	$;
  • $RS를 패턴으로 설정할 수 없어요. 문자열만 가능해요.
  • 의심스러우면 awk 구문을 a2p에 돌려서 어떤 결과가 나오는지 보세요.

C/C++ 프로그래머를 위한 함정 (C/C++ Traps)

  • ifwhile에는 중괄호가 필수예요.
  • else if가 아니라 elsif를 써야 해요.
  • C의 breakcontinue는 Perl에서 각각 lastnext가 돼요. C와 달리 이들은 do { } while 구문 안에서는 동작하지 않아요 (perlsyn의 "Loop Control" 참고).
  • switch 문은 given/when이라고 불러요. perl 5.10 이상에서만 사용 가능해요 (perlsyn의 "Switch Statements" 참고).
  • 변수는 $, @, %로 시작해요.
  • 주석은 /*//가 아니라 #으로 시작해요. Perl은 C/C++ 주석을 나눗셈 연산자·끝나지 않은 정규식·defined-or 연산자로 해석할 수 있어요.
  • 어떤 것의 주소를 취할 수는 없어요. 다만 Perl에는 비슷한 연산자로 참조(reference)를 만드는 백슬래시가 있어요.
  • ARGV는 대문자여야 해요. $ARGV[0]은 C의 argv[1]이고, argv[0]$0에 들어가요.
  • link(), unlink(), rename() 같은 시스템 호출은 성공 시 0이 아닌 값을 반환해요. (단, system()은 성공 시 0을 반환해요.)
  • 시그널 핸들러는 숫자가 아니라 시그널 이름을 다뤄요. 시스템에서 그 이름을 찾으려면 kill -l을 쓰세요.

JavaScript 프로그래머를 위한 함정 (JavaScript Traps)

  • Perl에서 이항 +항상 덧셈이에요. $string1 + $string2는 두 문자열을 숫자로 변환한 뒤 더해요. 두 문자열을 이어 붙이려면 . 연산자를 쓰세요.
  • 단항 + 연산자는 Perl에서 아무것도 하지 않아요. 문법적 모호함을 피하기 위해 존재할 뿐이에요.
  • for...in과 달리 Perl의 for(일명 foreach)는 좌변에 임의의 표현식을 허용하지 않아요. 반드시 변수여야 해요:
   for my $variable (keys %hash) {
	...
   }

그리고 여기서 keys를 빼먹지 마세요. foreach my $kv (%hash) {}는 키와 값을 번갈아 순회해서 대체로 쓸모없어요 ($kv는 키, 그다음 값, 그다음 또 키... 가 되니까요).

  • 배열의 인덱스를 순회하려면 foreach my $i (0 .. $#array) {}를 쓰세요. foreach my $v (@array) {}는 값을 순회해요.
  • Perl은 if, while, foreach 다음에 중괄호를 요구해요.
  • Perl에서 else ifelsif예요.
  • ? :는 대입보다 우선순위가 높아요. JavaScript에서는
condition ? do_something() : variable = 3

처럼 써서 조건이 거짓일 때만 변수에 대입할 수 있어요. Perl에서는 괄호가 필요해요:

$condition ? do_something() : ($variable = 3);

아니면 그냥 if를 쓰면 돼요.

  • Perl은 문장을 구분하려면 세미콜론이 필요해요.
  • my로 선언한 변수는 선언 뒤의 코드에만 영향을 줘요. $x = 1; my $x;라고 쓰고 첫 대입이 같은 변수에 영향을 주길 기대하면 안 돼요. 그 대입은 바깥 스코프에서 선언된 $x나 전역 변수에 가버려요. 또, 변수는 다음 문장부터 보여요. 즉 my $x = 1 + $x에서 두 번째 $x는 이전에 선언된 것을 가리켜요.
  • my 변수는 현재 함수가 아니라 현재 블록에 스코프돼요. {my $x;} $x;라고 쓰면 두 번째 $x는 블록 안에서 선언된 것을 가리키지 않아요.
  • 객체의 멤버를 변수처럼 접근할 수는 없어요. with(object) { method() }에 가장 가까운 Perl 대안은 for$_를 객체에 별칭하는 거예요:
    for ($object) {
	$_->method;
    }
  • 메서드가 호출되는 객체나 클래스는 메서드 인자 중 하나로 넘어와요. 별도의 this 값으로 넘어오지 않아요.

sed 사용자를 위한 함정 (Sed Traps)

  • Perl 프로그램은 입력 줄마다가 아니라 딱 한 번 실행돼요. 암묵적 루프는 -n이나 -p로 만들어요.
  • 치환(substitution)의 역참조는 \이 아니라 $를 써요.
  • 패턴 매칭 메타문자 (, ), |는 앞에 백슬래시가 없어요.
  • 범위 연산자는 쉼표가 아니라 ..예요.

shell 사용자를 위한 함정 (Shell Traps)

  • 백틱 연산자는 명령 안에 작은따옴표가 있어도 상관없이 변수 보간을 해요.
  • 백틱 연산자는 csh와 달리 반환값을 변환하지 않아요.
  • 셸(특히 csh)은 각 명령 줄에 여러 단계의 치환을 해요. Perl은 큰따옴표, 백틱, 꺾쇠괄호, 검색 패턴 같은 특정 구문에서만 치환해요.
  • 셸은 스크립트를 조금씩 해석해요. Perl은 실행 전에 전체 프로그램을 컴파일해요 (컴파일 시점에 실행되는 BEGIN 블록은 제외).
  • 인자는 $1, $2가 아니라 @ARGV로 접근해요.
  • 환경 변수가 자동으로 개별 스칼라 변수로 제공되지는 않아요.
  • 셸의 test는 문자열 비교에 =, !=, < 등을, 숫자 비교에 -eq, -ne, -lt 등을 써요. Perl은 그 반대예요. 문자열 비교에 eq, ne, lt를, 숫자 비교에 ==, !=, < 등을 써요.

Perl 프로그래머를 위한 함정 (Perl Traps)

  • 많은 연산이 리스트 컨텍스트스칼라 컨텍스트에서 다르게 동작한다는 걸 기억하세요 (perldata 참고).
  • 가능하면 bareword를 피하세요. 특히 전부 소문자인 것은요. bareword가 함수인지 문자열인지 보기만으로는 알 수 없어요. 문자열엔 따옴표를, 함수 호출엔 괄호를 붙이면 절대 헷갈리지 않아요.
  • 겉보기만으로 어떤 내장 함수가 단항 연산자인지(chop(), chdir()) 리스트 연산자인지(print(), unlink()) 구분할 수 없어요. (프로토타입이 없는 한, 사용자 정의 서브루틴은 리스트 연산자일 수밖에 없고 단항은 될 수 없어요.) perlop과 perlsub 참고.
  • 어떤 함수는 $_@ARGV를 기본으로 쓰는데, 그럴 것 같지 않은 다른 함수는 그렇지 않다는 걸 기억하기 어려워요.
  • <FH> 구문은 파일핸들의 이름이 아니라 그 핸들에 대한 readline 연산이에요. 읽은 데이터는 파일 읽기가 while 루프의 유일한 조건일 때만 $_에 대입돼요:
while (<FH>)      { }
while (defined($_ = <FH>)) { }..
<FH>;  # data discarded!
  • =가 필요한 곳에 =~를 쓰지 말고, 그 반대도 마찬가지예요. 이 둘은 상당히 달라요:
$x =  /foo/;
$x =~ /foo/;
  • do {} 구문은 루프 제어를 쓸 수 있는 진짜 루프가 아니에요.
  • 로컬 변수는 가능하면 언제나 my()를 쓰세요 (단, 쓸 수 없는 곳은 perlform 참고). local()을 쓰면 실제로는 전역 변수에 로컬 값을 주는 것이라, 동적 스코프의 예상치 못한 부작용에 노출될 수 있어요.
  • 모듈에서 내보낸(exported) 변수를 localize하면 그 내보낸 값은 바뀌지 않아요. 로컬 이름은 새 값의 별칭이 되지만, 바깥 이름은 여전히 원래 값의 별칭이에요.

언제나 그렇듯이, 이 중 어떤 것이 공식적으로 버그로 선언되면 고쳐져서 함정 목록에서 빠질 거예요.

더 알아보기 (Learn more)

  • warnings, perlrun-w — 경고 켜기
  • strict — 최소한의 안전장치
  • perldelta — Perl 버전 변경 사항
  • a2p — awk 프로그램을 Perl로 변환