Perl 포맷
Perl 포맷 (perlform)
Perl에는 간단한 리포트와 차트를 만들 수 있게 도와주는 메커니즘이 있어요. 이 문서는 그 포맷(format) 기능을 어떻게 선언하고 쓰는지 강사가 옆에서 설명하듯 안내해 드려요. 인쇄될 모양에 가깝게 출력 페이지를 코딩할 수 있다는 점이 핵심이에요.
Perl에는 간단한 리포트와 차트를 생성하도록 돕는 메커니즘이 있어요. 이를 위해 Perl은 출력 페이지를 인쇄됐을 때의 모습에 가깝게 코딩하도록 도와줘요. 페이지에 몇 줄이 있는지, 지금 몇 페이지인지, 페이지 헤더를 언제 출력할지 같은 것을 추적할 수 있어요. 키워드는 FORTRAN에서 빌려왔어요: 선언은 format(), 실행은 write()죠. 각 항목은 perlfunc에서 볼 수 있어요. 다행히 레이아웃은 훨씬 읽기 쉽고 BASIC의 PRINT USING 문에 더 가까워요. 가난한 사람의 nroff(1)라고 생각하면 돼요.
본문
DESCRIPTION
포맷은 패키지나 서브루틴처럼 실행이 아니라 선언되므로 프로그램 어느 지점에나 올 수 있어요.(보통은 다 함께 모아 두는 게 좋지만요.) Perl의 다른 모든 "타입"과는 별개의 네임스페이스를 가져요. 즉 "Foo"라는 함수가 있다고 해서 "Foo"라는 포맷과 같은 것은 아니에요. 다만 어떤 파일핸들에 연관된 포맷의 기본 이름은 그 파일핸들의 이름과 같아요. 그래서 STDOUT의 기본 포맷 이름은 "STDOUT"이고, 파일핸들 TEMP의 기본 포맷 이름은 "TEMP"예요. 그저 같아 보일 뿐이지, 같은 게 아니에요.
출력 레코드 포맷은 다음과 같이 선언해요:
format NAME =
FORMLIST
.
이름을 생략하면 포맷 "STDOUT"이 정의돼요. 포맷을 끝내려면 1열에 단독 "."을 씁니다. FORMLIST는 일련의 줄들로 구성되고, 각 줄은 다음 세 가지 유형 중 하나예요:
-
첫 열에 '#'을 넣어 나타내는 주석.
-
한 줄 출력의 형식을 주는 "picture" 줄.
-
앞선 picture 줄에 끼워 넣을 값을 공급하는 인자 줄.
Picture 줄은 출력 필드 정의와 리터럴 텍스트가 뒤섞여 있어요. 이 줄들은 어떤 변수 보간도 거치지 않아요. 필드 정의는 필드를 시작하고 원하는 폭으로 늘리는 데 쓰는 문자 집합으로 구성돼요. 필드 정의에 쓰는 문자 집합 전체는 이래요:
@ start of regular field
^ start of special field
< pad character for left justification
| pad character for centering
> pad character for right justification
# pad character for a right-justified numeric field
0 instead of first #: pad number with leading zeroes
. decimal point within a numeric field
... terminate a text field, show "..." as truncation evidence
@* variable width field for a multi-line value
^* variable width field for next line of a multi-line value
~ suppress line with all fields empty
~~ repeat line until all fields are exhausted
Picture 줄의 각 필드는 "@" 또는 "^"로 시작해요. 각각 "regular" 필드와 "special" 필드라고 부를게요. 패드 문자의 선택이 필드를 텍스트형으로 할지 숫자형으로 할지 결정해요. 틸드 연산자는 필드의 일부가 아니에요. 각 경우를 자세히 살펴볼게요.
텍스트 필드 (Text Fields)
필드의 길이는 필드를 여러 "<", ">", "|" 문자로 채워서 정해요. 각각 왼쪽 정렬, 오른쪽 정렬, 중앙 정렬을 지정하는 비숫자 필드예요. regular 필드의 경우 값(첫 줄바꿈까지)을 가져와 선택한 정렬에 따라 출력하고 초과 문자를 잘라요. 텍스트 필드를 "..."로 끝내면 값이 잘렸을 때 점 세 개가 표시돼요. special 텍스트 필드는 기초적인 다중 줄 텍스트 블록 채우기에 쓸 수 있어요. 자세한 내용은 "Using Fill Mode"를 보세요.
Example:
format STDOUT =
@<<<<<< @|||||| @>>>>>>
"left", "middle", "right"
.
Output:
left middle right
숫자 필드 (Numeric Fields)
패딩 문자로 "#"을 쓰면 오른쪽 정렬된 숫자 필드를 지정해요. 선택 사항인 "."이 소수점 위치를 정해요. 첫 "#" 대신 "0"(영)을 쓰면 필요 시 포맷된 숫자가 앞에 0으로 채워져요. special 숫자 필드는 값이 정의되지 않으면 비워져요. 결과 값이 지정된 폭을 넘으면 필드가 오버플로 증거로 "#"으로 채워져요.
Example:
format STDOUT =
@### @.### @##.### @### @### ^####
42, 3.1415, undef, 0, 10000, undef
.
Output:
42 3.142 0.000 0 ####
가변 폭 다중 줄 텍스트 필드 @* (The Field @* for Variable-Width Multi-Line Text)
필드 "@*"는 잘리지 않는 다중 줄 값을 출력하는 데 쓸 수 있어요. 줄에서 단독으로 나타나는 게 좋지만(필수는 아니에요) 필수는 아니에요. 마지막 줄바꿈은 잘라내지만 그 외의 모든 문자는 그대로 출력돼요.
줄마다 한 번씩 가변 폭 텍스트 필드 ^* (The Field ^* for Variable-Width One-line-at-a-time Text)
"@*"와 마찬가지로 이것도 가변 폭 필드예요. 공급되는 값은 스칼라 변수여야 해요. Perl은 텍스트의 첫 줄(첫 "\n"까지)을 필드에 넣고, 그다음 문자열 앞부분을 잘라내서 다음에 변수를 참조할 때 텍스트가 더 출력되도록 해요. 변수는 복원되지 않아요.
Example:
$text = "line 1\nline 2\nline 3";
format STDOUT =
Text: ^*
$text
~~ ^*
$text
.
Output:
Text: line 1
line 2
line 3
값 지정하기 (Specifying Values)
값은 picture 필드와 같은 순서로 다음 포맷 줄에 지정해요. 값을 제공하는 표현식은 쉼표로 구분해야 해요. 모두 줄이 처리되기 전에 리스트 컨텍스트에서 평가되므로, 단일 리스트 표현식이 여러 리스트 요소를 만들어낼 수도 있어요. 표현식을 중괄호로 감싸면 여러 줄로 늘어놓을 수도 있어요. 그럴 경우 여는 중괄호가 첫 줄의 첫 토큰이어야 해요. 표현식이 소수 부분이 있는 숫자로 평가되고, 해당 picture가 소수 부분이 출력에 나타나야 한다고 지정하면(즉 내장된 "." 없이 여러 "#" 문자가 아닌 모든 picture), 소수점에 쓰는 문자는 use locale이 적용되면 현재 LC_NUMERIC 로케일에 따라 결정돼요. 예를 들어 런타임 환경이 독일 로케일이라면 기본 "." 대신 ","가 쓰인다는 뜻이에요. 자세한 내용은 perllocale과 "WARNINGS"를 보세요.
채우기 모드 사용하기 (Using Fill Mode)
텍스트 필드에서 캐럿은 일종의 채우기 모드를 활성화해요. 임의 표현식 대신 공급되는 값은 텍스트 문자열을 담은 스칼라 변수여야 해요. Perl은 텍스트의 다음 부분을 필드에 넣고, 그다음 문자열 앞부분을 잘라내서 다음에 변수를 참조할 때 텍스트가 더 출력되도록 해요.(네, 이는 write() 호출 실행 중 변수 자체가 변경되고 복원되지 않는다는 뜻이에요.) 텍스트의 다음 부분은 조잡한 줄 바꿈 알고리즘으로 결정돼요. 캐리지 리턴 문자(\r)를 써서 줄 바꿈을 강제할 수 있어요. 어떤 문자에서 줄 바꿈이 허용되는지는 변수 $:(English 모듈을 쓰면 $FORMAT_LINE_BREAK_CHARACTERS)를 원하는 문자 목록으로 바꿔서 바꿀 수 있어요.
보통은 같은 스칼라 변수에 연관된 필드를 세로로 쌓아 텍스트 블록을 출력해요. 마지막 필드를 "..." 텍스트로 끝내고 싶을 텐데, 텍스트가 너무 길어 전부 나타나지 못하면 이 "..."가 출력에 나타나요.
모든 필드가 비어 있는 줄 억제하기 (Suppressing Lines Where All Fields Are Void)
캐럿 필드를 쓰면 모든 필드가 빈 줄이 생길 수 있어요. 줄 어디에든 "~"(틸드) 문자를 넣으면 그런 줄을 억제할 수 있어요. 틸드는 출력 시 공백으로 변환돼요.
포맷 줄 반복하기 (Repeating Format Lines)
줄 어디에나 연속한 틸드 문자 두 개 "~~"를 넣으면, 모든 필드가 비어 있을 때 줄을 억제하는 것에 더해, 줄의 모든 필드가 소진(즉 undef)될 때까지 줄을 반복해요. special(캐럿) 텍스트 필드의 경우 이건 조만간 일어나지만, at 종류의 텍스트 필드를 쓰면 공급하는 표현식이 매번 영원히 같은 값을 주지 않는 게 좋아요!(shift(@f)가 동작할 간단한 예시예요.) 그런 줄에 regular(at) 숫자 필드를 쓰지 마세요. 절대 비어지지 않으니까요.
폼 최상단 처리 (Top of Form Processing)
폼 최상단 처리는 기본적으로 현재 파일핸들 이름에 "_TOP"을 붙인 포맷이 담당해요. 각 페이지의 맨 위에서 트리거돼요. "write" in perlfunc를 보세요.
예시:
# a report on the /etc/passwd file
format STDOUT_TOP =
Passwd File
Name Login Office Uid Gid Home
------------------------------------------------------------------
.
format STDOUT =
@<<<<<<<<<<<<<<<<<< @||||||| @<<<<<<@>>>> @>>>> @<<<<<<<<<<<<<<<<<
$name, $login, $office,$uid,$gid, $home
.
# a report from a bug report form
format STDOUT_TOP =
Bug Reports
@<<<<<<<<<<<<<<<<<<<<<<< @||| @>>>>>>>>>>>>>>>>>>>>>>>
$system, $%, $date
------------------------------------------------------------------
.
format STDOUT =
Subject: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$subject
Index: @<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$index, $description
Priority: @<<<<<<<<<< Date: @<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$priority, $date, $description
From: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$from, $description
Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$programmer, $description
~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
~ ^<<<<<<<<<<<<<<<<<<<<<<<<<...
$description
.
같은 출력 채널에 print()와 write()를 섞어 쓰는 것도 가능하지만, $-($FORMAT_LINES_LEFT)는 직접 처리해야 해요.
포맷 변수 (Format Variables)
현재 포맷 이름은 변수 $~($FORMAT_NAME)에, 현재 폼 최상단 포맷 이름은 $^($FORMAT_TOP_NAME)에 저장돼요. 현재 출력 페이지 번호는 $%($FORMAT_PAGE_NUMBER)에, 페이지의 줄 수는 $=($FORMAT_LINES_PER_PAGE)에 있어요. 이 핸들에 출력을 자동 flush할지 여부는 $|($OUTPUT_AUTOFLUSH)에 저장돼요. 각 페이지 최상단 이전에(첫 페이지 제외) 출력되는 문자열은 $^L($FORMAT_FORMFEED)에 저장돼요. 이 변수들은 파일핸들별로 설정되므로 영향을 주려면 select()로 다른 핸들로 전환해야 해요:
select((select(OUTF),
$~ = "My_Other_Format",
$^ = "My_Top_Format"
)[0]);
꽤 못생겼죠? 하지만 흔한 관용구니까 봤을 때 너무 놀라지 마세요. 적어도 임시 변수로 이전 파일핸들을 담아두면 돼요. (이쪽이 전반적으로 훨씬 나은 방법이에요. 가독성이 좋아질 뿐 아니라 디버거를 한 단계씩 진행시킬 수 있는 중간 단계가 표현식에 생기니까요):
$ofh = select(OUTF);
$~ = "My_Other_Format";
$^ = "My_Top_Format";
select($ofh);
English 모듈을 쓰면 변수 이름까지 읽을 수 있어요:
use English;
$ofh = select(OUTF);
$FORMAT_NAME = "My_Other_Format";
$FORMAT_TOP_NAME = "My_Top_Format";
select($ofh);
그래도 여전히 그 재미있는 select()들이 있어요. 그냥 FileHandle 모듈을 쓰세요. 이제 소문자 메서드 이름으로 이런 특수 변수에 접근할 수 있어요:
use FileHandle;
format_name OUTF "My_Other_Format";
format_top_name OUTF "My_Top_Format";
훨씬 낫죠!
NOTES
값 줄이 임의 표현식(at 필드용, 캐럿 필드가 아님)을 담을 수 있으므로, 더 정교한 처리를 sprintf()나 직접 만든 함수 같은 다른 함수에 넘길 수 있어요. 예를 들어:
format Ident =
@<<<<<<<<<<<<<<<
&commify($n)
.
필드에 진짜 at이나 캐럿을 넣으려면 이렇게 해요:
format Ident =
I have an @ here.
"@"
.
텍스트 줄 전체를 중앙 정렬하려면 이렇게 하세요:
format Ident =
@|||||||||||||||||||||||||||||||||||||||||||||||
"Some text line"
.
"어떤 너비든 페이지 오른쪽으로 띄워라"라고 말할 내장 방법은 없어요. 어디로 갈지 직접 지정해야 해요. 정말 절박하면 현재 컬럼 수를 기준으로 포맷을 즉석에서 생성한 다음 eval()할 수 있어요:
$format = "format STDOUT = \n"
. '^' . '<' x $cols . "\n"
. '$entry' . "\n"
. "\t^" . "<" x ($cols-8) . "~~\n"
. '$entry' . "\n"
. ".\n";
print $format if $Debugging;
eval $format;
die $@ if $@;
이러면 대략 이런 모양의 포맷이 생성돼요:
format STDOUT =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$entry
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
$entry
.
여기 fmt(1)와 비슷한 작은 프로그램이 있어요:
format =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ~~
$_
.
$/ = '';
while (<>) {
s/\s*\n\s*/ /g;
write;
}
푸터 (Footers)
$FORMAT_TOP_NAME이 현재 헤더 포맷 이름을 담고 있지만, 푸터에 대해 똑같이 자동으로 처리하는 메커니즘은 없어요. 포맷을 평가하기 전까지는 포맷이 얼마나 큰지 알 수 없다는 게 주요 문제 중 하나예요. TODO 목록에 있어요.
전략 하나를 소개할게요: 크기가 고정된 푸터가 있다면, 각 write() 전에 $FORMAT_LINES_LEFT를 확인하고 필요 시 푸터를 직접 출력하면 돼요.
또 다른 전략: open(MYSELF, "|-")("open" in perlfunc 참고)로 자신에게 파이프를 열고 STDOUT 대신 항상 MYSELF에 write()하는 거예요. 자식 프로세스가 자기 STDIN을 가공해서 헤더와 푸터를 원하는 대로 재배열하게 하면 돼요. 그리 편하진 않지만 가능하죠.
포맷팅 내부에 접근하기 (Accessing Formatting Internals)
포맷팅 메커니즘에 저수준으로 접근하려면 formline()을 쓰고 $^A($ACCUMULATOR 변수)에 직접 접근할 수 있어요.
예를 들어:
$str = formline <<'END', 1,2,3;
@<<< @||| @>>>
END
print "Wow, I just stored '$^A' in the accumulator!\n";
또는 write()에 대한 sprintf() 같은 swrite() 서브루틴을 만들려면 이렇게 해요:
use Carp;
sub swrite {
croak "usage: swrite PICTURE ARGS" unless @_;
my $format = shift;
$^A = "";
formline($format,@_);
return $^A;
}
$string = swrite(<<'END', 1, 2, 3);
Check me out
@<<< @||| @>>>
END
print $string;
WARNINGS
포맷을 끝내는 단독 점은, 설정이 잘못된 인터넷 메일러를 지나가는 메일 메시지를 조기에 끝낼 수도 있어요. (그리고 경험상 그런 잘못된 설정이 예외가 아니라 규칙이에요.) 그래서 포맷 코드를 메일로 보낼 때는 포맷-종료 점이 왼쪽 여백에 오지 않도록 들여 써야 SMTP 잘림을 막을 수 있어요.
"my"로 선언한 어휘 변수는 포맷이 그 어휘 변수의 스코프 안에서 선언되지 않는 한 포맷 안에서 보이지 않아요.
프로그램의 환경이 LC_NUMERIC 로케일을 지정하고 포맷이 선언될 때 use locale이 적용 중이면, 포맷된 출력의 소수점 문자를 지정하는 데 그 로케일이 쓰여요. 포맷된 출력은 write() 호출 시점의 use locale로는 제어할 수 없어요. 로케일 처리에 대한 더 자세한 논의는 perllocale을 보세요.
고정 길이 텍스트 필드로 표시될 문자열 안에서는 각 제어 문자가 공백으로 치환돼요.(단, 채우기 모드에서 \r의 특별한 의미를 기억하세요.) 일부 출력 매체에서 제어 문자가 "사라질" 때 정렬이 어긋나지 않도록 하기 위해서예요.