본 도구에서 사용하는 오픈소스 라이브러리

본 도구의 코드에는 1개의 오픈소스 라이브러리가 포함되어 있습니다.

Perl 치트시트 — 간결한 참고서

Perl 5.38 문법, 정규식, 레퍼런스와 가장 자주 쓰는 내장 함수 치트 시트로, 일상 작업의 약 80%를 다룸.

Pl

Perl Perl 5.38

perl 5 (interpreter) · 다중 패러다임(절차형 · OO · 함수형) · 동적

추천 학습 경로

먼저 perl hello.pl 실행과 use strict/warnings 익히기 → 스칼라, 배열, 해시와 흐름 제어 익히기 → 서브루틴과 컨텍스트 깊이 파기 → 레퍼런스로 복잡한 자료구조 만들기 → 정규식으로 텍스트 처리 → eval 오류 처리와 bless 객체 지향 이해 → 필요에 따라 스레드, 네트워크, 시간과 CPAN 모듈 학습. FAQ 절은 돌아와서 실수 피하기 좋다.

1.Hello World 및 빌드 환경

Perl 스크립트 실행, use strict/warnings 및 명령줄 도구.

최소 프로그램

hello.pl로 저장하고 perl hello.pl로 실행. use strict와 use warnings는 필수.

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
say "Hello, world!";
# 실행:
# perl hello.pl
# 첫 줄 shebang 이 인터프리터를 가리킴
# say 는 자동 줄바꿈, print 는 줄바꿈 안 함

실행과 인터프리터

perl은 스크립트를 직접 해석하여 실행; -c는 문법 검사만, -e는 한 줄 코드를 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 스크립트를 직접 실행:
# perl hello.pl
# 문법 검사 (실행 안 함):
# perl -c hello.pl
# 커맨드라인 한 줄 코드:
# perl -e 'print "hi\n"'
# 경고 켜기:
# perl -w hello.pl
# 로드 경로 출력:
# perl -V
# 줄 단위 처리 (-n/-p):
# perl -ne 'print if /error/' log.txt
# 인터프리터 위치:
# which perl

shebang와 인자

#!/usr/bin/perl은 인터프리터 선언; 명령줄 인자는 @ARGV에 저장, $0은 스크립트 이름.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
# 커맨드라인 인자는 @ARGV 에 저장:
my ($name) = @ARGV;
say "你好, $name" if defined $name;
# 실행:
# perl greet.pl Rex
# 전체 인자 순회:
for my $arg (@ARGV) {
say "参数: $arg";
}
# $0 은 스크립트 이름:
say "脚本: $0";
# 인자 개수:
my $count = scalar @ARGV;

출력과 서식 지정

print는 줄바꿈 없음, say는 자동 줄바꿈; printf는 서식 지정, sprintf는 문자열 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use warnings;
use feature 'say';
print "hello\n"; # 수동 줄바꿈
say "world"; # 자동 줄바꿈
# 포맷 출력:
printf "姓名: %s, 年龄: %d\n", "Rex", 5;
# 문자열을 반환:
my $msg = sprintf("%d + %d = %d", 1, 2, 3);
say $msg;
# 핸들로 출력:
print STDERR "出错啦\n";
# 자주 쓰는 형식:
# %s 문자열 %d 정수 %f 부동소수 %x 16진수

입력 읽기

<STDIN>은 한 줄을 읽음(줄바꿈 포함); chomp는 끝의 줄바꿈 제거; EOF는 undef 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use feature 'say';
# 한 줄 읽기 (줄바꿈 포함):
my $line = <STDIN>;
chomp $line; # 끝 줄바꿈 제거
say "输入: $line";
# 한 줄 정수 읽기:
my $num = <STDIN>;
chomp $num;
$num += 0; # 수치로 변환
# EOF 까지 여러 줄 읽기:
while (my $l = <STDIN>) {
chomp $l;
last if $l eq 'quit';
say "读到: $l";
}
# 입력이 없으면 <STDIN> 은 undef 반환

따옴표 연산자

q()는 작은따옴표 의미, qq()는 큰따옴표 의미, qw()는 단어 목록, qx()는 명령 대체.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
# q() 는 삽입하지 않음:
my $a = q(原样 $name);
# qq() 는 삽입 가능:
my $b = qq(你好, $name);
# qw() 는 공백으로 단어 분할:
my @words = qw(alpha beta gamma);
# qx() 는 명령 실행 후 출력 획득:
my $pwd = qx(pwd);
chomp $pwd;
say "目录: $pwd";
# 장점: q 계열 연산자는 임의 구분자 사용 가능:
# q{...} qq(...) qw!...!
# 따옴표 이스케이프를 피할 수 있음

use strict/warnings

use strict는 변수 선언을 강제하고 bareword를 금지; use warnings는 런타임 경고를 활성화. 현대 Perl의 필수.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict; # 오타/베어워드를 에러로
use warnings; # 실행 시 경고
# strict 의 세 가지:
# strict 'vars' 변수 선언 필수
# strict 'refs' 심볼릭 참조 금지
# strict 'subs' 베어워드 함수명 금지
# 미선언 변수는 컴파일 에러:
# $foo = 1;
# 에러: Global symbol requires explicit package name
# 선언하면 정상:
my $bar = 1;
say $bar;
# 프로덕션 코드는 항상 이 두 줄을 붙임

perldoc 문서

perldoc은 Perl과 함께 설치되어 함수, 모듈, 내장 변수, 언어 튜토리얼을 조회.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 내장 함수 확인:
# perldoc -f map
# 모듈 문서 확인:
# perldoc JSON
# 초보자 입문 튜토리얼:
# perldoc perlintro
# 내장 특수 변수:
# perldoc perlvar
# 연산자 우선순위:
# perldoc perlop
# 내장 함수 전체 표:
# perldoc perlfunc
# 모듈 설치 위치:
# perldoc -l JSON
# 문법 빠른 참조:
# perldoc perlrequick

2.변수와 상수

스칼라/배열/해시 선언, my/our/local/state와 상수 정의.

변수 선언

세 기호가 세 가지 컨테이너를 표시: $ 스칼라, @ 배열, % 해시. my는 어휘 변수 선언.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
# 세 종류의 심볼:
my $scalar = 42;
my @array = (1, 2, 3);
my %hash = (name => 'Rex', age => 5);
# 선언만 하고 할당 안 함:
my $later;
my (@a, %h);
# 일괄 할당:
my ($x, $y, $z) = (1, 2, 3);
# 리스트 분해:
my @nums = (1, 2, 3, 4);
my ($first, @rest) = @nums;
# $first=1, @rest=(2,3,4)

스칼라 타입

스칼라는 숫자, 문자열, 참조 등 하나의 값을 저장. 문자열과 숫자는 자동 변환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use warnings;
my $num = 3.14; # 숫자
my $str = "hello"; # 문자열
my $bool = 1; # 거짓값: 0 '' undef '0'
my $undef; # 할당 안 하면 undef
# 자동 변환 (컨텍스트에 따라):
my $sum = "3" + 4; # 7 (숫자 컨텍스트)
my $s = 3 . 4; # "34" (문자열 연결)
# 문자열 앞부분을 숫자로 변환:
my $n = "12abc" + 0; # 12
# 길이:
my $len = length $str; # 5

배열 조작

배열은 요소의 목록. push/pop은 끝을 조작, shift/unshift는 시작을 조작, 인덱스는 0부터.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3);
$nums[0] = 10; # 요소 수정
say $nums[0];
# 스택 연산:
push @nums, 4; # 끝에 추가
my $last = pop @nums; # 끝을 팝
unshift @nums, 0; # 앞에 삽입
my $first = shift @nums; # 앞을 시프트
# 길이:
my $count = scalar @nums;
# 마지막 요소:
my $end = $nums[-1];
# 슬라이스:
my @sub = @nums[0, 1];

해시 조작

해시는 키-값 쌍이며 키는 문자열. keys/values/each로 순회, exists/delete로 관리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my %ages = (alice => 30, bob => 25);
# 읽기/수정:
say $ages{alice};
$ages{carol} = 28;
# 삭제와 판단:
delete $ages{bob};
exists $ages{alice}; # 키 존재 여부
# 순회:
for my $name (keys %ages) {
say "$name: $ages{$name}";
}
# 키-값을 동시에 가져옴:
while (my ($k, $v) = each %ages) {
say "$k=$v";
}
# 주의: each 는 커서를 소비, 순회 중 증감 금지

상수 정의

use constant는 컴파일 타임 상수를 정의, 기호 없이 서브루틴처럼 호출.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
# use constant 로 상수 정의:
use constant PI => 3.14159;
use constant MAX_RETRIES => 3;
# 상수는 서브루틴처럼 호출 ($ 기호 없음):
my $area = PI * 2 * 2;
# 상수 해시/배열:
use constant SETTINGS => { debug => 1 };
# 읽기 전용 의미:
# Readonly, Const::Fast 모듈이 제공
# 상수는 직접 삽입 불가:
# my $s = "PI = PI"; # 치환되지 않음
# 삽입은 연결이나 임시 변수 필요

스코프

my 어휘 변수는 블록 내에서 보임; our 패키지 변수는 전역; local은 전역을 임시로 덮음; state는 정적 어휘.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'state';
# my 는 블록 스코프:
{
my $temp = 10;
say $temp; # 블록 안에서 보임
}
# 블록 밖에는 $temp 가 없음
# our 는 패키지 변수 (전역):
our $VERSION = '1.0';
# local 은 특수 변수를 임시로 덮어씀:
{
local $/; # 구분자를 임시로 undef 로
my $all = <$fh>; # 파일 전체를 한 번에 읽음
}
# 블록을 나가면 $/ 복원
# state 는 호출을 가로질러 유지:
sub counter {
state $n = 0;
return ++$n;
}
say counter(); # 1
say counter(); # 2

기본 변수

$_는 기본 입력 변수, @_는 인자 목록; $!는 시스템 오류, $0은 스크립트 이름 등 특수 변수가 자주 사용됨.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# $_ 는 기본 변수:
for (1..3) {
say $_; # 루프에서 $_ 에 자동 할당
}
# 많은 함수가 기본으로 $_ 를 조작:
for (@ARGV) {
chomp;
print;
}
# @_ 는 인자 목록:
sub sum {
my $t = 0;
$t += $_ for @_;
return $t;
}
# 자주 쓰는 특수 변수:
# $0 스크립트명 $! 최근 시스템 오류
# $/ 구분자 $| 버퍼링 해제
# $$ 프로세스 ID $ARGV 현재 파일명

문자열 보간

큰따옴표에서 $scalar와 @array가 보간됨; 작은따옴표는 그대로 출력; 중괄호는 변수 경계를 한정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @list = (1, 2);
# 큰따옴표 삽입:
my $a = "你好, $name"; # 스칼라
my $b = "元素: @list"; # 배열 (공백 구분)
# $ 이스케이프:
my $c = "转义: \$name";
# 작은따옴표는 삽입 안 함:
my $d = '你好, $name';
# 중괄호로 변수명 경계 구분:
my $e = "${name}s house";
# 인덱스/역참조 삽입:
my $h = [1, 2, 3];
my $f = "第二个: $h->[1]";
# 이스케이프: \n 줄바꿈 \t 탭 \\ 백슬래시

3.데이터 타입

스칼라, undef, 참/거짓, 리스트와 해시, 파일 핸들과 typeglob.

스칼라 타입

스칼라는 유일한 기본 타입이며 정수, 부동소수점, 문자열, 참조를 저장할 수 있음. 타입을 명시적으로 선언할 필요 없음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
my $n = 42; # 정수
my $f = 3.14; # 부동소수
my $s = "text"; # 문자열
my $r = \@ARGV; # 레퍼런스
my $u; # undef
# 문자와 코드포인트 상호 변환:
my $ch = chr(65); # "A"
my $ord = ord('A'); # 65
# 수치 진법:
my $hex = 0xFF; # 255
my $oct = 0b1010; # 10
# 큰 수 정밀도:
# use bigint / Math::BigInt
# 타입은 할당에 따라 자동으로 바뀜

숫자 문자열 상호 변환

컨텍스트에 따라 자동 변환: +는 수치, .은 연결. 명시적 변환은 +0과 ."" 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use feature 'say';
# 자동 변환:
my $sum = "3" + "4"; # 7 (숫자 컨텍스트)
my $cat = 3 . 4; # "34" (문자열)
# 명시적 변환:
my $n = "42" + 0; # 문자열에서 숫자로
my $s = 42 . ""; # 숫자에서 문자열로
# 앞 숫자 규칙:
my $v = "12px" + 0; # 12
# 숫자로 시작 안 하면 0:
my $z = "abc" + 0; # 0
# 포맷 출력:
my $out = sprintf "%.2f", 3.14159; # 3.14
# 숫자와 문자열은 비교 연산자가 다름:
# == != < > 숫자; eq ne lt gt 문자열

undef와 defined

undef는 미정의를 나타내며 수치 컨텍스트에서 0, 문자열 컨텍스트에서 빈 문자열. defined로 판단.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
my $x; # 할당 안 하면 undef
say "未定义" unless defined $x;
# undef 의 컨텍스트 값:
my $y = $x + 1; # 1 (숫자=0)
my $s = "值: $x"; # 值: (문자열=빈)
# 판단:
if (defined $x) {
say "已定义";
}
# 정의 안 되어 있으면 할당:
$x //= 5; # undef 면 5 를 할당
say $x; # 5
# 명시적으로 undef 로:
undef $x;
# // 는 정의 또는 연산자 (5.10+)

불리언 참/거짓

거짓 값은 undef, 0, "0", 빈 문자열뿐; 나머지는 모두 참. 논리 연산자는 피연산자를 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
# 거짓값: undef, 0, "0", ""
my @false = (undef, 0, "0", "");
# 그 외는 모두 참:
my @true = (1, "0.0", " ", [], {});
if ("0.0") { say "是真" } # 비어있지 않은 문자열
if ([]) { say "数组引用真" }
# 논리 연산자는 마지막으로 평가한 피연산자 반환:
my $a = 0 || "fallback"; # "fallback"
my $b = 1 && "x"; # "x"
# 정의 또는:
my $c = $undef // "默认值"; # "默认值"
# ? : 로 불리언 생성:
my $ok = 5 > 3 ? 1 : 0;

배열과 리스트

리스트 리터럴 (1,2,3), qw 단어 목록, 범위 1..5. 컨텍스트가 전체 또는 요소를 결정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
my @list = (1, 2, 3);
my @empty = ();
# qw 워드 리스트:
my @names = qw(alice bob carol);
# 범위:
my @digits = (1..5); # 1 2 3 4 5
my @chars = ('a'..'e'); # a b c d e
# 리스트 할당:
my ($a, $b, $c) = (1, 2, 3);
# 중첩 전개:
my @flat = (1, (2, 3), 4); # 1 2 3 4
# 컨텍스트 차이:
my @x = (1, 2); # 리스트, 전체 유지
my $y = (1, 2); # 스칼라, 끝의 2
# 스칼라 컨텍스트에서 배열 길이:
my $len = @x; # 2

해시 키-값 쌍

뚱뚱한 화살표 =>는 왼쪽에 자동으로 따옴표를 붙임; 키-값이 쌍으로 나타나며 키는 문자열 또는 정수.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
# 펫 애로우로 자동 따옴표:
my %h = (name => 'Rex', age => 5);
# 동등한 표기:
my %h2 = ('name', 'Rex', 'age', 5);
# 숫자 키:
my %num = (1 => 'one', 2 => 'two');
# 빈 해시:
my %empty = ();
# 해시를 리스트로:
my @kv = %h; # ('name','Rex','age',5)
# 혼합 표기:
my %mixed = (x => 1, 'y', 2);
# 키 접근은 중괄호:
my $name = $h{name};
# 중괄호 안에서 식 사용 가능:
my $key = 'age';
my $age = $h{$key};

파일 핸들

STDIN/STDOUT/STDERR은 내장 핸들; open 어휘 핸들로 파일을 읽고 쓰며, 세 인자 형태가 가장 안전.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 내장 핸들:
print STDOUT "输出\n";
print STDERR "错误\n";
my $line = <STDIN>; # 입력 읽기
# 3-인자 open:
open my $in, '<', 'data.txt' or die $!;
my $first = <$in>;
close $in;
# 쓰기 핸들:
open my $out, '>', 'out.txt' or die $!;
print $out "hello\n";
close $out;
# 터미널인지 판단:
my $tty = -t STDOUT;
# 핸들은 스칼라, 인자로 전달 가능:
sub dump_all {
my $fh = shift;
print $fh @_;
}

typeglob

* 접두사는 심볼 테이블 항목을 나타내며 스칼라/배열/서브루틴에 별칭을 부여. 모던 코드에서는 잘 쓰이지 않음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
my $scalar = 1;
my @array = (1, 2);
# typeglob 별칭:
*alias = \$scalar; # 스칼라 별칭
*array_alias = \@array;
# 별칭은 같은 데이터를 가리킴:
$alias = 99;
say $scalar; # 99
# 심볼 테이블에는 동명 슬롯이 포함:
# *name 은 $name @name %name &name 포함
# 흔한 용도:
# 1. 구식 파일핸들: *FH = *STDOUT;
# 2. 함수 별칭: *f = \&original;
# 현대는 레퍼런스 사용이 권장:
my $r = \$scalar;
${$r} = 100;

4.참조와 역참조

Perl은 참조로 포인터를 대체: 역슬래시로 주소 획득, 역참조, 화살표 연산, 중첩 구조.

참조 기본

역슬래시 \는 변수에 대한 참조를 가져와 참조 타입의 스칼라를 반환; ref 함수는 타입명을 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @arr = (1, 2, 3);
my %h = (a => 1);
my $sref = \$name; # 스칼라 레퍼런스
my $aref = \@arr; # 배열 레퍼런스
my $href = \%h; # 해시 레퍼런스
# ref 는 타입명을 반환:
say ref $sref; # SCALAR
say ref $aref; # ARRAY
say ref $href; # HASH
# 레퍼런스가 아니면 빈 문자열:
my $plain = 5;
say ref $plain; # ""
# 레퍼런스는 스칼라, 배열/해시에 저장 가능:
my @refs = ($aref, $href);

역참조

@$ref, %$ref, $$ref는 참조를 해당 타입으로 복원; 중괄호 형태 ${$ref}가 더 명확.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my @arr = (10, 20, 30);
my %h = (a => 1, b => 2);
my $aref = \@arr;
my $href = \%h;
# 배열 역참조:
say $aref->[0]; # 10 (권장)
say ${$aref}[0]; # 10
# 해시 역참조:
say $href->{a}; # 1
say ${$href}{a}; # 1
# 스칼라 역참조:
my $val = 99;
my $sref = \$val;
say $$sref; # 99
say ${$sref}; # 99
# 전체를 꺼냄:
my @copy = @$aref; # 역참조하여 리스트로
my %copy = %$href;
# 길이/키 취득:
my $n = scalar @$aref;

화살표 연산자

->는 중첩 구조를 역참조: $ref->[0]와 $ref->{key}, 단계별로 연결.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my $ref = [10, 20, 30];
say $ref->[0]; # 10
my $href = {name => 'Rex', age => 5};
say $href->{name}; # Rex
# 다단 중첩:
my $matrix = [[1, 2], [3, 4]];
say $matrix->[1][0]; # 3
my $users = [
{name => 'alice', age => 30},
{name => 'bob', age => 25},
];
say $users->[1]{name}; # bob
# 함수 레퍼런스 호출:
my $fn = sub { "hi" };
say $fn->(); # hi
# 동등한 중괄호 표기:
say ${$users->[0]}{name}; # alice

익명 참조

[] 익명 배열, {} 익명 해시, sub {} 익명 함수; 명명된 변수 없이 직접 구성.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 익명 배열:
my $aref = [1, 2, 3];
my $empty = [];
# 익명 해시:
my $href = {name => 'Rex'};
# 익명 함수:
my $fn = sub { "hello" };
# 직접 중첩 구성:
my $config = {
name => 'app',
hosts => ['a.com', 'b.com'],
};
say $config->{hosts}[0]; # a.com
# 소괄호는 리스트, 대괄호는 레퍼런스:
my @list = (1, 2, 3); # 리스트
my $ref = [1, 2, 3]; # 레퍼런스
# 익명 레퍼런스는 변수를 먼저 만들 필요 없음

중첩 구조

배열 안에 해시, 해시 안에 배열 등 복합 구조; 순회할 때 단계별로 역참조.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 배열의 배열 (AoA):
my $grid = [[1, 2], [3, 4]];
say $grid->[0][1]; # 2
# 해시의 해시 (HoH):
my $people = {
alice => {age => 30, city => 'NY'},
bob => {age => 25, city => 'LA'},
};
say $people->{bob}{city}; # LA
# 해시의 배열 (HoA):
my $grades = {
math => [90, 85],
eng => [88],
};
say $grades->{math}[0]; # 90
# 중첩 순회:
for my $name (keys %$people) {
say "$name: $people->{$name}{age}";
}
# 깊은 중첩은 Data::Dumper 로 구조 확인

참조로 인자 전달

참조 전달로 큰 배열 복사를 피하고 호출자 데이터를 제자리에서 수정; 함수가 참조를 반환하면 더 효율적.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
my @big = (1..1000);
# 레퍼런스로 합계 (배열을 복사하지 않음):
sub total {
my $arr = shift;
my $sum = 0;
$sum += $_ for @$arr;
return $sum;
}
say total(\@big);
# 호출자 데이터를 제자리 수정:
sub bump {
my $arr = shift;
$_++ for @$arr;
}
bump(\@big);
say $big[0]; # 2 (수정됨)
# 여러 레퍼런스 전달:
sub merge {
my ($a, $b) = @_;
return [@$a, @$b]; # 익명 레퍼런스 반환
}
my $m = merge([1, 2], [3, 4]);
say $m->[2]; # 3

bless와 객체

bless는 참조에 클래스 태그를 붙여 객체로 만듦; 이후 ref는 클래스명을 반환. 객체는 본질적으로 참조.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
package Animal;
sub new {
my $class = shift;
my %args = @_;
return bless { %args }, $class;
}
sub speak {
my $self = shift;
return "$self->{name} 叫了一声";
}
package main;
my $cat = Animal->new(name => '喵');
say $cat->speak; # 喵 叫了一声
# 객체는 클래스 라벨이 붙은 해시 레퍼런스:
say ref $cat; # Animal
say $cat->{name}; # 喵
# 완전한 OO 는 oop 절 참조

참조 비교

참조 주소 비교에는 == 사용; ref로 타입 판단; 참조 카운팅이 생명 주기를 관리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my @a = (1, 2);
my @b = (1, 2);
my $r1 = \@a;
my $r2 = \@a; # 같은 대상
my $r3 = \@b; # 다른 대상
# 레퍼런스는 == 로 주소 비교:
say $r1 == $r2; # 1
say $r1 == $r3; # 0
# eq 는 문자열화한 형태를 비교:
say $r1 eq $r2; # 1
# ref 로 타입 판단:
say ref $r1; # ARRAY
say ref \5; # SCALAR
say ref undef; # ""
# 역참조 전 타입 판단:
if (ref $href eq 'HASH') {
say $href->{name};
}
# 레퍼런스 카운트는 mem 절 참조

5.흐름 제어

if/unless, 접미사 형식, for/foreach/while과 next/last/redo.

if/elsif/else

if는 참/거짓을 판단; elsif는 연쇄; unless는 역 판단. 조건에 괄호는 필요 없음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $score = 85;
if ($score >= 90) {
say "优秀";
} elsif ($score >= 60) {
say "及格";
} else {
say "不及格";
}
# 거짓값 판단 (0/빈/undef 가 거짓):
my $name = "Rex";
if ($name) { say "有名字" }
# unless = if not:
my $debug = 0;
unless ($debug) { say "不开调试" }
# 중괄호는 생략 불가:
# if ($x) say 1; 문법 오류
# 식을 조건으로 사용 가능:
if (-f 'data.txt') { say "是文件" }

unless와 until

unless는 if의 부정, until은 while의 부정; do-until은 최소 한 번 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
my $debug = 0;
# unless = if not:
unless ($debug) {
say "不输出调试信息";
}
# until = while not:
my $n = 0;
until ($n >= 3) {
say "n=$n";
$n++;
}
# 후치 형식:
say "缺省" unless defined $config;
# do-until 은 최소 1회 실행:
do {
$n--;
} until ($n == 0);
# 반전 조건에 적합하며 가독성이 좋음
# 복잡한 조건은 if/while 사용

접미사 제어

if/unless/for/while은 단일 문장 뒤에 올 수 있으며 Perl의 관용구. 한 문장만 수정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $n = 120;
# 후치 수식문:
say "很大" if $n > 100;
die "超时" unless $done;
for my $i (1..5) {
say "第 $i 个";
}
# 후치 for:
print "$_ " for 1..3;
my $total = 0;
$total += $_ for @nums;
# 동등한 전치 표기:
# if ($n > 100) { say "很大" }
# for (@nums) { $total += $_ }
# 수식할 수 있는 문장은 하나뿐:
# say 1; say 2 if $c; if 는 say 2 만 수식
# 여러 문장은 일반 블록 사용

for/foreach 루프

for는 C 스타일 또는 리스트 순회로 작성 가능; foreach는 별칭. 변수 미지정 시 $_ 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# C 스타일:
for (my $i = 0; $i < 5; $i++) {
say $i;
}
# 리스트 순회:
for my $item (1..3) {
say $item;
}
# 배열 순회:
my @names = qw(alice bob);
for my $name (@names) {
say $name;
}
# 변수 생략 시 $_:
for (1..3) {
say "当前: $_";
}
# foreach 는 완전히 동일:
foreach my $x (@names) {
say $x;
}
# 빈 리스트면 루프 본문 실행 안 함

while 루프

while은 조건이 참인 동안 실행; 파일 읽기는 while로 한 줄씩 읽는 것이 가장 관용적; do-while은 먼저 한 번 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
# 파일을 줄 단위로 읽음:
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
say $line;
}
# EOF 에서 undef 가 반환되어 루프 종료
# 무한 루프:
my $done = 0;
while (1) {
last if $done;
$done = 1;
}
# 최소 1회 실행:
my $x = 10;
do {
say $x;
$x--;
} while ($x > 5);
# until 은 반전:
my $i = 0;
until ($i > 3) {
say "i=$i";
$i++;
}

next/last/redo

next는 현재 회차 건너뜀, last는 루프 종료, redo는 현재 회차 재실행; 레이블로 중첩 루프 제어.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
# next 는 이번 회 건너뜀:
for my $i (1..10) {
next if $i % 2; # 홀수 건너뜀
say $i;
}
# last 는 루프 종료:
for my $i (1..10) {
last if $i > 5;
say $i;
}
# redo 는 이번 회 다시 실행:
my $sum = 0;
my $i = 0;
BLOCK: {
$i++;
$sum += $i;
redo BLOCK if $i < 3;
}
say $sum; # 6
# 라벨로 중첩 루프 제어:
OUTER: for my $x (1..3) {
for my $y (1..3) {
next OUTER if $y == 2;
say "$x,$y";
}
}

삼항 연산과 단락 평가

?:는 조건 표현식; ||와 //는 기본값 제공; 논리 연산은 피연산자를 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my $n = -3;
# 삼항 연산자:
my $sign = $n >= 0 ? "非负" : "负数";
say $sign;
# || 기본값 (거짓이면 치환):
my $name;
my $shown = $name || "无名氏";
# // 정의 또는 (undef 만 치환):
my $port;
$port //= 8080; # undef 일 때만 기본값
# 논리 연산자는 피연산자를 반환:
my $a = 0 && die "不会执行";
my $b = 1 || die "不会执行";
# 중첩 삼항은 가독성 나쁨:
# my $g = $n>0 ? "正" : $n<0 ? "负" : "零";
# if/elsif 로 분해 권장

goto와 레이블

goto로 이동하는 레이블(제한 버전); 다중 루프 탈출은 라벨이 붙은 last를 우선 사용한다. 일상 코드에서는 goto를 거의 쓰지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 제한된 goto 로 라벨 이동:
my $i = 0;
START:
$i++;
goto START if $i < 3;
say "结束: $i";
# goto 는 블록 안으로 들어갈 수 없음
# 루프 제어를 우선:
my @items = (1, -1, 2);
for my $item (@items) {
last if $item < 0;
say $item;
}
# 다단 탈출은 라벨 + last:
OUTER: for my $a (1..3) {
for my $b (1..3) {
last OUTER if $a * $b > 4;
say "$a x $b";
}
}
# 가독성 우선, goto 로 이리저리 뛰지 않기

6.함수와 서브루틴

sub 정의, @_ 매개변수, 반환값, 클로저, 프로토타입과 시그니처.

함수 정의

sub은 명명된 서브루틴을 정의하며, 마지막 표현식이 자동으로 반환된다. 호출 시 괄호로 인자를 전달한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 정의와 호출:
sub greet {
return "hello";
}
say greet(); # hello
# 인자 포함:
sub add {
my ($a, $b) = @_;
return $a + $b;
}
say add(2, 3); # 5
# 마지막 식이 자동으로 반환:
sub square { my $x = shift; $x * $x }
say square(4); # 16
# 반환값 없음:
sub log_msg {
say "记录日志";
}
# 함수명은 대소문자 구분
# 미정의 상태로 호출하면 컴파일 시 오류

@_와 매개변수

매개변수 목록은 @_에 저장된다; shift는 첫 번째 값을 꺼낸다; @_의 원소는 별칭이므로 제자리에서 수정하면 외부로 새어나간다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
# @_ 는 인자 목록:
sub first_arg {
my $x = shift; # 첫 번째 꺼냄
return $x;
}
# 여러 인자:
sub two {
my ($a, $b) = @_; # 리스트 할당
return "$a-$b";
}
say two(1, 2); # 1-2
# 함수 안의 shift 는 기본으로 @_ 를 조작:
sub total {
my $sum = 0;
$sum += shift while @_;
return $sum;
}
say total(1, 2, 3); # 6
# @_ 는 별칭, 수정이 외부로 새어 나감:
sub bump_first { $_[0]++ }
my $x = 1;
bump_first($x);
say $x; # 2
# 보통은 렉시컬 변수에 복사한 뒤 사용

반환값

스칼라 또는 리스트 반환은 문맥에 따라 결정된다; wantarray는 호출 문맥을 판별한다; 참조를 반환하면 복사를 피할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
sub coords { return (1, 2, 3) }
# 리스트 컨텍스트에서는 전체 반환:
my @all = coords(); # (1,2,3)
# 스칼라 컨텍스트에서는 마지막 반환:
my $n = coords(); # 3
# 빈 return:
sub nothing { return }
# undef 반환:
sub missing { return undef }
# wantarray 로 컨텍스트 감지:
sub ctx {
return wantarray ? (1, 2) : 5;
}
my @l = ctx(); # (1,2)
my $s = ctx(); # 5
# 큰 구조는 레퍼런스를 반환해 복사 방지:
sub make_list {
return [1, 2, 3]; # 익명 배열 레퍼런스
}
my $r = make_list();

익명 서브루틴

sub {}는 함수 참조를 생성하며, ->()로 호출한다; 콜백, 배열·해시 저장에 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
# 익명 서브루틴:
my $square = sub {
my $x = shift;
return $x * $x;
};
say $square->(5); # 25
# 인자로 전달 (콜백):
sub apply {
my ($fn, $val) = @_;
return $fn->($val);
}
say apply(sub { $_[0] * 2 }, 21); # 42
# 배열에 저장:
my @handlers = (
sub { "one" },
sub { "two" },
);
say $handlers[1]->(); # two
# 해시에 저장 (디스패치 테이블):
my %ops = (
add => sub { $_[0] + $_[1] },
mul => sub { $_[0] * $_[1] },
);
say $ops{add}->(2, 3); # 5
# 함수 레퍼런스도 일반 레퍼런스와 같은 스칼라

클로저

클로저는 어휘 변수를 캡처하여 상태를 기억한다; 팩토리 함수를 호출할 때마다 독립적인 복사본이 만들어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 카운터 클로저:
sub make_counter {
my $count = 0;
return sub {
$count++; # 상태를 기억
return $count;
};
}
my $c = make_counter();
say $c->(); # 1
say $c->(); # 2
# 각자 독립된 상태:
my $a = make_counter();
my $b = make_counter();
$a->(); # 1
$b->(); # 1
# 팩토리 함수:
sub make_greeter {
my $prefix = shift;
return sub { "$prefix: $_[0]" };
}
my $hi = make_greeter("hi");
say $hi->("Rex"); # hi: Rex
# 클로저는 복사본이 아니라 변수 자체를 잡음

프로토타입

프로토타입은 컴파일 시점에 매개변수 문맥을 제약한다(예: (\@)는 배열 참조를 강제). 현대 코드에서는 시그니처 사용을 권장한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 프로토타입으로 인자 제약:
sub myshift (\@) {
my $arr = shift;
return shift @$arr;
}
my @nums = (1, 2, 3);
my $first = myshift(@nums); # 자동 레퍼런스화
say $first; # 1
# 자주 쓰는 프로토타입:
# ($) 스칼라 컨텍스트 강제
# (\@) 배열 레퍼런스 강제
# (\%) 해시 레퍼런스 강제
# ($@) 스칼라 1개 + 배열
# 프로토타입은 메서드 호출에 무효:
# $obj->meth(@args) 프로토타입 무시
# 프로토타입은 오해를 부를 수 있음
# 5.36+ 에서는 시그니처 권장:
sub real($a, $b) { $a + $b }

함수 시그니처

feature 'signatures'는 선언적 매개변수를 제공한다(5.36+ 안정); 기본값과 배열 매개변수를 지원한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
# 선언적 인자:
sub add($a, $b) {
return $a + $b;
}
say add(2, 3); # 5
# 기본값:
sub greet($name = "world") {
return "hello, $name";
}
say greet(); # hello, world
# 다중 인자 + 배열:
sub config($name, @rest) {
return ($name, @rest);
}
# 인자는 렉시컬 변수, @_ 를 건드리지 않음:
sub swap($x, $y) {
return ($y, $x);
}
my ($u, $v) = swap(1, 2);
# 시그니처가 shift 보다 명확
# 이름 있는 인자 미지원, 해시로 대체

함수 참조

\&sub는 함수 참조를 가져오고, ->() 또는 &$ref()로 호출한다; 콜백, 디스패치 테이블, 고차 함수에 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 함수 레퍼런스 취득:
sub twice { $_[0] * 2 }
my $fn = \&twice;
say $fn->(10); # 20
say &$fn(10); # 20
# 인자로 전달:
sub run {
my $code = shift;
$code->();
}
run(sub { say "运行" });
# 코드 레퍼런스인지 확인:
my $cb = \&twice;
say ref $cb; # CODE
# 꼬리 호출 최적화 (호출 프레임 재사용):
sub loop {
return if $_[0] <= 0;
@_ = ($_[0] - 1);
goto &loop;
}
loop(100000);
# 깊은 재귀는 goto &sub 를 우선

7.문자열

따옴표, 보간, 연결, 포맷, 부분 문자열, 인코딩 처리.

문자열 리터럴

작은따옴표는 그대로, 큰따옴표는 보간; q()/qq()는 동등; 인접한 문자열은 자동으로 연결된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
# 작은따옴표는 삽입 안 함:
my $a = '原样 $name';
# 큰따옴표는 삽입:
my $b = "hi $name";
# q/qq 연산자 (임의 구분자):
my $c = q(原样 $name);
my $d = qq(插值 $name);
# 작은따옴표 안 \\ 와 \' 이스케이프:
my $e = 'it\'s ok';
# 인접 리터럴은 자동 연결:
my $f = 'a' 'b' 'c'; # abc
# 줄바꿈과 탭:
my $g = "第一行\n第二行\t制表";
# 따옴표 중첩은 이스케이프 주의:
my $h = "他说 \"你好\"";

보간 규칙

큰따옴표 안에서 $scalar와 @array가 보간된다; 중괄호로 경계를 제한; 인덱싱·역참조도 보간할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @nums = (1, 2, 3);
# 스칼라 삽입:
my $a = "你好, $name";
# 배열 삽입 (공백 구분):
my $b = "数组: @nums"; # 数组: 1 2 3
# 중괄호로 변수 경계 구분:
my $s = "${name}s house";
# 인덱스 삽입:
my @h = qw(x y z);
my $c = "第二个: $h[1]"; # y
# 역참조 삽입:
my $user = {name => 'Rex'};
my $d = "名字: $user->{name}";
# 이스케이프:
my $e = "换行\n制表\t";
# \\ 백슬래시 \$ 리터럴 달러
# 변수명에 비단어 문자가 있으면 중괄호

연결과 반복

.은 문자열 연결, x는 반복, join은 배열 연결; length는 길이, reverse는 역순.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 점 연결:
my $full = "Hello" . " " . "World";
# 숫자는 점 컨텍스트에서 문자열이 된다:
my $s = 1 . 2; # "12"
# 반복 연산자:
my $line = "-" x 20; # 하이픈 20개
# join 으로 배열 연결:
my @parts = ('a', 'b', 'c');
my $csv = join ",", @parts; # a,b,c
# 길이:
my $len = length "hello"; # 5
# 뒤집기:
my $rev = reverse "abc"; # cba
# 문자열 비교:
"a" eq "a"; # 같음
"a" lt "b"; # 작음(사전순)
"abc" cmp "abd"; # 비교 결과
# 연결은 .= 로 변수에 추가:
my $buf = "x";
$buf .= "y"; # xy

부분 문자열과 검색

substr은 부분 문자열을 가져오거나 변경; index/rindex는 위치를 찾는다(찾지 못하면 -1 반환).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my $s = "Hello, World";
# 부분 문자열 추출:
my $sub = substr $s, 0, 5; # Hello
my $from = substr $s, 7; # World
my $last4 = substr $s, -5; # World(끝에서)
# 위치 검색:
my $pos = index $s, "World"; # 7
my $last = rindex $s, "o"; # 8(오른쪽에서 찾음)
# 찾지 못하면 -1 반환:
index $s, "zzz"; # -1
# 부분 문자열 치환:
my $t = $s;
substr($t, 7, 5) = "Perl"; # Hello, Perl
# 줄 단위 분할은 split:
my @words = split /,/, $s;
# 더 복잡한 추출은 정규식(regex 절 참조)

chomp와 split

chomp는 끝의 개행을 제거; split은 구분자로 리스트를 분리; join은 그 역연산이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# chomp 는 끝의 개행을 제거:
my $line = "abc\n";
chomp $line; # "abc"
# chomp 는 파괴적 변경으로,제거한 문자 수를 반환
# split 은 구분자로 분할:
my @f = split /,/, "a,b,c"; # a b c
my @w = split /\s+/, "a b"; # a b
# 구분자를 생략하면 공백으로 분할:
my @t = split /\s+/, "a b c";
# 분할 개수 제한:
my @two = split /,/, "a,b,c", 2; # a, "b,c"
# join 은 역연산:
my $back = join "-", @f; # a-b-c
# 문자열을 행 배열로 변환:
my @lines = split /\n/, $text;
# CSV 파싱은 Text::CSV 모듈 권장

printf/sprintf

printf는 직접 출력하고, sprintf는 문자열을 반환; %d/%s/%f 등의 포맷과 0 채우기·정렬.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
# printf 는 직접 출력:
printf "%s 今年 %d 岁\n", "Rex", 5;
# sprintf 는 문자열을 반환:
my $s = sprintf "%05d", 42; # 00042
# 자주 쓰는 형식:
# %s 문자열 %d 정수 %f 부동소수점
# %x 16진수 %o 8진수 %e 과학 표기
# %.2f 소수 2자리 %5d 오른쪽 정렬 공백 채움
# %-5d 왼쪽 정렬 %05d 0 채움
my $money = sprintf "%.2f", 3.14159; # 3.14
my $hex = sprintf "%x", 255; # ff
# 동적 너비:
my $w = 10;
my $padded = sprintf "%${w}s", "hi";
# 핸들에 출력:
printf STDOUT "值: %d\n", 42;
# 인자는 순서대로 매칭되며,여러 인자도 지원

대소문자와 정제

uc/lc/ucfirst로 대소문자를 변경; 치환 정규식으로 공백 제거; tr///로 문자 변환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my $s = "Hello World";
my $upper = uc $s; # HELLO WORLD
my $lower = lc $s; # hello world
my $cap = ucfirst $s; # Hello World
my $w = lcfirst "Hello"; # hello
# 앞뒤 공백 제거(정규식):
my $t = " padded ";
$t =~ s/^\s+//; # 앞 제거
$t =~ s/\s+$//; # 뒤 제거
# 접두사/접미사 판별:
$s =~ /^Hello/;
$s =~ /World$/;
# 문자 변환:
my $u = "abc";
$u =~ tr/a-z/A-Z/; # ABC
# 삭제 변환:
my $d = "a1b2c3";
$d =~ tr/0-9//d; # abc
# 문자와 코드포인트:
ord 'A'; # 65
chr 65; # "A"

인코딩과 UTF-8

use utf8로 소스를 표시; encode/decode로 변환; 인코딩 레이어를 사용해 파일을 읽고 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use utf8; # 소스를 UTF-8 로 해석
use Encode;
my $chinese = "中文";
binmode STDOUT, ':utf8'; # UTF-8 로 출력
# 인코딩과 디코딩:
my $bytes = encode('UTF-8', $chinese); # 바이트
my $back = decode('UTF-8', $bytes); # 문자
# 문자 수와 바이트 수:
my $text = "你好";
length $text; # 2(문자)
length encode('UTF-8', $text); # 6(바이트)
# 인코딩 레이어를 붙여 읽고 쓰기:
open my $fh, '<:utf8', 'file.txt' or die $!;
open my $out, '>:utf8', 'out.txt' or die $!;
# JSON 은 UTF-8 사용:
use JSON::PP;
my $j = JSON::PP->new->utf8->encode({k => "中文"});

8.집합과 자료구조

배열/해시 조작, 슬라이스, map/grep, 정렬과 List::Util.

배열 추가·삭제·수정

push/pop/shift/unshift 네 가지 끝점 조작; splice로 중간 삽입·삭제; reverse/sort.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3);
# 스택 연산:
push @nums, 4; # 끝에 추가
my $top = pop @nums; # 끝에서 pop
unshift @nums, 0; # 앞에 추가
my $head = shift @nums; # 앞에서 shift
# 중간 삽입/삭제:
splice @nums, 1, 0, 99; # 인덱스1 에 99 삽입
splice @nums, 0, 1; # 인덱스0 삭제
# 뒤집기와 정렬:
my @rev = reverse @nums;
my @sorted = sort @nums;
# 병합:
my @all = (@nums, (4, 5));
# 끝에 여러 개 추가:
push @nums, 6, 7;
# 배열 길이와 마지막 요소:
my $len = scalar @nums;
my $end = $nums[-1];

슬라이스

@arr[...]는 배열 슬라이스, %hash{...}는 해시 슬라이스; 슬라이스는 한꺼번에 대입할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my @arr = qw(a b c d e);
# 배열 슬라이스:
my @mid = @arr[1..3]; # b c d
my @sel = @arr[0, 2, 4]; # a c e
# 슬라이스는 대입 가능:
@arr[1, 2] = qw(X Y);
my %h = (alice => 30, bob => 25, carol => 28);
# 해시 슬라이스(여러 키의 값 가져오기):
my @ages = @h{qw(alice bob)}; # 30 25
# 해시 슬라이스 대입:
@h{qw(dan eve)} = (22, 35);
# 배열/해시 전체:
my @all = @arr;
# 레퍼런스의 슬라이스:
my $r = \@arr;
my @sub = @$r[1..2];
# 스칼라 컨텍스트에서는 개수를 반환:
my $n = @arr[1..3]; # 3

리스트 연산

sum/min/max는 List::Util에서 제공; grep으로 중복 제거, 범위에서 값 가져오기, 리스트 구조분해.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
use List::Util qw(sum min max);
my @nums = (3, 1, 4, 1, 5);
my $sum = sum @nums; # 14
my $min = min @nums; # 1
my $max = max @nums; # 5
# 중복 제거(첫 등장 순서 유지):
my %seen;
my @uniq = grep { !$seen{$_}++ } @nums;
# 병합:
my @a = (1, 2);
my @b = (3, 4);
my @ab = (@a, @b);
# 리스트 분해:
my ($x, $y) = @nums; # 3, 1
my ($head, @rest) = @nums;
# 정렬 후 앞 3개 가져오기:
my @top3 = (sort { $b <=> $a } @nums)[0..2];
# 반환값을 무시할 때 임시 배열 오버헤드 주의

map과 grep

grep은 필터링, map은 변환이며 둘 다 $_로 반복한다. 스칼라 문맥에서 grep은 일치 개수를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3, 4, 5);
# grep 필터링:
my @big = grep { $_ > 2 } @nums; # 3 4 5
my @even = grep { $_ % 2 == 0 } @nums; # 2 4
# map 변환:
my @sq = map { $_ * $_ } @nums; # 1 4 9 16 25
my @labels = map { "第 $_ 个" } @nums;
# 스칼라 컨텍스트에서는 일치 개수 반환:
my $count = grep { $_ > 3 } @nums; # 2
# map 으로 해시 생성:
my %square = map { $_ => $_ * $_ } 1..5;
# 조합 사용:
my @big_sq = map { $_ * $_ }
grep { $_ > 2 } @nums; # 9 16 25
# grep/map 은 원본 배열을 변경하지 않음
# 매핑 결과를 사용하지 않으면 낭비

해시 순회

keys/values는 키·값 리스트를 가져오고, each는 키-값 쌍을 순회; exists/delete로 관리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my %h = (a => 1, b => 2, c => 3);
# 키 / 값 목록:
my @keys = keys %h;
my @values = values %h;
# 키-값 페어 순회(each 는 커서 소비):
while (my ($k, $v) = each %h) {
say "$k=$v";
}
# each 호출마다 다음 페어를 가져옴
# each 중첩은 오류가 쉽고,단일 계층에서 사용
# 존재 확인과 삭제:
exists $h{a}; # 1
delete $h{b};
# 비우기:
%h = ();
# 병합:
my %m = (d => 4, %h);
# 키-값 반전:
my %inv = reverse %m;
# 카운트는 each + ++ 가 정석:
$count{$_}++ for @words;

해시 관용구

해시는 카운팅, 중복 제거, 집합, 캐시, 그룹화에 사용. 순회 중 추가·삭제는 주의해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use feature 'say';
# 카운트:
my @words = qw(a b a c b a);
my %count;
$count{$_}++ for @words; # a=>3 b=>2 c=>1
# 중복 제거:
my %seen;
my @uniq = grep { !$seen{$_}++ } @words;
# 집합 판별:
my %set = map { $_ => 1 } qw(a b c);
say "存在" if $set{a};
# 캐시:
my %cache;
sub expensive {
my $key = shift;
return $cache{$key} if exists $cache{$key};
$cache{$key} = $key * $key;
return $cache{$key};
}
# 그룹화(첫 글자 기준):
my %groups;
for my $word (@words) {
my $first = substr $word, 0, 1;
push @{ $groups{$first} }, $word;
}
# 배열 값 해시는 레퍼런스를 저장하고,읽을 때 역참조 필요

정렬

sort의 기본은 문자열 순서; 사용자 정의 비교 블록은 <=>로 숫자, cmp로 문자열을 비교한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
my @nums = (5, 3, 8, 1);
# 기본은 문자열 정렬:
my @s = sort @nums; # 1 3 5 8
# 숫자 오름차순/내림차순:
my @n = sort { $a <=> $b } @nums; # 오름차순
my @d = sort { $b <=> $a } @nums; # 내림차순
# 문자열 비교:
my @w = sort { $a cmp $b } qw(banana apple);
# 길이순:
my @len = sort { length $a <=> length $b }
@words;
# 안정 정렬(보조 키):
my @st = sort {
length $a <=> length $b || $a cmp $b
} @words;
# 키 정렬로 해시 순회:
my %h = (b => 2, a => 1);
for my $k (sort keys %h) {
say "$k=$h{$k}";
}
# 비교 블록의 $a/$b 는 요소이며,인덱스가 아님

List::Util

sum/sum0/min/max/first/reduce/any/all/shuffle. sum0은 빈 리스트에서 0을 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
use List::Util qw(
sum sum0 min max first reduce shuffle any all
);
my @nums = (3, 1, 4, 1, 5);
my $sum = sum @nums; # 14
my $sum0 = sum0 @nums; # 빈 리스트는 0
my $min = min @nums; # 1
my $max = max @nums; # 5
# first 는 처음으로 조건을 만족하는 것:
my $f = first { $_ > 3 } @nums; # 4
# any / all:
my $has = any { $_ > 4 } @nums; # true
my $all = all { $_ > 0 } @nums; # true
# reduce 로 접기:
my $prod = reduce { $a * $b } @nums;
# shuffle 로 섞기:
my @mix = shuffle @nums;
# 리스트를 넘어 최값 구하기:
min @a, @b;
# 주의:sum/min 은 빈 리스트에서 undef 반환

9.메모리와 성능

참조 카운팅, 순환 참조, 약한 참조, 자동 활성화, 성능 벤치마크.

참조 카운팅

Perl은 참조 카운팅으로 메모리를 자동 회수하므로 수동 free가 필요 없다. 카운트가 0이 되면 해제된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
# 참조 카운트로 자동 관리:
my $ref = [1, 2, 3]; # 카운트 1
my $copy = $ref; # 카운트 2
undef $copy; # 카운트 1
undef $ref; # 카운트 0,해제
# 렉시컬 변수는 스코프를 벗어나면 즉시 회수:
{
my $tmp = "数据";
} # $tmp 는 여기서 해제
# 수동 malloc/free 불필요:
# 생성만 하고 회수는 엔진에 맡김
# C/Rust 의 명시적 관리와는 다름
# 메모리 피크 관찰:
# 큰 배열은 처리 후 undef 로 일찍 해제
# 또는 스트림 처리로 전환,한꺼번에 읽지 않기
# 순환 참조는 카운트로 회수 불가。다음 절 참조

순환 참조

객체가 서로를 참조하면 카운트가 0이 되지 않아 누수가 발생; 약한 참조를 사용하거나 수동으로 순환을 끊는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 순환 참조로 누수:
my $a = {};
my $b = {};
$a->{other} = $b; # a 는 b 를 참조
$b->{other} = $a; # b 는 a 를 참조
# 카운트가 서로 유지되어 두 메모리는 영원히 해제되지 않음
# 자기 참조도 마찬가지로 누수:
my $node = {};
$node->{self} = $node;
# 해결 1:수동으로 고리 끊기:
$a->{other} = undef;
# 해결 2:약한 참조:
use Scalar::Util 'weaken';
my $wa = {};
my $wb = {};
$wa->{other} = $wb;
weaken $wa->{other}; # 강한 참조 아님
# $wb 는 정상적으로 회수됨
# 검출:Devel::Leak / valgrind
# 데이터 구조는 가능한 DAG(비순환)으로

약한 참조

Scalar::Util::weaken은 참조가 카운트를 증가시키지 않게 한다; 대상이 회수되면 약한 참조는 undef가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
use Scalar::Util qw(weaken isweak);
# 강한 참조 + 약한 참조:
my $data = {name => 'Rex'};
my $strong = $data; # 강한 참조
my $weak = $data;
weaken $weak; # 약한 참조로 전환
say isweak($weak); # 1
# 강한 참조 해제:
undef $data;
undef $strong;
# 약한 참조는 해제된 메모리를 가리킴:
say defined $weak; # 0(undef 로 변함)
# 캐시는 회수를 막지 않음:
my %cache;
my $obj = {id => 1};
$cache{1} = $obj;
weaken $cache{1};
# 사용 전에 비었는지 확인:
if (defined $cache{1}) {
say "命中缓存";
}
# 약한 참조는 옵저버/캐시 용도로 자주 사용

스코프 해제

어휘 변수와 핸들은 스코프를 벗어날 때 자동으로 해제·닫힌다; undef로 미리 해제할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 함수에서 돌아오면 지역 변수 해제:
sub work {
my $big = "x" x 1000000; # 큰 문자열
process($big);
return 1;
# $big 는 반환 시 해제
}
# 블록으로 라이프사이클 조기 종료:
{
my $tmp = load_data();
process($tmp);
} # $tmp 는 블록 종료 시 해제
# 파일 핸들은 자동으로 닫힘:
sub read_all {
open my $fh, '<', 'data.txt' or die $!;
local $/;
return <$fh>; # 반환 시 $fh 가 닫힘
}
# 명시적 해제:
undef @big_array; # 즉시 해제
# 참조된 데이터는 참조가 살아있는 동안 유지:
my $keep = [1, 2, 3];
my $holder = {list => $keep}; # $keep 은 생존

자동 활성화

정의되지 않은 참조에 값을 대입하면 컨테이너가 자동으로 생성된다; 쓰기는 편하지만 중첩 읽기 시 잘못 생성될 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 쓰기 시 자동 생성:
my $hash;
$hash->{a} = 1; # 해시 레퍼런스 자동 생성
say $hash->{a}; # 1
my $arr;
$arr->[0] = "x"; # 배열 레퍼런스 자동 생성
# 다단 자동 생성:
my $deep;
$deep->{a}[0]{b} = 1; # 단계적으로 생성
# 존재하지 않는 키 읽기는 undef:
my %h;
say defined $h{missing} ? "有" : "无";
# 읽기 중첩도 중간 계층을 만들 수 있음:
my $outer;
my $v = $outer->{x}{y}; # {x} 생성됨
# 단락 평가로 빈 판정 방지:
if ($outer && $outer->{x}) {
say "有值";
}
# 완전히 끄고 싶다면:
# use no autovivification;

할당과 속도 향상

배열 사전 할당, 정규식 사전 컴파일, 보간 연결은 최소화; 측정 후 최적화.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 배열 용량 미리 확보:
my @nums;
$#nums = 9999; # 미리 확보
for my $i (0..9999) {
$nums[$i] = $i;
}
# 정규식 사전 컴파일 재사용:
my $re = qr/\d{4}-\d{2}-\d{2}/;
for my $line (@lines) {
$line =~ $re; # 컴파일 결과 재사용
}
# 연결은 join 우선:
my $big = join '', @parts;
# 대량 .= 연결은 여전히 느림:
my $s = "";
$s .= "x" for 1..1000;
# 순수 텍스트는 작은따옴표:
print '纯文本';
# 해시로 O(1) 검색:
my %lookup = map { $_ => 1 } @list;
# 분석은 Devel::NYTProf
# 먼저 측정 후 최적화。무작정 미세조정 금지

메모리 측정

Devel::Size로 구조 점유 측정; 메모리 피크 관찰; 대용량 파일은 스트리밍 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 구조체 메모리 측정:
use Devel::Size qw(total_size);
my $struct = {
name => 'Rex',
tags => [qw(dev tools)],
};
say total_size($struct); # 바이트 수
# 단일 변수:
Devel::Size::size($struct);
# 블록 단위로 큰 객체 찾기:
# 먼저 최상위를 dump 하고 블록 단위로 비교
# 프로세스 메모리 모니터링(Linux):
# /proc/$$/status 의 VmRSS 읽기
# Windows 는 작업 관리자로 관찰
# 큰 파일은 스트림 처리:
open my $fh, '<', 'big.log' or die $!;
while (my $line = <$fh>) {
process($line); # 한꺼번에 읽지 않음
}
# 참고:perldoc Devel::Size

성능 벤치마크

Benchmark 모듈로 구현을 비교; Time::HiRes로 고정밀 타이밍.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
use Benchmark qw(cmpthese);
my @data = (1..1000);
# 두 가지 작성법 비교:
cmpthese(1000, {
join_way => sub {
my $s = join ',', @data;
},
concat_way => sub {
my $s = "";
$s .= "$_" for @data;
},
});
# 초당 실행 횟수와 상대 속도 출력
# 초 단위 계측:
my $t0 = time;
work();
my $elapsed = time - $t0;
say "耗时 $elapsed 秒";
# 고정밀 계측:
use Time::HiRes qw(time);
my $t1 = time;
work();
my $ms = (time - $t1) * 1000;
say "耗时 $ms 毫秒";
# 핫스팟 분석은 Devel::NYTProf

10.객체 지향

bless 참조, 메서드, 상속, 접근자, 연산자 오버로딩과 Moose/Moo.

bless 생성

bless는 참조에 클래스 태그를 붙여 객체로 만든다; 객체는 본질적으로 클래스 이름이 붙은 참조이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
package Person;
sub new {
my $class = shift;
my $self = { name => '未知' };
return bless $self, $class;
}
# 객체는 태그가 붙은 레퍼런스:
my $p = Person->new();
say ref $p; # Person
# 클래스명은 기본적으로 현재 패키지:
sub new_short {
my $self = {x => 1};
return bless $self; # 클래스는 현재 패키지
}
# 객체는 본질적으로 해시 레퍼런스:
$p->{name} = 'Rex'; # 필드 직접 변경
# 메서드에 캡슐화하는 것을 권장(접근자 참조)
# 다른 타입의 레퍼런스도 bless 가능:
# bless [], 'Stack'; bless \$x, 'Ref'

메서드와 호출

->의 왼쪽은 객체, 오른쪽은 메서드; 메서드의 첫 번째 매개변수는 $self이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
package Counter;
sub new {
my $class = shift;
return bless { count => 0 }, $class;
}
sub inc {
my $self = shift;
$self->{count}++;
return $self->{count};
}
sub value {
my $self = shift;
return $self->{count};
}
package main;
my $c = Counter->new(); # 생성자 호출
$c->inc(); # 메서드 호출
$c->inc();
say $c->value(); # 2
# 메서드의 첫 인자는 객체:
# 함수 형식 호출과 동등:
Counter::value($c); # 2
# 메서드는 클래스와 부모 클래스에서 상속
# -> 는 체이닝 가능:$c->inc()->inc()

상속

use parent로 부모 클래스를 선언; 자식 클래스는 동일 이름 메서드를 오버라이드; 부모 클래스 메서드를 재사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
package Animal;
sub new {
my $class = shift;
my %a = @_;
return bless { name => $a{name} }, $class;
}
sub speak {
my $self = shift;
return "$self->{name} 叫";
}
sub eat {
my ($self, $food) = @_;
return "$self->{name} 吃 $food";
}
package Dog;
use parent -norequire, 'Animal';
sub speak { # 부모 메서드 오버라이드
my $self = shift;
return "$self->{name} 汪汪";
}
package main;
my $d = Dog->new(name => '旺财');
say $d->speak; # 旺财 汪汪
say $d->eat('骨头'); # 상속된 eat
# 메서드 탐색:객체 클래스 → 부모 클래스 체인
# 다중 상속은 @ISA 또는 use parent 사용

생성자

new는 클래스 이름과 인자를 받아 bless 객체를 반환; 다양한 생성 방식을 제공할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
package Point;
sub new {
my ($class, %args) = @_;
my $self = {
x => $args{x} // 0,
y => $args{y} // 0,
};
return bless $self, $class;
}
sub coords {
my $self = shift;
return ($self->{x}, $self->{y});
}
# 변형:리스트에서 생성:
sub from_list {
my ($class, $pair) = @_;
return $class->new(x => $pair->[0],
y => $pair->[1]);
}
package main;
my $p = Point->new(x => 3, y => 4);
my ($x, $y) = $p->coords;
say "$x,$y"; # 3,4
# 필드 기본값과 검증은 new 에 둔다
# 명명 인자는 팻 애로우가 더 명확

캡슐화와 접근자

필드 접근을 메서드로 캡슐화하고, 접근자에 검증을 추가; 외부에서 직접 필드를 변경하지 못하게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
package Account;
sub new {
my ($class, $name) = @_;
return bless {
name => $name,
money => 0,
}, $class;
}
# 읽기 접근자:
sub name { my $s = shift; return $s->{name} }
sub money { my $s = shift; return $s->{money} }
# 쓰기 접근자(검증 포함):
sub deposit {
my ($self, $amt) = @_;
die "金额必须为正" if $amt <= 0;
$self->{money} += $amt;
}
package main;
my $acc = Account->new('alice');
$acc->deposit(100);
say $acc->name; # alice
say $acc->money; # 100
# 의미 검증은 메서드 안에 집중
# $acc->{money} 를 직접 바꾸면 검증을 우회

연산자 오버로딩

use overload로 +, "" 등의 연산자를 정의; 객체가 숫자·문자열처럼 연산에 참여하도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
package Money;
use overload (
'""' => 'as_string', # 문자열화
'+' => 'add', # 덧셈
'fallback' => 1,
);
sub new {
my ($class, $cents) = @_;
return bless { cents => $cents }, $class;
}
sub as_string {
my $self = shift;
return sprintf %.2f", $self->{cents} / 100;
}
sub add {
my ($a, $b) = @_;
return Money->new($a->{cents} + $b->{cents});
}
package main;
my $m1 = Money->new(199);
my $m2 = Money->new(1);
say $m1 + $m2; # ¥2.00
say "$m1"; # ¥1.99
# 오버로드 가능:+ - * / <=> cmp 등
# 참/거짓·레퍼런스 비교의 의미 주의

Moo/Moose

Moo 경량 객체 시스템: has로 필드 선언, 접근자 자동 생성, extends로 상속.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# Moo(가볍고, 소규모 프로젝트에 추천):
package Employee;
use Moo;
has name => (is => 'ro', required => 1);
has salary => (is => 'rw', default => 0);
sub annual {
my $self = shift;
return $self->salary * 12;
}
package Manager;
use Moo;
extends 'Employee';
has team_size => (is => 'ro', default => 0);
package main;
my $e = Employee->new(name => 'Rex');
$e->salary(5000);
say $e->name; # Rex
say $e->annual; # 60000
# Moo 는 접근자를 자동 생성
# is => 'ro' 는 읽기 전용,'rw' 는 읽기/쓰기
# Moose 는 완전판(타입/롤/메타프로그래밍)
# 스크립트는 Moo,대규모 프로젝트는 Moose

역할과 조합

Moo::Role은 재사용 가능한 메서드 집합을 정의하며, with로 도입한다; 조합이 상속보다 낫다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 롤은 재사용 가능한 메서드 집합:
package Walkable;
use Moo::Role;
sub walk { "行走中" }
package Car;
use Moo;
with 'Walkable'; # 롤 도입
sub drive { "驾驶中" }
package main;
my $car = Car->new;
say $car->walk; # 行走中
say $car->drive; # 驾驶中
# 여러 롤 조합:
# with 'Walkable', 'Drivable';
# 같은 이름의 메서드는 with 순서에서 나중 것이 우선
# 롤은 다이아몬드 상속 문제를 피함
# Moose 의 롤도 생각은 같음
# 순수 손코딩은 해시에 서브루틴을 저장해 모방 가능
# 공유 동작은 상속보다 롤 우선

11.오류 처리

die/warn, eval 캐치, Try::Tiny, Carp, 그리고 사용자 정의 예외.

die와 warn

warn은 경고를 출력하고 종료하지 않는다; die는 출력하고 종료한다; die는 문자열이나 객체를 던질 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# warn 은 STDERR 로 출력하고,종료하지 않음:
warn "警告:配置缺失\n";
# die 는 출력 후 종료(0 이 아닌 상태):
# die "致命错误\n";
# die 이후의 코드는 실행되지 않음
# die 는 eval 안에서 포착 가능:
eval {
die "可捕获的错误\n";
};
say "捕获: $@" if $@;
# die 는 객체/레퍼런스도 전달 가능:
die { code => 500, msg => "server error" };
# 기본 종료 코드:
# die 는 $! 또는 255 사용;exit N 으로 커스텀
# 상식:$! 에는 최근 시스템 오류가 기록됨
# 프로덕션 코드:die 앞에 $! 를 붙여 원인 추적 용이

eval 캐치

eval BLOCK은 예외를 캐치하여, 오류 시 $@에 저장한다; 성공하면 블록 내 마지막 표현식을 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# eval BLOCK 으로 예외 포착:
eval {
die "boom";
};
if ($@) {
say "捕获: $@";
}
# 성공 시 $@ 는 비어 있음
# eval 의 반환값:
my $result = eval {
compute(); # 예외면 undef 반환
};
# 오류 시 undef 반환
# 중첩 eval 에서 $@ 저장:
my $outer_err;
eval {
eval { die "内层" };
$outer_err = $@;
};
# 문자열 eval 은 보안 문제가 있음:
# eval $code; # 비추천
# 예외 포착에는 eval BLOCK 만 사용
# 문법 오류는 컴파일 시점에 드러남

$@ 처리

$@는 최근 오류를 저장한다; 문자열을 매칭하거나 객체 타입을 검사할 수 있다; undef와 예외를 구분한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# $@ 에는 최근 오류가 들어 있음:
eval { die "出错啦" };
my $err = $@; # 저장 후 사용
say $err if $err;
# 내용으로 매칭:
eval { die "timeout" };
if ($@ && $@ =~ /timeout/) {
say "超时处理";
}
# die 에 객체를 넘기면 $@ 는 객체:
eval {
die bless {msg => 'x'}, 'MyError';
};
if (ref $@ eq 'MyError') {
say $@->{msg};
}
# 결과 undef 와 예외를 구분:
my $ok = eval { compute() };
if ($ok) { say "成功" }
# $@ 는 전역 변수이므로,호출을 넘어 의존하지 말 것
# 모던한 방식은 Try::Tiny 로 $_ 를 격리

Try::Tiny

try/catch/finally 구조가 명확하며, $_가 오류를 담는다; $@ 경합 문제를 해결한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use Try::Tiny;
# try/catch:
try {
die "boom";
} catch {
say "捕获: $_"; # 오류는 $_ 에 들어감
};
# 반환값:
my $result = try {
compute();
} catch {
warn "失败: $_";
undef; # 오류 시 undef 반환
};
# 마지막 정리:
try {
risky();
} catch {
say "出错";
} finally {
cleanup(); # 항상 실행됨
};
# 컨텍스트 유지:
my @rows = try { fetch_rows() };
# Try::Tiny 는 포착에 지역 $_ 사용
# $@ 가 중첩 eval 로 덮이는 것을 방지
# 코어 모듈 JSON 등의 오류도 포착 가능

Carp 보고

croak/carp는 호출자 위치를 보고한다; confess/cluck는 스택을 포함한다. 라이브러리 코드에 더 친화적이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use Carp qw(carp croak confess);
# croak 은 호출자 위치를 보고:
sub open_data {
croak "无法打开" unless -e "data.txt";
}
open_data(); # 오류는 호출 행을 가리킴
# carp 은 경고(호출자 보고):
sub deprecated { carp "已废弃" }
# confess 는 전체 스택을 출력:
sub inner { confess "内部错误" }
sub outer { inner() }
# 차이:
# die/warn 현재 행을 보고
# croak/carp 호출자 행을 보고
# confess/cluck 호출자 + 스택
# 라이브러리는 croak 을 쓰면 사용자가 위치를 찾기 쉬움

사용자 정의 예외

객체를 die로 던지고 ref/isa로 판단한다; ""를 오버로드하여 문자열 변환을 친화적으로 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
package MyError;
sub new {
my ($class, %args) = @_;
return bless { %args }, $class;
}
sub as_string {
my $self = shift;
return "$self->{code}: $self->{msg}";
}
use overload ('""' => 'as_string');
package main;
sub risky_thing {
my $ok = shift;
die MyError->new(code => 500, msg => "失败")
unless $ok;
}
# 던지기와 포착:
eval { risky_thing(0) };
if (ref $@ && $@->isa('MyError')) {
say "错误码: $@->{code}"; # 500
}
# 간단한 방법:
# use Exception::Class;
# 또는 Ouch 모듈로 원버튼 에러
# 객체 예외는 구조화된 정보를 담기 좋음

autodie

autodie는 open 등 시스템 호출 실패 시 자동으로 die 처리하여, 직접 or die를 쓸 필요가 없게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use autodie;
# 열기 실패 시 자동 die:
open my $fh, '>', 'out.txt';
# or die $! 가 더 이상 필요 없음
# 흔한 시스템 호출 커버:
# open close chdir system exec
# chmod mkdir rmdir rename unlink
# 범위 지정:
use autodie qw(open);
# 자동 검사 끄기:
no autodie;
# eval 과 결합해 포착:
eval {
open my $f, '<', 'missing.txt';
};
if ($@) { say "打开失败" }
# Try::Tiny 와 결합하면 더 좋음:
use Try::Tiny;
try {
open my $f, '<', 'x.txt';
} catch {
say "无法打开: $_";
};
# autodie 는 모던 Perl 에 포함(2.30+)

오류 처리 패턴

or die 관용구, 사전 검증, 실패 시 조기 반환; 최상위에서는 die, 라이브러리 내부에서는 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use strict;
use warnings;
use feature 'say';
# or die 관용구:
open my $fh, '<', 'data.txt'
or die "打开失败: $!";
# 사전에 인자 검증:
sub divide {
my ($a, $b) = @_;
die "除数为零" unless $b;
return $a / $b;
}
# 예외 대신 상태 반환:
sub parse {
my $s = shift;
return $s =~ /^\d+$/ ? $s + 0 : undef;
}
my $n = parse("12");
if (defined $n) {
say "数字: $n";
} else {
say "非法输入";
}
# 실패 시 조기 반환:
sub load_config {
my $path = shift;
return undef unless -f $path;
open my $f, '<', $path or return undef;
local $/;
return <$f>;
}
# 최상위는 die,라이브러리 내부는 반환 중심

12.파일 I/O

open 읽기/쓰기, 다이아몬드 연산자, slurp, 인코딩, 경로, 그리고 JSON.

열기와 읽기

세 인수 open으로 파일을 읽으며, 줄 단위 while이 메모리를 가장 적게 사용한다; -e/-f로 파일 속성을 판단한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 3-인자 open(권장):
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
say $line;
}
close $fh;
# 전부 배열로 읽기:
open my $f2, '<', 'data.txt' or die $!;
my @lines = <$f2>;
close $f2;
# 파일 속성 판별:
if (-e 'data.txt') { say "存在" }
-f 'data.txt'; # 일반 파일인지
-d 'dir'; # 디렉터리인지
-r 'data.txt'; # 읽기 가능한지
# 특수 문자를 포함한 파일명은 3-인자 open 이 안전
# 줄 단위 처리가 한꺼번에 읽기보다 메모리 절약

쓰기와 추가

>는 덮어쓰기, >>는 추가 쓰기; print/printf로 핸들에 쓴다; umask로 권한을 제어한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 덮어쓰기:
open my $out, '>', 'out.txt' or die $!;
print $out "hello\n";
close $out;
# 추가 쓰기:
open my $log, '>>', 'app.log' or die $!;
print $log "time=2026 msg=start\n";
close $log;
# 포맷 쓰기:
printf $out "%05d\n", 42;
# 여러 줄을 한 번에 쓰기:
open my $f, '>', 'data.txt' or die $!;
print $f @lines;
close $f;
# 기본 권한 0666 & ~umask:
umask 022;
# 자동 생성:> 와 >> 는 없으면 생성
# 바이너리 데이터는 먼저 binmode

다이아몬드 연산자

<>는 인수 파일을 읽고, 인수 없이는 STDIN을 읽는다; $.는 누적 줄 번호, $ARGV는 현재 파일명이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 다이아몬드 연산자로 인자 파일 읽기:
while (<>) {
chomp;
say "读到: $_";
}
# 실행:
# perl script.pl a.txt b.txt
# 인자 없으면 STDIN 을 읽음:
# echo hi | perl script.pl
# 핸들을 지정한 다이아몬드:
open my $fh, '<', 'data.txt' or die $!;
while (<$fh>) {
print; # 기본적으로 $_ 출력
}
# $. 는 파일을 넘어 줄 번호 누적:
while (<>) {
say "第 $. 行: $_";
}
# $ARGV 는 현재 파일명:
print "文件: $ARGV\n" if eof;
# 줄 단위로 처리하는 소형 도구에 적합

전체 파일 읽기

local $/를 undef로 두면 한 번에 읽는다; File::Slurper는 read_text 편의 함수를 제공한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 네이티브 slurp:
open my $fh, '<', 'data.txt' or die $!;
local $/; # 구분자를 undef 로
my $content = <$fh>;
close $fh;
# File::Slurper(편리):
use File::Slurper 'read_text';
my $text = read_text('data.txt');
# 한 번에 파일 쓰기:
use File::Slurper 'write_text';
write_text('out.txt', "hello\n");
# 바이너리 읽기:
# read_binary / write_binary
# 큰 파일에 slurp 금지:
# 메모리를 다 채우므로,줄 단위 루프 사용
# 인코딩 처리가 필요하면 decode 와 병행

binmode와 인코딩

binmode는 이진 안전이다; <:utf8 레이어로 UTF-8을 읽고 쓴다; raw는 인코딩 레이어를 제거한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 바이너리 안전 쓰기:
open my $fh, '>', 'img.bin' or die $!;
binmode $fh;
print $fh pack("C*", 0..255);
close $fh;
# 인코딩 레이어를 달고 열기:
open my $in, '<:encoding(UTF-8)', 'data.txt'
or die $!;
# utf8 레이어 약식:
open my $in2, '<:utf8', 'data.txt' or die $!;
# UTF-8 로 출력:
binmode STDOUT, ':utf8';
# 인코딩 레이어 제거:
binmode $fh, ':raw';
# 읽은 뒤 트랜스코딩:
use Encode;
while (my $line = <$in>) {
print encode('UTF-8', $line);
}
# 인코딩 레이어가 바이트 단위 세부를 처리
# 혼합 인코딩 파일은 수동 변환

표준 핸들

STDIN/STDOUT/STDERR; 핸들 복제, 터미널 판단, 즉시 플러시.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 표준 핸들:
print STDOUT "标准输出\n";
print STDERR "标准错误\n";
my $line = <STDIN>;
# 핸들 복사:
open my $copy, '>&', STDOUT;
print $copy "副本输出\n";
# 터미널인지 판별:
my $tty = -t STDOUT;
say $tty ? "终端" : "管道";
# 즉시 플러시(버퍼링 없음):
$| = 1; # print 마다 기록
# 고정 줄 수 읽기:
for (1..3) {
my $l = <STDIN>;
last unless defined $l;
}
# 닫기 실패 검사:
close $copy or die "关闭失败: $!";
# 리다이렉션은 셸이 처리:
# perl script.pl > out.txt 2>&1

경로 작업

Cwd는 현재 디렉터리, File::Spec는 크로스 플랫폼 결합, glob은 파일 매칭, File::Find는 재귀.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
use Cwd;
my $cwd = getcwd();
say $cwd;
use File::Spec;
my $path = File::Spec->catfile('data', 'sub', 'f.txt');
use File::Basename;
my ($name, $dir, $ext) = fileparse(
'data/app.pl', qr/\.[^.]*/);
# 절대 경로:
my $abs = File::Spec->rel2abs('data.txt');
# 파일 매칭:
for my $f (glob 'data/*.txt') {
say $f;
}
# 재귀 순회:
use File::Find;
find(sub { say $File::Find::name }, '.');
# 경로 결합은 catfile 을 쓰고,문자열 결합 금지

JSON 처리

JSON::PP는 코어 모듈이다: encode/decode, 미화, UTF-8. 운영 환경에서는 JSON::XS를 사용 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# JSON::PP 코어 모듈(5.14+):
use JSON::PP;
my $json = JSON::PP->new->utf8->pretty;
# 인코딩:
my $data = { name => 'Rex', tags => ['dev'] };
my $str = $json->encode($data);
# 디코딩:
my $back = $json->decode($str);
say $back->{name};
# 편리한 함수:
use JSON::PP qw(encode_json decode_json);
my $s = encode_json({a => 1}); # {"a":1}
my $h = decode_json('{"a":1}');
say $h->{a}; # 1
# 디코딩 실패 처리:
eval { decode_json("bad json") };
say "解析失败" if $@;
# 성능 중시라면 JSON::XS
# 구조체는 순수한 Perl 레퍼런스여야 함

13.흔한 오해

Perl 일상 개발에서 가장 자주 빠지는 함정과 올바른 작성법.

use strict 잊기

strict를 활성화하지 않으면 오타가 조용히 넘어가고 변수가 자동으로 전역화된다. strict는 안전의 최저선이다.

1
2
3
4
5
6
7
8
9
10
11
# BAD:엄격 검사를 켜지 않으면,오타가 조용히 통과
$foo = 1; # $f00 로 잘못 써도 발견하기 어려움
@array = (1, 2); # 선언 없이 자동 전역화
# GOOD:use strict 로 선언 강제
use strict;
use warnings;
my $foo = 1;
# 오타는 컴파일 시점에 오류
# strict 의 3면:vars refs subs
# 실행 시 문제는 warnings 에 맡김

컨텍스트 혼동

스칼라와 리스트 컨텍스트의 결과가 다르다: 배열은 스칼라 컨텍스트에서 길이를 반환한다. scalar로 명시한다.

1
2
3
4
5
6
7
8
9
10
11
12
# BAD:스칼라/리스트 컨텍스트 차이 무시
my @arr = (1, 2, 3);
my $n = @arr; # 3 은 배열 길이
my $s = @arr; # 역시 3
# GOOD:의도를 명시
my @arr2 = (1, 2, 3);
my $len = scalar @arr2; # 명시적으로 길이 구하기
my @copy = @arr2; # 리스트 컨텍스트 복사
my $last = $arr2[-1]; # 마지막 요소 가져오기
# 리스트를 원하면 (),스칼라를 원하면 $s
# scalar 로 스칼라 컨텍스트 강제

chomp 잊기

<STDIN>은 개행을 포함하며, chomp를 빠뜨리면 문자열 비교가 영원히 실패한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:chomp 를 잊으면,비교는 영원히 실패
my $line = <STDIN>; # 끝에 개행이 붙음
if ($line eq "quit") { say "不会到这" }
# GOOD:먼저 chomp 후 비교
my $line2 = <STDIN>;
chomp $line2;
if ($line2 eq "quit") { say "退出" }
# chomp 는 $_ 를 파괴적으로 변경하고,제거한 문자 수를 반환
# 숫자 입력도 먼저 chomp 권장:
my $num = <STDIN>;
chomp $num;
my $value = $num + 0;

비교 연산자 혼용

==은 수치 비교, eq는 문자열 비교. 잘못 쓰면 "10"과 "010"을 같다고 판단한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:문자열에 == 를 쓰면,숫자화 후 오판
my $a = "10";
my $b = "010";
if ($a == $b) { say "误判相等" } # 둘 다 10
# GOOD:문자열은 eq
my $a2 = "10";
my $b2 = "010";
if ($a2 eq $b2) { say "不同字符串" }
# 숫자 비교는 ==:
if (10 == 10.0) { say "数值相等" }
# 연산자 대조표:
# == != < > 숫자;eq ne lt gt 문자열
# 혼용은 찾기 어려운 bug 를 만든다

해시 순회 무질서

해시 내부 순서는 정해져 있지 않으며, 순회에 의존하면 무작위로 오류가 발생한다. 순서가 필요하면 키를 정렬한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BAD:해시 순회 순서에 의존
my %h = (b => 1, a => 2);
for my $k (keys %h) {
print "$k=$h{$k}\n"; # 순서는 랜덤
}
# GOOD:키로 정렬해 순회
my %h2 = (b => 1, a => 2);
for my $k (sort keys %h2) {
print "$k=$h2{$k}\n"; # a 가 먼저,b 가 나중
}
# 해시 내부 순서는 불확정,의존 금지
# 순서 유지가 필요하면 배열에 키를 저장:
my @order = qw(a b c);
for my $k (@order) { print "$k=$h2{$k}\n" }

읽기 시 autovivification

중첩 구조를 읽을 때 중간 참조가 자동으로 생성되어 메모리가 조용히 증가한다. 단락 평가로 빈 값 판정을 하면 피할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BAD:읽기 중첩에서 의도치 않게 구조가 생성됨
my $config;
if ($config->{db}{host}) {
say "有配置";
}
# 읽기로 $config->{db} 가 자동으로 레퍼런스가 된다!
# GOOD:단락 평가로 빈 판정
my $config2;
if ($config2 && $config2->{db}{host}) {
say "有配置";
}
# $config2 가 undef 면 오른쪽은 실행되지 않음
# 불필요한 구조는 생성되지 않음
# 쓰기는 자동 활성화가 필요(편리)
# 읽기는 먼저 defined / exists 를 판정
# no autovivification 으로 전부 끌 수 있음

local과 my 혼동

local은 전역 변수를 일시적으로 덮어쓰며(블록 밖에서 복원), my가 어휘적 지역 변수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BAD:local 을 지역 변수처럼 사용
sub work {
local $temp = 5; # 바꾸는 것은 전역 변수
}
# GOOD:일반 임시 변수는 my
sub work2 {
my $temp = 5; # 렉시컬 변수
return $temp;
}
# local 은 특수 변수를 임시로 바꿀 때 사용:
{
local $/; # 임시로 구분자를 undef 로
my $all = <$fh>; # 파일 전체를 한 번에 읽음
}
# local 은 $! $@ 의 로컬화에도 사용
# local 로 지역 변수를 가장하지 말 것

open 작성법 구식

두 인수 open + bareword 핸들은 심볼 테이블을 오염시키고 안전하지 않다. 세 인수 + 어휘 핸들을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:2-인자 open + bareword 핸들
open FH, ">out.txt"; # 심볼 테이블 오염,모드도 안전하지 않음
# GOOD:3-인자 open + 렉시컬 핸들
open my $fh, '>', 'out.txt' or die $!;
print $fh "数据\n";
close $fh;
# 읽기도 동일:
open my $in, '<', $user_file or die $!;
# 3-인자는 모드와 파일명을 구분
# > 로 시작하는 파일명도 오판하지 않음:
open my $f, '<', ">weird_name.txt";
# 렉시컬 핸들은 자동으로 닫혀,누수 없음

배열과 스칼라 컨텍스트

배열은 스칼라 컨텍스트에서 길이를 반환하고, 리스트는 마지막 항목을 반환한다. 길이는 scalar를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:배열이 스칼라 컨텍스트에서 첫 요소를 반환한다고 착각
my @arr = (1, 2, 3);
my $x = @arr; # 3(길이!)
my $y = (1, 2, 3); # 3(리스트 마지막 항)
# GOOD:컨텍스트 명시
my @arr2 = (1, 2, 3);
my $len = scalar @arr2; # 길이 3
my ($first, @rest) = @arr2; # 1, (2,3)
my $last = $arr2[-1]; # 3
# 콤마 표현식은 스칼라 컨텍스트에서 마지막 항을 취함
# 요소 수가 필요하면 scalar 로 명시
# 복사가 필요하면 @copy = @arr

14.멀티스레드

threads는 스레드를 생성하고, threads::shared는 데이터를 공유하며, 락과 큐로 동기화한다.

스레드 생성

threads->create는 새 스레드를 시작한다; join은 종료를 기다리고 반환값을 가져온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use threads;
# 스레드 생성:
my @threads;
for my $i (1..3) {
push @threads, threads->create(sub {
say "线程 $i 运行中";
return $i * 10;
});
}
# 기다렸다 결과 받기:
for my $t (@threads) {
my $result = $t->join();
say "结果: $result";
}
# join 은 스레드가 끝날 때까지 블록
# 스레드 종료 후 객체는 해제됨

join과 오류

join은 스레드 결과를 반환한다; 스레드 내 die는 메인 스레드로 전파되며, eval로 캐치할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use threads;
# join 은 반환값과 오류를 받음:
my $t = threads->create(sub {
die "线程内错误\n" if rand > 0.5;
return "成功";
});
my $result = $t->join();
if ($result) {
say "返回: $result";
}
# 스레드 내부 die 는 메인 스레드도 die 시킴
# join 에서 포착하려면 eval 사용:
my $t2 = threads->create(sub {
die "boom\n";
});
eval { $t2->join() };
say "捕获: $@" if $@;
# join 되지 않은 스레드는 누수
# detach 는 스레드 종료 시 자동 정리

공유 데이터

threads::shared의 :shared 변수는 스레드 간 공유된다; 일반 my 변수는 각 스레드의 복사본이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
use threads;
use threads::shared;
# 공유 변수 선언:
my $count :shared = 0;
my @list :shared = ();
my %seen :shared = ();
# 공유 해시의 중첩 키-값도 공유 필요:
my %stats :shared;
# 여러 스레드의 동시 ++ 는 원자적:
for (1..5) {
threads->create(sub { $count++ })->join();
}
say "count = $count"; # 5
# 공유 배열/해시만 스레드 간 읽기/쓰기 가능
# 일반 my 변수는 스레드마다 독립된 복사본

락과 세마포어

lock은 임계 영역을 보호한다; Thread::Semaphore는 흐름을 제한한다. 락은 스코프 종료 시 자동 해제된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
my $counter :shared = 0;
my $lock :shared;
# lock 으로 임계 영역 보호:
sub increment {
lock $lock; # 다른 스레드는 대기
$counter++;
# 스코프 종료 시 자동 해제
}
# 세마포어로 흐름 제한:
my $sem = Thread::Semaphore->new(2);
sub worker {
$sem->down(); # 허가 1개 획득
do_work();
$sem->up(); # 해제
}
# lock 은 블록 종료 시 자동 해제
# 교착 위험:중첩 lock 은 순서를 맞출 것

스레드 큐

Thread::Queue는 스레드 안전한 큐이며, dequeue가 비어 있으면 블록되어 생산자-소비자에 적합하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use threads;
use Thread::Queue;
# 스레드 안전 큐:
my $q = Thread::Queue->new();
# 프로듀서:
threads->create(sub {
$q->enqueue($_) for 1..5;
$q->end(); # 종료 표시
});
# 컨슈머:
my $worker = threads->create(sub {
while (defined(my $item = $q->dequeue())) {
say "处理: $item";
}
});
$worker->join();
# dequeue 는 큐가 비면 블록
# enqueue 는 일괄 지원:
# $q->enqueue(@items);
# 프로듀서-컨슈머 모델에 매우 적합

스레드 인수

create의 두 번째 인수부터는 서브루틴에 전달되는 인수이다; 어휘 변수는 스레드마다 독립적인 복사본이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use threads;
# 스레드는 인자와 렉시컬 변수를 복사:
my $base = 10;
my $t = threads->create(sub {
my $num = shift;
return $num + $base; # $base 는 복사본
}, 5);
say $t->join(); # 15
# create 마다 인자 전달:
my @results = map {
threads->create(sub {
my $x = shift;
return $x * $x;
}, $_)->join();
} 1..3;
# 렉시컬 변수는 스레드마다 한 벌의 복사본
# 공유가 필요하면 반드시 :shared 사용

병렬 작업

고정된 스레드 수로 작업을 가져와 처리한다; 공유 인덱스를 원자적으로 할당하여 작업마다 스레드를 생성하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use threads;
use threads::shared;
# 단순 병렬 태스크 풀:
my @jobs = 1..10;
my @workers;
my $total :shared = 0;
my $next :shared = 0;
# 고정 4개 워커 스레드:
for (1..4) {
push @workers, threads->create(sub {
while (1) {
my $job;
lock $next;
last if $next >= @jobs;
$job = $jobs[$next++];
# 시간이 걸리는 작업:
$total += $job;
}
});
}
$_->join() for @workers;
say "总和: $total";
# 태스크를 고정 스레드 풀에 분배
# 태스크마다 스레드를 새로 만드는 오버헤드 방지

스레드 풀 패턴

큐로 분배 + 결과 큐로 수집; end를 여러 번 호출하여 여러 블록된 스레드를 깨운다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use threads;
use Thread::Queue;
# 고정 스레드 풀 패턴:
my $q = Thread::Queue->new();
my $result_q = Thread::Queue->new();
# 컨슈머 스레드 풀:
my @workers = map {
threads->create(sub {
while (defined(my $job = $q->dequeue())) {
my $out = process($job);
$result_q->enqueue($out);
}
})
} 1..4;
# 태스크 분배:
$q->enqueue($_) for 1..100;
$q->end() for 1..4; # 모든 스레드 종료 신호
# 결과 수집:
my @results;
while (my $r = $result_q->dequeue()) {
push @results, $r;
}
$_->join() for @workers;
# 스레드 풀은 스레드를 재사용해 오버헤드 최소
# 결과 큐로 공유 구조 직접 쓰기 회피

15.네트워크 프로그래밍

TCP 소켓, HTTP 클라이언트, URL 파싱, DNS 조회.

TCP 클라이언트

IO::Socket::INET가 연결을 생성한다; print로 전송하고 <...>로 응답을 읽는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use IO::Socket::INET;
# TCP 클라이언트:
my $sock = IO::Socket::INET->new(
PeerHost => 'example.com',
PeerPort => 80,
Proto => 'tcp',
Timeout => 10,
) or die "无法连接: $@";
# HTTP 요청 보내기:
print $sock "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
# 응답 읽기:
while (my $line = <$sock>) {
print $line;
}
close $sock;
# 생성 실패 시 $@ 에 원인이 들어감
# Timeout 으로 무한 블록 방지

TCP 서버

Listen으로 포트를 청취하고, accept로 연결을 수락한다; 각 연결은 에코하거나 자식 프로세스에 위임할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use IO::Socket::INET;
# TCP 서버:
my $server = IO::Socket::INET->new(
LocalAddr => '0.0.0.0',
LocalPort => 8080,
Proto => 'tcp',
Listen => 10,
ReuseAddr => 1,
) or die "无法监听: $@";
say "监听 8080 端口";
while (my $client = $server->accept()) {
# 각 연결을 단순 에코:
my $line = <$client>;
print $client "收到: $line";
close $client;
}
# 다중 연결은 fork 와 결합해 처리
# ReuseAddr 로 재시작 시 포트 점유 방지

HTTP GET

HTTP::Tiny는 코어 모듈이다; get은 status/content를 반환하며, JSON::PP와 함께 파싱할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use HTTP::Tiny;
my $ua = HTTP::Tiny->new(
timeout => 10,
agent => 'Mozilla/5.0',
);
# GET 요청:
my $res = $ua->get('https://api.example.com/data');
if ($res->{success}) {
say "状态: $res->{status}";
print $res->{content};
} else {
warn "请求失败: $res->{status}";
}
# JSON 응답 파싱:
use JSON::PP;
my $data = decode_json($res->{content});
say $data->{name};
# HTTP::Tiny 는 코어 모듈
# 리다이렉트/프록시는 옵션으로 설정 가능

HTTP POST

post는 임의의 body를 전송한다; post_form은 자동으로 URL 인코딩하여 폼을 제출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $ua = HTTP::Tiny->new();
# POST JSON:
my $payload = encode_json({
name => 'Rex',
age => 5,
});
my $res = $ua->post(
'https://api.example.com/users',
{
content => $payload,
headers => {
'Content-Type' => 'application/json',
},
},
);
# 폼 인코딩 POST:
my $form = $ua->post_form(
'https://example.com/login',
{ user => 'admin', pass => 'secret' },
);
# 결과 확인:
if ($res->{success}) {
say "已提交";
}
# post_form 은 자동으로 URL 인코딩
# 파일 업로드는 multipart 나 파일 핸들 사용

URL 파싱

URI 모듈로 URL을 분해·구축; query_form으로 쿼리 매개변수를 읽고 쓰며 자동 인코딩.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use URI;
# URL 파싱:
my $uri = URI->new('https://user:[email protected]:8080/path?a=1&b=2#frag');
say $uri->scheme; # https
say $uri->host; # example.com
say $uri->port; # 8080
say $uri->path; # /path
# 쿼리 파라미터:
my %query = $uri->query_form;
say $query{a}; # 1
# URL 구성:
my $u = URI->new('https://example.com/api');
$u->query_form(name => 'Rex', page => 2);
print $u; # 인코딩된 쿼리 문자열
# 상대 경로 결합:
my $abs = URI->new_abs('about.html', 'https://site.com/docs/');
# URI 모듈이 인코딩/이스케이프 처리
# URL 을 손으로 만들지 말고,URI 로 정확성 보장

DNS 조회

Net::DNS가 A/MX/TXT 레코드를 해석; search는 응답 객체를 반환하며 외부 모듈이 필요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use Net::DNS;
# A 레코드 조회:
my $resolver = Net::DNS::Resolver->new();
my $query = $resolver->search('example.com');
if ($query) {
for my $rr ($query->answer) {
if ($rr->type eq 'A') {
say $rr->address;
}
}
} else {
warn "查询失败: ", $resolver->errorstring;
}
# 다른 레코드 타입:
# $resolver->search('example.com', 'MX');
# $resolver->search('example.com', 'TXT');
# 또는 서버를 수동 지정:
Net::DNS::Resolver->new(nameservers => ['8.8.8.8']);
# Net::DNS 모듈 필요(코어 아님)

소켓 옵션

setsockopt은 하위 옵션을 설정; timeout은 블록을 제어; IO::Select는 비블록 대기에 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use IO::Socket::INET;
my $sock = IO::Socket::INET->new(
PeerHost => 'example.com',
PeerPort => 443,
Proto => 'tcp',
Timeout => 5,
) or die "连接失败";
# 옵션 설정:
$sock->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1);
# 타임아웃:
$sock->timeout(10);
# 읽기/쓰기:
print $sock "ping\n";
my $reply = <$sock>;
# 논블로킹 읽기:
use IO::Select;
my $sel = IO::Select->new($sock);
if ($sel->can_read(2)) {
my $data = <$sock>;
}
# SO_RCVBUF 등 버퍼 옵션 조정 가능
# IO::Select 로 여러 핸들 대기 구현

REST 클라이언트

HTTP::Tiny를 감싸 인증과 JSON을 통합 처리; 오류는 일관되게 호출자에게 던짐.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $base = 'https://api.example.com/v1';
my $ua = HTTP::Tiny->new(timeout => 10);
# 범용 REST 래퍼:
sub api {
my ($method, $path, $body) = @_;
my $url = $base . $path;
my %opts = (headers => {
'Accept' => 'application/json',
'Authorization' => 'Bearer token123',
});
$opts{content} = encode_json($body) if $body;
my $res = $ua->request($method, $url, \%opts);
return decode_json($res->{content})
if $res->{success};
die "API 错误: $res->{status}";
}
my $user = api('GET', '/users/1');
my $created = api('POST', '/users',
{ name => 'Rex', age => 5 });
# 오류는 일괄적으로 호출자에게 던짐
# 페이지네이션/재시도는 필요에 따라 강화

16.날짜와 시간

time/localtime, strftime 포맷, Time::Piece와 DateTime 처리.

localtime 분해

time으로 epoch를 얻음; localtime은 연월일시분초로 분해하며 월과 년의 오프셋에 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
# localtime 은 시간의 각 요소를 반환:
my ($sec, $min, $hour, $mday, $mon, $year,
$wday, $yday, $isdst) = localtime;
# 월은 0부터,연도는 오프셋:
my $display = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday,
$hour, $min, $sec;
say $display;
# gmtime 은 UTC 를 반환:
my @utc = gmtime;
# time 은 현재 epoch 를 반환:
my $now = time;
say "当前时间戳: $now";
# localtime 은 시스템 시간대에 의존
# 포맷은 strftime 이 더 명확

strftime 포맷

POSIX::strftime는 %Y %m %d 같은 자리표시자로 포맷; %A %B는 요일과 월 이름을 출력.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use POSIX 'strftime';
# strftime 으로 시간 포맷:
my $now = time;
my $fmt = strftime(
'%Y-%m-%d %H:%M:%S', localtime $now);
say $fmt; # 2026-08-02 12:34:56
# 자주 쓰는 형식:
# %Y 4자리 연도 %y 2자리
# %m 월 %d 일 %H 시 %M 분 %S 초
# %A 요일 전체 이름 %a 약어
# %B 월 전체 이름 %b 약어
# %j 연중 몇 번째 날
my $date = strftime('%A %B %d', localtime);
my $custom = strftime('%Y/%m/%d %I:%M %p',
localtime); # 12시간제
# strftime 은 C 함수이며,로케일 반영 출력

타임스탬프 변환

timelocal/timegm은 로컬 또는 UTC 시간 배열을 다시 epoch로 변환하며 오프셋에 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use Time::Local;
# time 은 현재 epoch 초를 반환:
my $now = time;
# epoch 를 배열로 변환(로컬/UTC):
my @local = localtime $now;
my @utc = gmtime $now;
# 배열을 epoch 로 변환:
use Time::Local 'timelocal';
my $epoch = timelocal(
$sec, $min, $hour, $mday, $mon, $year);
# 날짜 문자열에서 epoch 파싱:
my $ts = timelocal(0, 0, 12, 2, 7, 126);
say "2026-08-02 12:00 的 epoch: $ts";
# timegm 은 UTC 를 처리:
use Time::Local 'timegm';
my $utc_epoch = timegm(0, 0, 0, 1, 0, 126);
# 월과 연도의 오프셋 주의(월 -1,년 -1900)

sleep 지연

sleep은 정수 초; Time::HiRes는 소수와 마이크로초 지원; select는 비블록 대기 가능.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# sleep 은 지정 초만큼 블록:
sleep 2;
say "睡醒了";
# 소수 지원(Time::HiRes 필요):
use Time::HiRes qw(sleep usleep);
sleep 0.5; # 반 초
usleep 500000; # 마이크로초
# 정시 루프:
for (1..3) {
say "第 $_ 次";
sleep 1;
}
# 논블로킹 지연 방법:
# select 의 빈 타임아웃 사용:
use IO::Select;
my $sel = IO::Select->new();
$sel->can_read(0.5); # 기다리지만 읽지 않음
# sleep 은 시그널 인터럽트 후 남은 초를 반환
# 프로덕션 스케줄링은 Time::HiRes 가 더 정확

소요 시간 측정

time은 초 단위 측정; Time::HiRes는 밀리초 단위; 일자 변경/시간대 차이는 DateTime 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 간단한 계측(초):
my $t0 = time;
do_work();
my $elapsed = time - $t0;
say "耗时 $elapsed 秒";
# 고정밀 계측:
use Time::HiRes qw(time);
my $s = time;
do_work();
my $ms = (time - $s) * 1000;
say "耗时 $ms 毫秒";
# 시간대/날짜 차이는 DateTime:
use DateTime;
my $a = DateTime->now;
my $b = $a->clone->add(days => 3);
my $dur = $b - $a;
say $dur->in_units('hours'); # 72
# DateTime 은 기간 객체를 반환
# 서머타임 처리는 DateTime 우선

DateTime 객체

DateTime의 완전한 시간 처리: 생성, 값 접근, 시간대 변환과 날짜 산술.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use DateTime;
# 생성과 값 가져오기:
my $dt = DateTime->now;
my $d2 = DateTime->new(
year => 2026, month => 8, day => 2,
hour => 12, minute => 30,
);
say $d2->ymd; # 2026-08-02
say $d2->hms; # 12:30:00
say $d2->year;
# 시간대:
my $tokyo = DateTime->now(time_zone => 'Asia/Tokyo');
my $utc = $tokyo->set_time_zone('UTC');
# 산술:
my $later = $dt->clone->add(days => 7, hours => 3);
my $earlier = $dt->clone->subtract(months => 1);
# 포맷:
say $dt->format_cldr('yyyy-MM-dd HH:mm');
# DateTime 은 외부 모듈로,기능이 풍부
# 서머타임/시간대 변환 처리에 안전

문자열 파싱

Time::Piece::strptime은 형식에 따라 날짜 문자열을 파싱하여 epoch와 차이를 얻음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use Time::Piece;
# Time::Piece 는 strptime 파싱 내장:
my $t = Time::Piece->strptime(
'2026-08-02 12:30:00', '%Y-%m-%d %H:%M:%S');
say $t->epoch;
# 다른 형식 파싱:
my $d = Time::Piece->strptime(
'02/Aug/2026', '%d/%b/%Y');
# 포맷 출력:
say $t->strftime('%Y/%m/%d');
# epoch 에서 생성:
my $now = localtime; # Time::Piece 객체
say $now->epoch;
say $now->year; # 2026
# 차이:
my $diff = $t - $now;
say $diff->days;
# Time::Piece 는 코어 모듈
# strptime 은 실패 시 예외를 던짐

포맷 객체

DateTime::Format::Strptime은 파싱과 포맷을 통합; RFC3339는 ISO 시간 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use DateTime;
# DateTime 포맷과 파싱:
use DateTime::Format::Strptime;
my $format = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M:%S',
time_zone => 'UTC',
);
# 문자열 파싱:
my $dt = $format->parse_datetime(
'2026-08-02 12:00:00');
# 포맷 출력:
my $str = $format->format_datetime($dt);
say $str;
# 자주 쓰는 pattern:
# %Y 년 %m 월 %d 일
# %H 시 %M 분 %S 초 %z 시간대 오프셋
my $iso = $dt->format_cldr('yyyy-MM-dd HH:mm:ss');
# 더 간결한 RFC3339:
use DateTime::Format::RFC3339;
my $rfc = DateTime::Format::RFC3339->parse_datetime(
'2026-08-02T12:00:00Z');
# 형식 객체로 파싱/포맷을 통일

17.프로세스와 명령

system/역따옴표로 명령 실행, 환경 변수, @ARGV, 시그널과 파이프.

명령 실행

역따옴표는 출력을 캡처; system은 실행 후 캡처하지 않음; 리스트 형식은 셸 주입을 피함.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
# 백틱으로 출력 캡처:
my $out = `ls -la`;
print $out;
# qx 가 더 명확(구분자 커스텀 가능):
my $date = qx(date);
chomp $date;
# system 은 실행만 하고 캡처하지 않음:
system("echo hello");
# system 은 종료 상태를 반환
# 백틱은 스칼라 컨텍스트에서 출력 전체를 반환
# 리스트 컨텍스트에서는 줄 단위로 분할:
my @lines = `cat file.txt`;
# 이스케이프와 인젝션 주의:
# 리스트 형식으로 shell 연결 회피:
system("ls", "-l");
# 사용자 입력을 명령 문자열에 연결하지 말 것

exec와 system

exec는 현재 프로세스를 교체; system은 자식 프로세스 종료를 기다림; $?는 종료 상태를 저장.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
# exec 은 현재 프로세스를 교체:
# exec "ls", "-l"; # 이후의 코드는 실행되지 않음
# system 은 자식 프로세스 종료를 기다림:
system("echo", "hi");
say "system 之后";
# 반환값은 종료 코드:
my $status = system("true");
say "退出码: $status"; # 0
# $? 에 마지막 상태가 들어감:
my $exit = $? >> 8; # 실제 종료 코드
my $sig = $? & 127; # 시그널
# exec 도 system 도 출력을 캡처하지 않음:
# 출력이 필요하면 백틱이나 open 파이프
# exec 는 다른 프로그램으로 교체할 때 사용

환경 변수

%ENV 해시로 환경 변수를 읽고 쓰며 이후 자식 프로세스에 영향; local로 임시 격리 가능.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 환경 변수 읽기:
my $home = $ENV{HOME};
say "主目录: $home";
# 환경 변수 설정:
$ENV{PATH} = '/usr/bin:/bin';
$ENV{LANG} = 'en_US.UTF-8';
# 모든 환경 변수 순회:
for my $key (sort keys %ENV) {
print "$key=$ENV{$key}\n";
}
# 자식 프로세스에 전달:
$ENV{MODE} = 'test';
system("echo", $ENV{MODE});
# 환경 변수 삭제:
delete $ENV{SECRET};
# %ENV 는 이후의 모든 자식 프로세스에 영향
# 스크립트 환경 분리는 local:
{
local $ENV{DEBUG} = 1;
run_tests();
} # 블록 종료 시 복원

명령행 인수

@ARGV는 인수 리스트; 복잡한 인수 파싱은 Getopt::Long 선언식 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# @ARGV 는 커맨드라인 인자:
my ($file, $count) = @ARGV;
say "文件: $file, 次数: $count";
# 인자는 곧 배열:
my @files = @ARGV;
for my $f (@files) {
say "处理: $f";
}
# shift 는 기본적으로 @ARGV 에서 가져옴:
my $first = shift @ARGV;
# 인자가 없으면 기본적으로 STDIN 을 읽음:
# while (<>) { ... }
# 인자 파싱은 Getopt::Long 권장:
use Getopt::Long;
my ($verbose, $name);
GetOptions(
'verbose' => \$verbose,
'name=s' => \$name,
);
say $verbose ? "详细模式" : "简洁模式";
say "名字: $name" if $name;
# $0 은 스크립트 이름

종료 상태

exit N으로 종료 코드 설정; 0은 성공, 0이 아니면 실패; END 블록은 종료 전에 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
# exit 은 종료하며 상태 코드 설정:
exit 0; # 성공
# exit 1; # 실패
# 인자 없으면 종료 코드 0
# 조건부 종료:
my $ok = check();
exit 1 unless $ok;
# die 의 종료 코드는 255(또는 $!):
# 실패 시나리오에서도:
if ($err) {
print STDERR "失败: $err\n";
exit 2;
}
# $? 와 system:
system("false");
say $?; # 256(128 + 종료 코드)
my $code = $? >> 8; # 1
# shell 과 상호작용할 때의 약속:
# 0 성공;0 이 아니면 실패
# END 블록은 종료 전에 실행:
END { print "清理\n" }
# 시그널 인터럽트 시 $? 로 원인 판단

시그널 처리

$SIG{INT}로 시그널 캡처; 처리 함수는 플래그만 설정하고 메인 루프가 응답.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
# 시그널 포착:
$SIG{INT} = sub {
print "收到中断,清理退出\n";
exit 1;
};
$SIG{TERM} = 'DEFAULT'; # 기본값 복원
$SIG{QUIT} = 'IGNORE'; # 무시
# 대기 루프:
while (1) { sleep 1 }
# 시그널 보내기:
kill 'TERM', $pid;
# 자식 프로세스에 시그널:
my $pid = fork();
if ($pid == 0) {
exit 0; # 자식 프로세스
}
kill 'USR1', $pid;
# 시그널 핸들러에서 무거운 작업 금지:
# 플래그만 세우고,메인 루프에서 처리:
my $stop = 0;
$SIG{INT} = sub { $stop = 1 };
while (!$stop) { work() }
# %SIG 는 전역 해시
# Windows 에서는 시그널 지원이 제한적

프로세스 파이프

-|은 자식 출력을 읽고 |-는 자식 입력에 쓰기; 양방향 상호작용은 IPC::Open3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 파이프로 명령 출력 읽기:
open my $fh, '-|', 'grep error /var/log/app.log'
or die "无法执行: $!";
while (my $line = <$fh>) {
chomp $line;
say "匹配: $line";
}
close $fh;
# 파이프에 쓰기:
open my $out, '|-', 'gzip > out.gz'
or die "无法执行: $!";
print $out "要压缩的数据\n";
close $out;
# 대체 작성법 open2/open3:
use IPC::Open3;
my $pid = open3(
my $in, my $err, 'cmd', 'args');
# '-|' 는 자식 프로세스 stdout 읽기
# '|-' 는 자식 프로세스 stdin 쓰기
# 양방향 상호작용은 IPC::Open3

출력 캡처

리다이렉트로 stdout/stderr 병합; $?로 종료 코드 확인; Capture::Tiny로 구조적 캡처.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# stdout + stderr 캡처:
my $out = `cmd 2>&1`;
# stderr 만 캡처:
my $err = `cmd 2>&1 1>/dev/null`;
# 종료 코드 확인(백틱):
my $result = `grep pattern file.txt`;
my $exit = $? >> 8;
if ($exit == 0) {
say "找到匹配";
} else {
say "无匹配或出错";
}
# 캡처 시 타임아웃 방지:
# 복잡한 상황은 IPC::Open2 나 IPC::Run
# 안전하게 실행하고 출력 얻기:
use Capture::Tiny qw(capture);
my ($stdout, $stderr, $exit) =
capture { system("ls", "-l") };
say $stdout;
# Capture::Tiny 는 외부 모듈
# 많은 명령 연동은 IPC::Run 검토

18.정규식

패턴 매칭, 문자 클래스, 수량사, 캡처 그룹, 사전 컴파일과 치환.

매칭 기초

=~는 매칭, !~는 비매칭; 앵커 ^ $ \b로 위치 지정; i 수식자로 대소문자 무시.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 매치 연산자 =~ m//:
my $str = "hello world";
if ($str =~ /world/) {
say "包含 world";
}
# 비매치 !~:
if ($str !~ /xyz/) {
say "不含 xyz";
}
# 앵커:
/^start/; # 행 시작
/end$/; # 행 끝
/\bword\b/; # 단어 경계
# 대소문자 무시:
/hello/i;
# 한 번 매치하고 결과 취득:
my ($match) = $str =~ /(\w+) (\w+)/;
# 다른 수식자는 이후 각 절 참조

매칭과 추출

g는 전역 매칭; 리스트 컨텍스트는 모든 일치를 반환; grep와 결합해 배열 필터링.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 매치 여부 판정:
if ($line =~ /error/) { ... }
# 리스트 컨텍스트로 전체 히트 가져오기:
my $str = "x1y2z3";
my @nums = $str =~ /(\d)/g; # 1 2 3
# 매치 횟수 세기:
my $count = () = $str =~ /\d/g;
# grep 필터와 조합:
my @words = qw(cat dog bird);
my @short = grep { length $_ < 4 } @words;
# 변수 보간으로 패턴 구성:
my $pat = "a.c";
$str =~ /$pat/; # . 은 임의의 문자
# 사전 컴파일은 이후 토픽:
my $re = qr/\d{3}-/;
$str =~ $re;
# grep/map 은 $_ 로 암묵적 반복

문자 클래스

\d \w \s와 그 부정; 사용자 정의 [...]와 제외 [^...]; 수량사.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
# 자주 쓰는 문자 클래스:
# \d 숫자 [0-9]
# \w 단어 문자 [A-Za-z0-9_]
# \s 공백 [ \t\n\r\f]
# 대문자는 부정:\D \W \S
# 커스텀 문자 클래스:
/[aeiou]/; # 모음
/[0-9a-fA-F]/; # 16진수
/[^0-9]/; # 숫자 아님
# 수량자:
/a*/; # 0 개 이상
/a+/; # 1 개 이상
/a?/; # 0 또는 1 개
/a{3}/; # 정확히 3 개
/a{2,4}/; # 2 에서 4 개
/a{2,}/; # 최소 2 개
# 그룹화와 선택:
/(foo|bar)/;
/(ab)+/;
# 특수 문자 이스케이프:
/\$dollar/;
/1\+1/;
# 점 . 은 기본적으로 임의의 문자(개행 제외)

수량사와 백트래킹

기본은 탐욕, 비탐욕은 ? 추가; 소유 수량사 +는 백트래킹 금지; 중첩 수량사는 재앙적 백트래킹 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
# 탐욕 vs 비탐욕:
my $s = "<b>hi</b><b>yo</b>";
my @greedy = $s =~ /<b>.*<\/b>/g; # 문자열 전체
my @lazy = $s =~ /<b>.*?<\/b>/g; # 두 개의 작은 덩어리
# 수량자:
# * 탐욕 0+ *? 비탐욕
# + 탐욕 1+ +? 비탐욕
# ? 0/1 ?? 비탐욕
# {n,m} {n,m}? 비탐욕
# 점유 수량자(역추적 금지):
my $big = "aaaaa";
$big =~ /a++a/; # 실패(a++ 가 점유)
$big =~ /a+a/; # 성공(역추적)
# 행 앵커 수식:
/^foo/m; # m 은 ^$ 가 각 행에 매치
/foo./s; # s 는 . 이 개행에도 매치
# 성능 주의:중첩 수량자는 재앙적 역추적 위험
# 단순한 대상은 고정 문자 클래스로 대체

캡처 그룹

$1 $2는 왼쪽 괄호 순서대로 캡처 접근; (?:)는 비캡처; (?<name>)는 이름 있는 캡처.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 캡처 그룹 $1 $2:
my $s = "name=alice age=30";
if ($s =~ /name=(\w+) age=(\d+)/) {
say "名字: $1, 年龄: $2";
}
# 리스트 컨텍스트로 전체 캡처 취득:
my ($name, $age) = $s =~ /name=(\w+) age=(\d+)/;
# 중첩 캡처:
if ("abc123" =~ /((\w+)(\d+))/) {
say $1; # abc123
say $2; # abc
say $3; # 123
}
# 비캡처 그룹:
"hello" =~ /(?:he)(llo)/; # $1 은 llo
# 이름 있는 캡처:
if ("Rex 5" =~ /(?<who>\w+) (?<num>\d+)/) {
say $+{who}; # Rex
say $+{num}; # 5
}
# $1-$9 는 1 부터 시작,미매치는 undef
# 캡처 그룹 순서는 여는 괄호의 등장 순서

이름 있는 캡처

(?<name>...)는 이름 있는 그룹 + %+ 해시 접근; \k<name>은 역참조.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 이름 있는 캡처 그룹:
my $line = "2026-08-02 12:30";
if ($line =~ /(?<year>\d{4})-(?<mon>\d{2})-(?<day>\d{2}) (?<time>\d{2}:\d{2})/) {
say "年: $+{year}";
say "月: $+{mon}";
say "日: $+{day}";
say "时间: $+{time}";
}
# %+ 해시 접근:
say $+{year};
# 역참조:
/(?<pair>\w)\k<pair>/; # 같은 문자의 반복
# 숫자 역참조:
/(\w)\1/;
# 분기 리셋:
"ab" =~ /(?|a(b)|c(d))/; # 어느 분기든 $1
# 이름 있는 캡처는 가독성을 크게 높임
# %- 는 같은 이름의 캡처를 모두 저장

정규식 사전 컴파일

qr//로 한 번 컴파일해 재사용; 모듈 내 공유로 큰 루프 성능 향상; 수식자 사용 가능.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 사전 컴파일 정규식:
my $email_re = qr/[\w.]+@[\w.]+\.\w+/;
my $phone_re = qr/1[3-9]\d{9}/;
# 매치 재사용:
my $text = "联系 a@b.com 或 13800138000";
my @emails = $text =~ /($email_re)/g;
my @phones = $text =~ /($phone_re)/g;
say "邮箱: @emails";
# 더 큰 정규식에 보간:
my $full = qr/($email_re|$phone_re)/;
# 모듈 내 공유(한 번만 컴파일):
{
my $DATE_RE = qr/\d{4}-\d{2}-\d{2}/;
sub is_date { $_[0] =~ $DATE_RE }
}
# 사전 컴파일로 매번 재컴파일 방지
# 큰 루프에서 성능 향상이 뚜렷
# qr 은 수식자를 붙일 수 있음:
my $ci = qr/foo/i;

치환과 변환

s///는 치환, g는 전역, i는 대소문자 무시; 치환 부분에 $1 사용 가능; tr///는 문자 변환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# s/// 치환:
my $s = "apple apple";
$s =~ s/apple/orange/; # 첫 번째만 치환
say $s; # orange apple
# g 로 전역 치환:
$s =~ s/apple/kiwi/g;
say $s; # kiwi kiwi
# i 로 대소문자 무시:
"HELLO" =~ s/hello/hi/i; # hi
# 치환에 캡처 사용:
my $date = "2026-08-02";
$date =~ s/(\d{4})-(\d{2})-(\d{2})/$2\/$3\/$1/;
# 치환 부분에서 $1 $2 사용
# 빈 치환으로 개수 세기:
my $count = ($s =~ s/x//g);
# tr/// 는 문자 단위 변환(정규식 없음):
my $t = "hello";
$t =~ tr/a-z/A-Z/; # HELLO
# s 는 치환 횟수를 반환(스칼라 컨텍스트)
# 치환 부분의 백슬래시 이스케이프에 주의

19.모듈과 공학화

모듈 구조, use/require, 옵션 파싱, CPAN 설치와 테스트.

모듈 구조

패키지 이름은 파일 경로에 대응; Exporter는 내보내기; 1;로 끝나야 use 성공.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package My::Greet;
use strict;
use warnings;
# 내보내기 목록:
use Exporter 'import';
our @EXPORT_OK = qw(greet);
our @EXPORT = qw(); # 기본적으로 내보내지 않음
sub greet {
my $name = shift;
return "hello, $name";
}
# 버전 번호:
our $VERSION = '0.01';
1; # 모듈은 참을 반환해야 함
# 파일명은 패키지명에 대응:
# My/Greet.pm → My::Greet
# @EXPORT 는 자동 내보내기;@EXPORT_OK 는 명시 요청 필요

use와 require

use는 컴파일 시 로드 후 임포트; require는 런타임 로드; do는 심볼 테이블 없이 파일 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# use 는 컴파일 시 로드 + 임포트:
use List::Util 'sum';
say sum(1, 2, 3);
# 로드만 하고 임포트 안 함:
use List::Util ();
# require 는 실행 시 로드:
require JSON::PP;
say JSON::PP::encode_json({a => 1});
# do 는 파일 실행(심볼 테이블 없음):
my $cfg = do './config.pl';
# 로드 여부 확인:
my $loaded = $INC{'List/Util.pm'};
# 로드 실패:
# use 는 컴파일 시 실패하면 종료
# require 는 실패 시 예외를 던져 포착 가능:
eval { require Missing::Module };
say "加载失败: $@" if $@;

모듈 설치

cpanm으로 CPAN 모듈 설치; 핵심 모듈은 Perl과 함께 배포; cpanfile은 의존성 기록.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 설치된 모듈 찾기:
# perldoc -l Module
# 모듈 정보 확인:
# perldoc Module::Name
# cpanm 설치:
# cpanm DateTime
# 버전 지정:
# 스크립트에서 가용성 확인:
use List::Util; # 코어 모듈은 Perl 에 포함
# 코어 vs CPAN 구분:
# 코어:Perl 과 함께 배포,설치 불필요
# CPAN:직접 설치 필요(예 Moose)
# 이식성을 위해 코어 모듈 의존 권장:
use JSON::PP; # 코어
use HTTP::Tiny; # 코어
use Time::Piece; # 코어
# 코어 모듈 목록 보기:
# corelist -a Time::Piece
# 프로젝트 의존은 cpanfile 에 기록

Getopt::Long

선언형 옵션 파싱: =s 문자열, =i 정수, 스위치, 별칭과 반전.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use Getopt::Long;
# 옵션 선언:
my ($verbose, $name, $count, $help);
GetOptions(
'verbose|v' => \$verbose,
'name=s' => \$name,
'count=i' => \$count,
'help|h' => \$help,
) or die "参数错误";
# 사용:
if ($help) { print_usage(); exit 0 }
say $verbose ? "详细模式" : "简洁模式";
say "名字: $name" if defined $name;
say "次数: $count" if defined $count;
# 자주 쓰는 타입:
# =s 문자열 =i 정수 =f 부동소수점
# =s@ 여러 값 ! 반전 스위치
# 긴 옵션은 --name=value 와 --name value 지원
# 짧은 옵션 병기:-vh 는 -v -h 와 동등
# GetOptions 는 약어를 지원(모호하지 않을 때)

자주 쓰는 CPAN 모듈

DateTime/Moose/DBI 등 생태계; MetaCPAN으로 검색; eval로 가용성 탐지.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 자주 쓰는 CPAN 모듈(설치 필요):
# DateTime 날짜/시간 처리
# Moose / Moo 객체 시스템
# DBI 데이터베이스 접근
# Mojo::UserAgent 웹 클라이언트
# JSON::XS 빠른 JSON
# LWP::UserAgent 전통 HTTP 클라이언트
# Text::CSV CSV 파싱
# XML::LibXML XML 파싱
# 설치 방법:
# cpanm Mojo::UserAgent
# 또는 스크립트에서 탐지:
my $has_json = eval { require JSON::XS; 1 };
# 사용 예(Mojolicious):
# use Mojo::UserAgent;
# my $ua = Mojo::UserAgent->new;
# my $tx = $ua->get('https://example.com');
# say $tx->result->body;
# 설치된 모듈 확인:
# perldoc perlmodlib
# 모듈 검색은 MetaCPAN:
# https://metacpan.org

스크립트와 명령행

shebang으로 인터프리터 지정; perl -w/-e/-ne 같은 자주 쓰는 명령행 옵션.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
# 실행 가능 스크립트:
# chmod +x script.pl
# ./script.pl 을 직접 실행
# 이식 가능한 shebang(택일):
# #!/usr/bin/env perl
# PATH 안의 perl 사용(가상 환경 친화)
# 커맨드라인 실행:
# perl script.pl
# perl -w script.pl # 경고 켜기
# perl -Mstrict script.pl # strict 켜기
# perl -e 'print "hi"' # 한 줄 실행
# perl -ne 'print if /foo/' file # 줄 단위 처리
# 인자 확인:
my ($input, $output) = @ARGV;
die "用法: $0 <in> <out>\n" unless $input && $output;
say "处理 $input$output";
# $0 은 스크립트 이름;@ARGV 는 인자 목록
# 스크립트 상단에 반드시 use strict + warnings

패키징과 디렉터리

lib/t 디렉터리 배치, Makefile.PL 빌드; -Ilib로 로컬 개발 로드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package My::Lib;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT_OK = qw(helper);
our $VERSION = '0.01';
sub helper { return 42 }
1;
# 디렉터리 구성:
# lib/My/Lib.pm 모듈 본체
# t/01-basic.t 테스트
# Makefile.PL / cpanfile 빌드 설명
# Changes / README 변경 이력과 문서
# 로컬 개발 로드:
# -I 로 라이브러리 디렉터리 지정:
# perl -Ilib t/01-basic.t
# 또는 PERL5LIB=lib
# 빌드와 테스트(정석 플로우):
# perl Makefile.PL
# make
# make test
# 모듈 패키징/배포:
# cpanm 은 로컬 디렉터리에서 설치 가능
# 버전 번호를 바꾸면 Changes 도 동기화

Test::More

plan으로 단언 수 선언; is/ok/like로 단언; prove -l로 테스트 일괄 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use Test::More;
# 테스트 수 선언:
plan tests => 4;
# 단언:
is(add(2, 3), 5, "2+3=5");
ok(1 == 1, "真值断言");
isnt("a", "b", "不等");
like("hello", qr/^he/, "匹配断言");
sub add { $_[0] + $_[1] }
# 테스트 파일은 단독 실행:
# perl t/01-basic.t
# 또는 prove 와 병행:
# prove -l t/
# 다른 단언:
# is_deeply 는 복잡한 구조 비교
# cmp_ok 는 커스텀 비교
# done_testing 은 개수 자동 집계
# 조건부 테스트 스킵:
# SKIP: { skip "Windows 不支持", 1 }
# Test::More 는 코어 모듈

공식 링크

공식 문서와 리소스에 대한 직접 링크.

이 치트시트에 대해

본 페이지는 Perl 5.38의 자기 완결적인 치트시트로, 언어 핵심과 가장 자주 쓰이는 내장 함수(소수의 CPAN 모듈 포함)가 실제 프로젝트에서 약 80%의 흔한 용법을 다룹니다. 내용은 현대 관용구를 지향합니다: `use strict` + `use warnings` 필수, 접미사형 제어 흐름, scalar/list 컨텍스트 구분, 레퍼런스로 복잡한 자료구조 만들기, `say` / `state` / `signatures`. Perl은 Larry Wall이 1987년에 발표한 다중 패러다임 동적 언어로, 「문제를 해결하는 방법은 여러 가지다(TMTOWTDI)」로 유명하며, 텍스트 처리, 시스템 관리와 웹 백엔드 영역에 깊은 저력이 있습니다. 권위 있는 참고는 Perl과 함께 설치되는 `perldoc`과 [Perl 공식 문서](https://perldoc.perl.org/)를 참고하세요. 19개 장은 각각 한 주제에 집중합니다 — 첫 프로그램부터 레퍼런스, bless 객체 지향과 흔한 실수까지. 각 절은 8–14개의 예제가 있는 소절(각 5–20줄)로 나뉘며, 총 약 150개 주제입니다. 코드 스니펫은 의도적으로 짧고 자기 해설적이므로 복사 후 바로 `perl`로 실행할 수 있습니다. 모든 처리는 브라우저에서 완료됩니다 — 업로드 없음, 추적 없음. 본 페이지는 GuruToolkit 무료 개발자 도구 모음의 일부이며, 코드 스니펫은 어떠한 보증 없이 자유롭게 사용할 수 있습니다.

버전 2.1.0