티스토리 뷰

2002-9-2

파일 핸들(File Handle)

파일 관련 내용에 들어가기 전에, 펄의 사용자 입력처리에 관한 내용을 먼저 알고 있어야 합니다. 그 글에서 설명한 것처럼  < > 사이에 파일핸들을 넣으면 파일을 한 줄 읽어 들입니다. 그 파일핸들이 뭔 지를 우선 알아 봅시다.

 

라마 북(Llama book)에 보면, 파일핸들은 "바깥 세계와 펄 프로세스 사이의 I/O connection" 이라고 설명되어 있습니다. 바깥 세계라는 것은 하드디스크 등에 물리적으로 저장된 파일을 얘기합니다. 그 파일과 펄 프로그램 내의 프로세스 사이에 데이타를 넣고 뺄 수 있는(I/O) 하나의 연결을 만들 수 있는데 이것을 파일 핸들이라고 합니다. 예를 하나 봅시다.

 

open MYFILE, "logfile.txt";

 

위의 코드는 open() 이라는 함수를 이용해서 logfile.txt라는 파일을 MYFILE이라는 파일핸들로 여는 코드입니다. 이렇게 MYFILE이라는 파일핸들을 이용해서 연 다음 logfile.txt 파일의 내용물을 읽어온다거나(Input), logfile.txt에 다른 데이타를 기록(Output) 할 수 있습니다. 파일핸들은 파일을 펄 프로그램내에서 다루기 위해 사용하는 핸들인 것입니다.

 

파일핸들을 만드는 방법은, 다른 펄 변수와 달리 앞에 붙는 문자($, @, %)가 없다는 점만 지키면 됩니다. 관용적으로는 파일핸들은 대문자만 사용하는 것이 보통입니다. 소문자로 쓸 경우 펄의 예약어(reserved word)와 충돌할 가능성이 있기도 하고 또 대문자로 쓰면 찾기가 쉽기 때문입니다.

 

위의 코드에서 본 것처럼 펄에서 파일핸들을 이용해서 파일을 열때 사용하는 함수가open() 함수입니다. 

 

open FILEHANDLE, "filename"

 

이때 파일을 여는 모드(mode)에 크게 3 가지 정도가 있습니다.

 

open FH1, "myfile";
open FH2, ">newfile";
open FH3, ">>logfile";

 

첫번째 것은 myfile 을 읽기 모드로 연것입니다.
myfile 이라는 파일의 내용물을 읽어 들이기 위해서 myfile 을 FH1으로 연 것입니다.  파일을 읽는다는 것은 밑에서 더 자세히 알아 봅시다.

 

그 다음 모드로는, 파일 이름 앞에 >을 한 개 붙인 것으로 쓰기 모드 중 덮어쓰기(overwrite) 모드입니다. 파일에 어떤 내용을 기록하기 위해 열 때 사용하는 것인데 덮어쓰기 모드로 여는 경우 기존의 내용을 다 없애고 새로운 내용을 쓰게 됩니다. 위 코드의 경우, newfile이란 파일이 없다면 새로 만들어서, 있다면 기존의 내용을 지우고 덮어쓰기를 하게 되는 것입니다.

 

세 번째로, 파일이름 앞에 >>을 붙이는 것으로, 쓰기모드 중 붙여쓰기(append) 모드입니다. 파일을 열어서 기존의 내용은 건드리지 않고 끝에 새로운 내용을 덧붙일 때 사용합니다. 위의 코드의 경우, logfile이라는 파일이 없다면 새로 만들어서, 있다면 열어서, 기존 내용은 그대로 두고 끝에 새로운 내용을 덧붙일 수 있게 준비하는 것입니다.

 

open()은 펄 프로그램이 직접 파일을 여는 것이 아니라 운영체제에 파일을 열어라고 요청만 하는 함수입니다. 따라서 운영체제에서 파일을 열 수 없다고 판단되는 경우(퍼미션이 안맞다든지, 요청한 파일이 존재하지 않는다든지) open()거짓(0)을 리턴하게 됩니다. 이 때 파일을 여는 데 실패한 것이 드러나는 경우도 있지만 드러나지 않는 경우도 있구요. 그 경우 프로그램은 제대로 동작하지 않는데 특별한 에러 메씨지는 없는 일이 생길 수 있습니다. 그래서 open()이 성공했는지 아닌지를 확인할 필요가 있습니다. 그 목적으로 사용하는 것이 die()입니다.

open (HANDLE, "myfile") or die ("Can't open myfile: $!");
open (HANDLE , "myfile") || die "Can't open myfile: $!";

 

두 코드는 거의 같은 것입니다. myfile이라는 파일을 HANDLE이라는 파일핸들을 이용해서 읽기 모드로 열어라, 열지 못하면 Can't open myfile이라는 메시지를 띄우고 프로그램 실행을 중단한다는 의미입니다. $! 은 에러의 구체적 내용을 담고 있는 변수입니다. 더 자세한 에러의 원인을 알고 싶으면 써도 되고 아니면 안 써도 됩니다.

 

파일핸들을 연 다음에 작업을 하고 닫을 때에는 close(FILEHANDLE)을 사용하면 됩니다. close()는 꼭 있어야 하는 것은 아닙니다. 파일핸들을 더 이상 열어 둘 필요가 없으면 펄이 알아서 닫습니다. 하지만 어떤 파일을 동시에 여러곳에서 수정을 하는 것을 처리하는 파일 잠그기(file locking)를 할 때는 close()가 특별한 의미를 갖습니다. 이제 파일을 열어서 어떤 일을 할 수 있는지 자세히 알아 봅시다.

 

파일 읽기

사용자 입력 처리에서 설명한 것처럼 표준입력으로부터 한 줄을 읽어들여서 \n을 떼어 내고 어떤 변수에 저장하는 것은,

 

chomp($line = <STDIN>);

 

처럼 하면 된다고 했습니다.
그러면 표준입력으로부터 여러 줄을 읽으려면 어떻게 할까요?

 

while(defined($line=<STDIN>)) {
print $line;
}

 

defined는 변수에 어떤 값이 할당되어 있으면 1을 리턴, 그렇지 않으면 0을 리턴하는 함수입니다. 위 코드는 표준입력으로부터 한 줄을 읽어 들여서 $line에 할당을 할 수 있다면(즉, 읽어 들일 줄이 있다면) while 루프 내의 구문을 실행한다는 의미입니다. 만약 표준입력으로부터 읽어 들일것이 더 이상 없다면 $lineundefined가 되고, while ()은 거짓이 되어서 while 루프를 종료합니다.

 

그런데 STDIN 자리에 파일핸들도 넣을 수가 있다고 했습니다. 파일을 열어서 그 파일에 이 있는 한은 계속해서 읽어들일 수가 있는 것입니다. 따라서 myfile.txt라는 파일을 열어서 파일 내용을 그대로 다 출력하는 코드는,

 

open MYHANDLE, "myfile.txt";
while(defined($line=<MYHANDLE>) {
print $line;
}

 

그런데 이렇게 파일을 열어서 읽는 기능은 매우 자주 사용하기 때문에 펄에는 관용적으로 쓰이는 축약형 코드가 있습니다. 이렇게 합니다.

 

open MYHANDLE, "myfile.txt";
while(<MYHANDLE>) {
print $_;
}

 

while() 안에 바로 <FILEHANDLE>을 집어 넣은 것입니다. myfile.txt에 읽어 들일 내용이 있는 한 <MYHANDLE>은 참일것이고, while 루프 내의 구문이 계속 실행하게 됩니다. 위의 경우는 파일핸들로부터 읽어 들인 줄을 명시적으로 어떤 변수에 할당하지 않았으므로 펄의 default variable 인 $_에 담기게 되는 것입니다.

 

위 코드를 이용해서 파일을 읽기모드로 열고, 그 파일의 각 줄을 $_을 통해서 다룰 수가 있는 것입니다. 아주 자주 쓰이는 것이므로 외워두면 좋습니다. 그런데 펄은 여기서 한 번 더 나갑니다.

open MYHANDLE, "myfile.txt";
print while (<MYHANDLE>);

 

위 코드 역시 myfile.txt라는 파일을 열어서 그 내용을 출력하는 코드 입니다. 훨씬 간단할 뿐만 아니라 일반적인 영어 문장처럼 자연스럽게 읽힙니다. "MYHANDLE을 읽어올 수 있는 한은 (while) 프린트 해라."

 

위와 같은 스타일이 펄 스타일입니다.

비슷한 것으로 < >를 리스트 컨텍스트에서 사용한 것도 있습니다.

 

foreach (<FILEHANDLE>) {
print $_;
}

 

또는,

 

for $line (<FILEHANDLE>) {
print $line;
}

 

for()foreach()는 괄호 안을 리스트처럼 다루는 리스트 컨텍스트(list context)입니다. 첫 번째 코드는 FILEHANDLE을 리스트 컨텍스트에서 읽어 들여서 각 줄을 출력합니다. 결과적으로는 while 루프와 같을 지 몰라도 내부적으로는 다른 것입니다. 리스트 컨텍스트에서 읽어 들일 때는 FILEHANDLE이 연결한 파일을 우선 처음부터 끝까지 다 읽어 들인 다음 한 줄씩 루프를 돌립니다만 while의 경우는 한 줄만 읽어서 while 루프 내의 구문을 실행하고, 그 다음 줄을 읽어서 루프 내 구문을 실행하는 식으로 진행됩니다. 라마 북에 나온 얘기처럼, 만약 수백 MB 이상의 로그파일을 연다면, 이것을 처음부터 끝까지 다 읽어 들인 다음 루프를 돌리는 for나 foreach보다는 한 줄씩 읽어 들이는 while이 훨씬 효율적입니다.

 

for()foreach() 코드 역시 축약할 수 있습니다. 

 

open (FI, "myfile") || die ("can't open myfile");
print for (<FI>);

 

파일 쓰기

파일쓰기 역시 읽기와 똑같습니다. 열 때 쓰기모드로 연다는 것만 다릅니다. 파일쓰기는 print() 함수를 사용합니다.
예를 들어 myfile이라는 파일을 새로 만들어서 뭔가를 쓰려면,

 

open (FH, ">myfile") || die ("Can't open myfile");
print FH "It's a beautiful day!";

 

print 다음에 파일핸들을 쓰고 써넣을 내용을 쓰면 됩니다. print 다음에 파일핸들을 생략하면 특정 파일에 쓰기를 하는 것이 아니라 default인 표준출력에 결과를 띄웁니다.

 

파일을 붙여쓰기 모드로 열어서 새로운 내용을 파일 끝에 첨가할 수도 있을 것입니다.

 

open (FH2, ">>myfile") || die "Can't open myfile";
print FH2 "2002/9/1: 오늘 추가한 내용입니다.";

 

예를 들어 웹 싸이트 방문 로그파일을 만드는 코드 중 일부는 이런 식입니다.

 

open (LOG, ">>logfile.txt") || die ("Can't open logfile.txt");
print LOG "$ENV{'REMOTE_ADDR'}|$ENV{'HTTP_USER_AGENT'}\n";

 

$ENV{'REMOTE_ADDR'}등의 환경변수는 펄 CGI 글에서 설명합니다. 위의 코드는 로그파일을 붙여쓰기 모드로 열어서 각 줄에 "접속자 ip주소|사용 웹 브라우져"를 기록해줍니다.

읽기 쓰기 모드

위에서 파일을 읽기모드, 쓰기모드로 여는 방법을 알아 보았습니다. 그런데 어떤 파일을 열어서 내용 중 일부를 고치고 싶은 경우, 읽기모드로 열어서는 내용을 고칠 수가 없을 것이고 쓰기모드로 열어서는 기존의 내용이 없어져 버리거나 끝에 새로운 내용을 덧붙이는 정도밖에 할 수가 없습니다. 이럴때 쓰는 모드가 읽기쓰기 모드입니다.

 

open FILEHANDLE, "+<myfile";

 

이것은 파일을 열어서 그 내용을 읽어올 수 있을 뿐만 아니라, 파일 내용 수정도 할 수 있습니다. 한 가지 조심할 점은 myfile이라는 파일이 없는 경우 새로운 파일을 만들지는 못합니다. 

파일 테스트

파일테스트는 파일이 읽을 수 있는 파일인지 쓰기가 가능한 파일인지, 크기가 100MB 이상인지, 만들어진 지 10 일이 안되었는지 등 파일 관련 메타 정보를 다양하게 테스트하는 것입니다. 방법은 간단합니다. if 다음에 파일 테스트 문을 붙여주면 됩니다. 파일테스트는 이런 것들이 있습니다.

 

 

이외에도 몇 가지 더 있고 이런 식으로 사용합니다.
위에서 +<< 모드는 파일이 없을 때 새로 만들지는 못한다고 했는데 파일테스트를 이용하면 해결할 수 있습니다.

 

if (-e "myfile") {
open (MY, "+<myfile") || die ("Can't open");
}
else {
open (MY, ">myfile") || die ("Can't create");
}

 

if (-e "myfile")는 myfile이라는 파일이 있느냐를 테스트 하는 것입니다. myfile이 존재하면 if() 괄호 안은 참이 되는 것입니다. 다른 예로 어떤 디렉토리 내의 html 파일 중 수정한 지 5 일 이상인 파일만 따로 모으고 싶다면,

 

@files = glob("*.html");
foreach $file (@files) {
if (-M $file > 5) {
push @old_files, $file
}
}
print "@old_files";

 

glob("*.html")에 대한 자세한 설명은 펄에서의 globbing과 날짜 처리를 참고하세요. 여기서는 디렉토리 내에 있는 파일 중 확장자가 html인 파일 이름을 리스트 형태로 리턴하는 것이라는 정도로 이해하면 됩니다.

파일 다루기

1. 파일 지우기

파일을 지우려면,

 

unlink "temp1";

rm가 아닙니다. unlink입니다. unlink 에 의해서 반환되는 값은 지워진 파일의 갯수입니다. 만약 tmp라는 디렉토리에 있는 파일 중 10 일 이상 억세스되지 않은 파일만 지우려면,

 

chdir "/home/linuxer/tmp" || die "can't cd";
@files = glob "*";
foreach $file (@files) {
unlink $file if -A $file > 10
}

 

2. 파일 이름 바꾸기

rename입니다.

rename "old" , "new";

 

라마북에 아주 좋은 예가 있습니다. 어떤 디렉토리 내의 파일 중 확장자가 .txt인것을 .html로 바꾸고 싶다면,

 

foreach my $old_name (glob "*.txt") {
my $new_name = $old_name;
$new_name =~ s/\.txt$/.html/;
if (-e $new_name) {
warn "can't rename $old_name to $new_name: $new_name exists !\n";
}
elsif (rename $old_name, $new_name) {}
else {
die "rename failed :$!\n";
}
}

 

elsif 부분은 주의해서 보세요. warn은 die와 비슷한 것입니다.

 

3. 퍼미션 바꾸기

chmod 0755 , "*.pl";

 

똑같습니다.

 

4. 파일 카피

간단한 방법으로는 시스템 함수 cp를 이용하면 됩니다. system() 함수는 유닉스 쉘 커맨드를 펄 내에서 사용할 수 있게 해주는 함수입니다.

 

system ("cp $oldfile, $newfile");

 

기본적인 디렉토리 작업

기본적인 디렉토리 작업에는 다른 디렉토리로 이동, 디렉토리 만들기, 디렉토리 없애기 등이 있습니다. 

1. 디렉토리 이동

디렉토리 이동은 유닉스 쉘 명령어 cd와 똑같은 기능으로 이렇게 합니다.

 

chdir "/bin" or die ("can't change directory");

 

주의할 점은 윈도우즈에서 사용할 때도 유닉스 방식으로 디렉토리 패스를 써야 한다는 것입니다.  예를 들어, C 드라이브에 있는 WINNT라는 디렉토리로 이동하려고 한다면,

 

chdir "C:/WINNT" or die ("can't change directory");

 

가 됩니다. C:\WINNT로 하면 에러가 납니다.

2. 디렉토리 만들기

역시 쉘 명령어와 똑같습니다.

 

mkdir "backup", 0755 || die "can't create directory";

 

위 코드는 퍼미션이 755이고, 이름이 backup인 디렉토리를 현재 펄 프로그램이 저장된 디렉토리 내에 만드는 코드입니다. 퍼미션에 대한 자세한 설명은 리눅스 기본 명령어 글을 참고하세요. 그리고 주의할 점 한 가지는 755 앞에 꼭 0을 붙여서 0755로 해야 한다는 것입니다. 왜 그럴까요? 스칼라 변수 설명할 때 얘기한 것처럼 펄은 숫자를 파악할 때 맨 첫 번째 숫자가 0으로 시작하는 숫자만 8진수로 인식하기 때문입니다. 만약 755라고만 하면 펄은 십진수 755로 인식합니다.

3. 디렉토리 없애기

내용물이 없는 디렉토리는 이렇게 하면 됩니다.

 

rmdir "temp" or die "can't delete temp directory";

 

하지만 내용물이 있는 경우엔 위 코드로 지워지지 않습니다. 그러면 내용물이 있는 디렉토리는 어떻게 지울까요? 이 글을 다 읽고 나면 코딩할 수 있을 것입니다. 디렉토리 내의 파일을 다 지운 다음 디렉토리를 지우는 식으로 합니다.

 

이상 살펴 본 것처럼 대체로 유닉스 명령어와 매우 유사합니다. 

반응형
댓글