[13/20] 파일
글을 시작하기에 앞서 해당 시리즈는 Allen Downey, Ben Lauwens의 저서인 Think Julia: How to Think Like a Computer Scientist 를 바탕으로 작성된 글임을 알려드립니다.
이 포스트는 Files를 한글로 요약 정리한 글입니다.
파일 (Files)
이번 장에서는 데이터를 영구 저장소에 보관하는 '지속되는(persistent)' 프로그램에 대해 소개하고, 파일 및 데이터베이스와 같은 다양한 종류의 영구 저장소를 사용하는 법을 알아볼 것이다.
지속성 (Persistence)
지금까지 본 대부분의 프로그램들은 일시적으로 실행되었다. 즉, 프로그램이 종료되는 순간 데이터는 사라지며 프로그램을 다시 실행하면 깨끗한 슬레이트에서 시작한다는 것을 의미한다.
하지만 영구적인 프로그램도 존재한다. 프로그램이 장시간 또는 항상 실행되며, 데이터의 일부를 하드 드라이브와 같은 영구 저장소에 보관하여 재부팅해도 프로그램이 멈춘 그 상태를 반환한다.
영구적인 프로그램의 예시로 운영체제(OS)와 웹서버를 볼 수 있다. 운영체제는 컴퓨터가 켜질 때마다 실행되며, 웹서버는 네트워크에서 들어오는 요청을 받기 위해 항상 켜져 있다.
프로그램이 데이터를 유지하는 가장 간단한 방법 중 하나는 텍스트 파일을 읽고 쓰는 것이다. 우리는 이미 앞에서 텍스트 파일을 읽는 프로그램을 보았다. 이 장에서는 텍스트 파일을 작성하는 프로그램을 보게 될 것이다.
다른 대안은 프로그램 상태를 데이터베이스에 저장하는 것이다. 이 장에서는 간단한 데이터베이스를 사용하는 방법도 살펴볼 것이다.
읽고 쓰기 (Reading and Writing)
텍스트 파일은 하드 드라이브나 플래시 메모리(flash memory)와 같은 영구 매체에 저장된 문자들의 시퀀스이다. 우리는 8. 워드 플레이에서 파일을 열고 읽는 방법을 배웠다.
파일을 쓰기 위해서는, 두 번째 매개 변수에 "w"을 입력하여 '쓰기 모드'로 파일을 열어야 한다.
1 |
|
파일이 이미 존재하는 경우 파일을 쓰기 모드로 열면 이전 데이터가 전부 지워지니 주의해야 한다. 파일이 없다면 새 파일이 만들어진다. open()
은 파일 객체를 반환하고 write()
는 데이터를 파일에 넣는다.
1 |
|
반환 값은 작성된 문자의 개수이다. 파일 객체는 현재 위치를 추적하므로 다시 write()
를 호출하면 새 데이터가 파일 끝에 추가된다.
1 |
|
쓰기가 끝났다면, 파일을 닫아야 한다.
1 |
|
만약 파일을 닫지 않으면, 프로그램이 종료될 때 자동으로 닫힌다.
서식 (Formatting)
write()
의 인수는 문자열이어야 하기 때문에 만약 파일에 값을 넣고 싶다면 문자열로 변환해야 한다. 가장 쉬운 방법은 string()
이나 보간법을 사용하는 것이다.
1 |
|
write()
대신에 print(ln)
을 사용할수도 있다.
1 |
|
Tip 가장 강력한 방식은 @printf
을 사용하는 것이다. 이에 대한 세부적인 사항은 이 링크에서 확인할 수 있다.
파일이름과 경로
파일은 폴더라고 불리는 디렉토리(directory)에 저장된다. 실행중인 모든 프로그램은 기본값으로 '현재 작동중인 디렉토리'을 가진다. 예를 들어, 만약 파일을 열어 읽어보고 싶다면, 줄리아는 현재 디렉토리에서 파일을 찾는다.
1 |
|
cwd
는 현재 작동중인 디렉토리를 보여준다. 위 예시는 결과로 사용자인 ben의 홈 디렉토리를 보여주었다.
파일이나 디렉토리를 식별하는 "/home/ben"
와 같은 문자열을 경로(path)라고 한다.
memo.txt
와 같은 간단한 파일 이름도 경로가 될 수 있지만, 해당 파일은 현재 디렉토리에 저장되어 있기 때문에 파일 이름만 사용하는 것은 상대 경로이다. 현재 디렉토리가 "/home/ben"
인 경우, 상대 경로인 memo.txt
는 "/home/ben/memo.txt"
를 참조한다.
/
로 시작하는 경로는 현재 디렉토리에 의존하지 않는다. 파일의 절대 경로를 찾고 싶다면 abspath()
를 사용하면 된다.
1 |
|
줄리아에서는 파일 이름과 경로와 관련된 여러 함수들을 제공한다. 예를 들어 ispath()
는 파일이나 디렉토리가 존재하는지 확인한다.
1 |
|
만약 존재한다면, isdir()
은 디렉토리인지를 확인한다.
1 |
|
비슷하게 isfile()
은 파일인지를 확인한다.
readdir()
은 인수로 제공한 디렉토리에 들어있는 파일과 다른 디렉토리의 이름들을 베열로 반환한다.
1 |
|
이런 함수들을 설명하기 위해 다음 예시를 확인해보자. 아래의 함수는 디렉토리 경로를 받으면 그 안에 있는 파일들과 디렉토리의 이름을 반환한다. 재밌는 점은 디렉토리 내부에 있는 다른 디렉토리도 통과하여 그 안의 파일 이름을 전부 가져오는 재귀로 구성되있다는 것이다.
1 |
|
joinpath()
는 디렉토리와 파일 이름을 가져와 완전한 경로로 결합한다.
Tip 줄리아는 walkdir()
이라는 함수를 제공한다. 이 함수는 우리가 작성한 함수와 비슷하지만 더 다양한 기능을 제공한다. 연습문제로 위 링크의 설명서를 읽고 함수를 사용하여 디렉토리 내부의 파일 이름을 출력해보라.
예외 포착 (Catching Exceptions)
파일을 읽고 쓸 때, 많은 오류들이 발생할 수 있다. 만약 존재하지 않는 파일을 열려고 한다면 SystemError
가 일어난다.
1 |
|
또한 파일에 접근할 권한이 없는 경우에도 위와 같은 오류가 발생한다.
1 |
|
이런 오류들을 피하기 위해서는 ispath()
와 isfile()
같은 함수를 이용해야 하지만, 모든 가능성을 확인하려면 많은 시간이 걸린다.
try
문을 사용하면 더 쉽게 문제를 해결할 수 있다. try
문은 문법적으로 if
문과 유사하다.
1 |
|
줄리아는 try
절을 실행하며 시작한다. 모두 잘 진행되면 catch
절을 건너 뛰고 진행하지만, 예외가 발생하면 catch
절을 실행한다. try
문으로 예외를 처리하는 것을 '예외 포착(Catching Exceptions)'이라고 한다. 이 예시에서 예외 절은 별로 도움이 되지 않는 오류 메시지를 출력한다. 일반적으로 예외를 발견하면 우리는 문제점을 수정하거나 다시 시도하거나 적어도 프로그램이 어떤 상황인지 알고 종료할 수 있는 기회를 얻는다.
일반적으로 상태 변경을 수행하거나 파일과 같은 리소스를 사용하는 코드에는 코드가 완료될 때 수행해야하는 정리 작업이 있다.(예시: 파일 닫기) 예외로 인해서 정상적인 종료에 도달하기도 전에 코드가 종료될 수 있기 때문에 작업이 복잡해진다. finally
키워드는 이전의 코드들이 종료되는 이유와 관계없이 특정 코드를 작동한다.
1 |
|
위 예시에서 close()
는 무조건 실행된다.
데이터베이스 (Databases)
데이터베이스(database)는 데이터를 저장하기 위해 조직된 파일이다. 많은 데이터베이스는 키에서 값으로 매핑된다는 점에서 딕셔너리처럼 구성되어 있다. 하지만 데이터베이스는 디스크와 같은 다른 영구저장소에 있으므로 프로그램이 종료된 후에도 유지된다는 점이 딕셔너리와는 다르다.
ThinkJulia
는 데이터베이스 작성 및 업데이트를 위해 GDBM
(GNU dbm)에 대한 인터페이스를 제공한다. 예를 들어 이미지 파일의 설명(captions)이 포함된 데이터베이스를 만들 수 있다.
데이터베이스를 여는 것은 다른 파일을 여는 것과 비슷하다.
1 |
|
"c모드"는 데이터베이스가 존재하지 않는 경우 데이터베이스를 작성해야 함을 의미한다. 결과는 딕셔너리처럼 사용할 수 있는 데이터베이스 객체이다.
새 아이템을 만들 때는 GDBM
이 데이터베이스 파일을 업데이트해야 한다.
1 |
|
만약 아이템 중 하나에 접근하고 싶다면, GDBM
은 파일을 읽어온다.
1 |
|
원래 존재하던 키에 다른 값을 재할당한다면, GDBM
은 새로운 값으로 변경한다.
1 |
|
keys()
와 values()
와 같이 딕셔너리에 적용되는 몇몇의 함수는 데이터베이스에서 작동하지 않는다. 하지만 for
루프와 같은 반복에서는 작동한다.
1 |
|
파일과 마찬가지로 데이터베이스도 작성이 끝나면 닫아야 한다.
1 |
|
직렬화 (Serialization)
GDBM
의 한계는 키와 값이 문자열 또는 바이트 배열이어야 한다는 것이다. 다른 유형을 사용하려고 하면 오류가 발생한다.
위의 상황에서는 직렬화해주는 serialize()
와 역직렬화해주는 deserialize()
가 도움이 될 수 있다. 이 함수들은 거의 모든 유형의 객체를 데이터베이스에 저장하기에 적합한 바이트 배열(an iobuffer)로 변환한 다음 바이트 배열을 다시 객체로 변환한다.
1 |
|
위의 형식은 사람을 위한 것이 아닌 줄리아가 해석하기 쉽게 변환한 것이다. deserialize()
는 객체를 재구성한다.
1 |
|
serialize()
와 deserialize()
는 메모리에 입출력하는 흐름을 나타하는 'iobuffer' 객체로부터 쓰고 읽는다. take!()
는 'iobuffer'의 내용을 바이트 배열로 가져오고 'iobuffer'을 초기상태로 재설정한다.
새 객체가 이전 객체와 동일한 값을 갖지만, 동일한 객체는 아니다.
1 |
|
즉, 직렬화와 역직렬화는 객체를 복사하는 것과 같다. 이 방식을 사용하여 문자열이 아닌 요소들도 데이터베이스에 저장할 수 있다.
Tip 실제로 문자열이 아닌 요소들이 저장된 데이터베이스는 JLD2
라는 패키지에 캡슐화하여 사용하는게 일반적이다.
명령 객체 (Command Objects)
대부분의 운영체제는 셀(shell)이라고 하는 명령 줄 인터페이스를 제공한다. 셀은 일반적으로 파일 시스템을 탐색하고 응용프로그램을 시작하는 명령을 제공한다. 예를 들어, Unix에서는 cd
를 사용하여 디렉토리를 변경하고 ls
를 사용하여 디렉토리의 내용을 표시하며 firefox
를 입력하여 웹브라우저를 시작할 수 있다.
셀에서 실행할 수 있는 모든 프로그램은 명령 객체를 사용하여 줄리아에서도 시작할 수 있다.
1 |
|
백틱(Backticks,`)은 명령을 구분하는데 사용된다.
run()
은 명령을 실행한다.
1 |
|
hello
는 echo 명령문의 출력이며, STDOUT
으로 전송된다. run()
은 명령을 실행하며 문제가 생기면 ErrorException
을 보여준다.
만약
1 |
|
예를 들어, 대부분의 Unix시스템은 파일의 내용을 읽고 "체크썸(checksum)"계산하는 md5sum
또는 md5
를 제공한다. md5
에 대한 정보는 해당 링크에 있다. 이 명령은 두 파일의 내용이 같은지 여부를 확인하는 효율적인 방법을 제공한다. 서로 다른 내용이 동일한 체크썸을 생성할 확률은 매우 적다.
아래의 코드는 줄리아에서 md5
를 실행하기 위한 명령을 사용하고, 결과를 얻는다.
1 |
|
모듈 (Modules)
이름이 "wc.ji"
인 파일이 있다고 가정하자.
1 |
|
위의 프로그램을 실행한다면, 파일 자체를 읽고 파일의 라인 수를 출력한다. 출력을 실행해보면 "wc.ji"
는 9개의 라인으로 이루어져 있다. 또한 아래와 같이 REPL에 포함시길 수 있다.
1 |
|
줄리아는 분리된 가변적인 작업공간을 생성하는 모듈을 소개한다. (마치 새로운 글로벌 설정 공간과도 같다)
모듈은 키워드 module
로 시작해서 end
로 끝난다. 모듈로 작성하면 도메인에 설정한 함수나 변수와 충돌하지 않는다. import
는 다른 모듈을 가져와서 제어할 수 있도록 허락하며, export
는 특정한 모듈 안에 있는 함수들을 공개한다. 즉, 모듈을 설정하지 않고도 외부에서 쓸 수 있는 것이다.
1 |
|
LineCount
모듈 객체은 linecount
를 제공한다.
1 |
|
WARNING 이미 가져온 모듈을 다시 import
하는 경우 줄리아는 아무 작업을 수행하지 않는다. 파일이 변경된 후에도 파일을 다시 읽지 않는다. 모듈을 다시 로드하려면 REPL을 다시 시작해야 한다. 세션을 더 오래 유지할 수 있는 Revise
패키지가 있다.
디버깅
파일을 읽고 쓸 때, 공백과 관련된 문제가 생길 수 있다. 공백, 탭, 줄바꿈과 같은 문제들은 일반적으로 코드에서 보이지 않기 때문에 디버깅하기 어렵다.
1 |
|
내장 함수인 repr()
와 dump()
는 어떤 객체든지 인수로 받아서 객체의 값을 문자열로 반환한다.
1 |
|
이런 함수들은 디버깅하기에 유용하다.
실행중에 발생할 수 있는 또 다른 문제는 시스템들이 라인의 끝을 구분하기 위해 각각의 다른 문자를 사용하는 것이다. 어떤 시스템은 줄바꿈을 \n
으로 표시하며, 다른 시스템은 \r
로 표시한다. 시스템들간에 파일을 이동하면 이런 불일치의 문제가 발생할 수 있다.
대부분의 시스템에는 한 형식에서 다른 형식으로 변환하는 응용 프로그램이 있다. 관련 정보는 해당 링크에서 확인할 수 있다.