오늘은 IDL에서 텍스트 파일(ASCII 파일이라고도 하죠)을 효과적으로 읽는 방법에 관한 얘기를 좀 해볼까 합니다. 굉장히 올드해보이는 주제이기도 하지만, IDL 사용자들이 상당히 많이 고민하게 되는 문제이기도 합니다. 왜냐하면 과학기술 분야에서의 자료 처리에 있어서, 텍스트 기반의 자료 파일을 읽어들여서 처리하는 일은 너무나 자주 직면하게 되는 문제이기 때문일겁니다. 물론 이런 문제는 당사자의 프로그래밍의 수준이 초급이냐 중급이냐 고급이냐를 따지지 않고 누구에게나 달려듭니다. 초급 프로그래머에게는 당연히 쉽지 않은 문제일 것이고요. 그렇다고 중/고급 프로그래머에게도 마냥 쉽기만 한 문제는 아닐 수 있습니다. 좀 귀찮게 느껴지는 문제이기도 합니다.
물론 이러한 문제의 해결을 위하여 많은 IDL 사용자들께서 아마 READCOL이라는 루틴을 자주 이용하고 계실겁니다. 제가 아는 어떤 IDL 사용자가 "이 READCOL을 만든 사람한테는 정말 큰 상을 줘야 한다"고 얘기를 하는 것을 들은 적이 있습니다. 저 역시 절대적으로 동감하고요. 정말 많은 IDL 사용자들이 이 루틴 덕분에 그래도 텍스트 파일을 다루는데 있어서 많은 도움을 얻고 있다는 점은 부인할 수 없는 사실입니다. 그런데, 이렇게 편리한 READCOL을 제대로 사용하기 위해서는 한가지 전제가 필요합니다. 대상이 되는 텍스트 파일의 컬럼 구조가 일관되어야 한다는 단서가 붙습니다. 물론 헤더(header)에 해당되는 일정 갯수의 줄(Line)들은 존재해도 상관없지만, 실제 데이터 값들에 해당되는 부분은 컬럼들의 갯수라든지 분리자의 형태 등등이 다 일정해야 합니다. 예를 들면 아래와 같은 형태라면 얼마든지 READCOL로 해결이 됩니다.
Player Pos G AB H
Bruce RF 160 626 164
Choo CF 154 569 162
Cozart SS 151 567 144
Frazier 3B 150 531 124
Hamilton CF 13 19 7
Hanigan C 75 222 44
이 내용의 경우 처음 한 줄이 헤더에 해당되고, 나머지 줄들이 실제 자료값들이 됩니다. 그러면 READCOL을 사용할 때 헤더의 줄 수가 한 줄이라는 것만 해당 키워드로 명시해주고, 각 컬럼별 값들이 어떤 자료형(Type)이어야 하는가만 지정해주면 나머진 다 알아서 해줍니다. 이 세상의 텍스트 파일들이 전부 이러한 일관성 있는 구조를 갖고 있다면 얼마나 좋겠습니까? 하지만, 불행히도 이렇게 내부적인 컬럼 구조가 일관되지 않은 텍스트 파일들도 세상에는 분명 존재합니다. 그리고 일을 하다보면 이러한(다른 표현을 쓴다면 "지저분한") 텍스트 파일들을 다뤄야만 하는 경우가 간혹 생깁니다. 피할 수 있다면 피하고 싶지만 도저히 피할 수 없는 경우에는 어떻게든 돌파구를 마련해야 합니다. 물론 방법은 존재합니다만, 이를 위해서는 IDL의 문자 처리와 관련된 내장함수들에 대한 이해가 어느 정도 필요합니다. 그래서 이와 관련하여 제가 오래전에(벌써 6년전이더군요) 예전 IDL User 게시판에 "ASCII 파일 읽기의 또 다른 길"이라는 글을 게재한 적이 있었습니다. 여기서 말하는 "또 다른"이란 의미는 READCOL외에 또 다른 방법이라는 의미입니다. 글 내용이야 그리 길지도 않고 내용도 그리 대단한 것은 아니었지만, 이 글의 내용을 바탕으로 2014년인 지금 비슷한 문제를 한번 더 다뤄보고자 합니다. 더구나 최근 들어서 신규 IDL User 게시판에 관련 질문들도 많이 올라왔었는데요. 이런 것을 보면서, 이 문제가 정말 이 바닥에서는 영원한 이슈라는 생각이 문득 들더군요. 어쨌든 한번으로는 좀 부족할 수도 있을 것 같아서 [1]이라는 일련번호를 붙여봤습니다. 총 몇 회가 될지는 모르겠지만, 저도 내용 반 잡담 반으로 편하게 적어보도록 하겠습니다. 다소 두서없이 적다보니 내용이 좀 길게 느껴질 수도 있다는 점 양해 부탁드립니다.
최근에 올라왔던 질문들 중에 텍스트 파일의 내용이 다음과 같은 경우 어떻게 읽어야 하는가에 대한 질문이 있었습니다. 물론 READCOL로는 해결이 안되는 경우입니다.
a b c d e
1 1 1 1
2 2 2
3 3 3 3 3
4 4 4
5 5 5 5
이 경우에는 텍스트 파일의 내용을 한 줄씩 읽어가면서 처리하는 아주 기본적인 방법을 사용하는 것이 좋습니다. 물론 제가 "기본적"이라고 언급하는 의미는 IDL에서 제공되는 기본적인 문자처리 내장함수들을 사용해야 한다는 의미입니다. 그리고 이러한 단계 이전에 선행되어야 할 것은 파일을 여는(OPEN) 일입니다. 파일을 "연다는" 의미는 IDL과 이 파일 사이의 연결 채널을 특정한 번호로 명시해준다는 것입니다. 만약 위의 내용을 담고 있는 파일의 이름이 "aaa.txt"라면 다음과 같은 과정을 먼저 수행합니다.
ifile = 'aaa.txt'
OPENR, 1, ifile
이 내용이 실행되면 IDL과 aaa.txt 파일간의 교신 채널이 열리고 그 번호는 1번이 됩니다. 이 번호는 1~99의 범위내에서 아무 번호나 적당히 선택하면 됩니다. 이런 번호를 전문용어로는 Logical Unit Number 또는 약자로 LUN이라고도 부릅니다. 이렇게 채널 번호를 프로그래머가 직접 정하는 경우도 있지만, 어떤 경우에는 그 번호의 선택을 IDL에 맡기는 경우도 있습니다. 만약 이렇게 처리하고자 할 경우에는 위의 내용의 두번째 줄을 다음과 같은 내용으로 대체할 수 있습니다. 참고로 이런 경우에는 IDL은 100~128의 번호들을 사용합니다.
OPENR, lun, ifile, /GET_LUN
이 내용은 적절한 번호를 IDL이 알아서 가져와서 그 번호를 lun이라는 변수에 저장한 후, 이 번호로 IDL과 aaa.txt 파일 사이의 교신 채널을 열어두라는 의미입니다. 여기서 lun은 변수명이기 때문에 어떤 이름이 되어도 상관없지만, 관례적으로는 앞서 언급한 LUN이란 의미로 그냥 이렇게 lun이라는 이름으로 적는 경우가 많습니다. 저도 예전에 다른 고급 IDL 프로그래머들(David Fanning, Michael Galloy 등)이 저러는거 보고 배운겁니다. 어쨌든 이와 같이 파일과 IDL 사이의 교신 채널이 열렸으면, 이제는 그 채널을 통해서 내용을 읽어오는 과정으로 들어가면 됩니다. 교신 채널 번호를 후자와 같이 lun이란 변수명으로 갖게 된다면, 다음과 같이 READF 명령을 사용하여 이 채널을 통하여 내용을 읽어오면 됩니다.
ss = ''
READF, lun, ss
PRINT, ss
여기서 주목해야 할 부분은 ss라는 문자변수를 하나 만든 다음, READF가 읽어온 내용을 ss에 저장하도록 했다는 점입니다. 기본적으로 이와 같이 READF가 한번 실행되면, 채널을 통해서 텍스트 파일의 첫번째 줄의 내용을 가져옵니다. 단 문자형(String)으로 가져오기 때문에 위와 같이 문자변수를 따로 하나 만들어서 사용을 합니다. 여기서 ss를 문자변수로 지정을 안하면 IDL은 묵시적으로 ss를 정수나 실수형으로 인식하는데, 이 경우에는 읽는 과정에서 I/O 에러가 발생합니다. 따라서 문자변수를 사용하는 것은 필수적이고, 이를 위해서 READF에서 사용될 변수를 문자형 변수라고 명시적으로 선언하는 과정이 필요하다고 보면 되겠습니다. 어쨌든 이렇게 하면 첫번째 줄의 내용 전체가 ss로 전달됩니다. 그래서 PRINT 명령을 사용하여 이 내용을 바로 출력해보면 내용 확인이 가능합니다.
만약 위와 같은 READF 명령을 또 한번 사용하면 그 때에는 그 다음 줄, 즉 두번째 줄의 내용을 가져옵니다. 또 사용하면 세번째 줄, 그 다음은 네번째 등등으로 줄 단위로 옮겨갑니다. 물론 지금 우리고 보고 있는 aaa.txt의 경우는 총 6줄인 것이 눈으로 보입니다. 따라서 READF 명령이 6회 사용되면 파일의 모든 줄들을 순차적으로 읽게 됩니다. 이 얘기는, 파일의 모든 줄들을 순차적으로 다 읽으려면, 결국은 파일의 줄 수 만큼 READF 명령을 반복적으로 사용해야 한다는 의미이기도 합니다. 따라서 FILE_LINES 함수를 사용해서 텍스트 파일의 줄 수를 먼저 파악하고 이 줄 수 만큼의 반복이 이루어지도록 반복문을 사용해야 하는 경우도 염두에 둬야 합니다. 하지만, 그 부분은 제가 다음 회차에서 다루기로 하고, 일단 각 줄마다의 내용이 읽혀진 ss를 어떻게 처리하는가에 대한 얘기를 좀 더 해보겠습니다.
그러면, 첫번째 줄의 내용이 문자형 변수 ss에 들어간 상태를 먼저 생각해보겠습니다. READF 명령이 처음 사용된 직후 ss의 내용을 PRINT 명령으로 출력하도록 해보면, 당연히 aaa.txt 파일의 첫 줄에 있는 다음과 같은 내용이 출력됩니다.
a b c d e
그런데 이 줄은 사실 각 컬럼의 내용이 무엇인지를 알려주는 일종의 헤더(header)에 해당되는 부분입니다. 즉, 실제 자료값은 아닌 셈이죠. 따라서 이 줄은 그냥 한번 읽기만 하고 넘기고, 바로 이어서 다음 줄을 READF로 읽은 후 그 내용이 담긴 ss를 확인해보는 것이 올바른 순서일겁니다. 이렇게 두번째 줄을 읽고 ss를 출력해보면 아래와 같은 내용이 출력될겁니다.
1 1 1 1
그런데 여기서 이 원본 텍스트 파일의 자료 구조를 생각을 해봐야 할 것 같습니다. 제가 직접 사용하던 자료 파일이 아니라서 정확한 정보는 갖고 있지 않지만, 그냥 제 짐작에는 이 텍스트 파일에서 자료값들은 기본적으로 a, b, c, d, e 등 다섯 개의 컬럼으로 들어가 있어야 하는 것이 원칙일텐데, 실제로 자료값들이 있는 줄들을 보면 각 줄마다 값의 갯수가 다릅니다. 그러면 실제 자료값이 시작되는 첫번째 줄에 있는 네 개의 1이란 값들은 순서나 칸 수로 보면 a, b, c, d의 값들일 것이고, 이 줄에서는 e에 해당되는 컬럼은 없는 셈입니다. 그 다음 줄을 보면 세 개의 2라는 값들만 있는데, 마찬가지 방식으로 보면 a, b, c의 값들만 있고 d, e에 해당되는 값들은 없는 경우가 됩니다. 그러면 이 텍스트 파일이 원래 작성될 때 실제 자료값들이 있는 각 줄이 과연 어떤 방식으로 쓰여졌는가가 중요해집니다. 만약 한 칸의 공백이 분리자(Delimeter)로 사용된 경우라면, 한 줄의 내용에 해당되는 문자열을 이 분리자를 사용하여 마디마디 쪼개야 합니다. 이를 위하여 사용되는 내장함수가 바로 STRSPLIT인데요. 다음과 같은 요령으로 사용하면 됩니다.
spl = STRSPLIT(ss, ' ', /EXTRACT)
HELP, spl
PRINT, spl
이 내용은 ss라는 문자변수에 저장된 문자열을 분리자 ' '를 기준으로 쪼개어 그 조각에 해당되는 문자값들을 spl이라는 문자형 배열로 갖고 있으라는 의미입니다. 따라서 바로 뒤이어 HELP 명령으로 이 spl의 정보를 조회하면 몇 개의 원소들로 이루어진 문자형 배열이라는 정보가 뜹니다. 만약 실제 자료값이 시작되는 첫번째 줄(1이란 값들이 네 개 있는)에 대하여 위와 같은 작업을 수행해보면, spl은 네 개의 원소들로 이루어진 문자형 배열이라는 정보가 출력됩니다. 그리고 바로 이어지는 PRINT 명령으로 이 spl의 값들을 출력하라고 했으므로 당연히 1이란 값 네 개가 출력될겁니다. 이 얘기는 다섯번째 컬럼인 e에 해당되는 값은 이 줄에는 없다는 의미입니다. 같은 요령으로 그 다음 줄에 대하여 작업해보면 2라는 값 세 개가 출력될겁니다. 그리고 네번째 및 다섯번째 컬럼인 d, e에 해당되는 값은 이 줄에는 없다는 의미가 되겠지요.
일단 오늘은 여기까지만 적어보겠습니다. 사실 지금 다루고 있는 aaa.txt라는 파일의 내용과 관련해서 더 많은 경우의 수들을 생각해볼 여지가 있습니다. 이 얘기는 조만간 이어질 2회차 게시물에서 계속 해보도록 하겠습니다.
'IDL > Programming' 카테고리의 다른 글
바이트 순서, Little Endian, Big Endian (0) | 2014.05.15 |
---|---|
텍스트 파일을 효과적으로 읽는 방법에 관하여 [2] (0) | 2014.05.11 |
배열내 마지막 원소값을 조회하는 요령 (0) | 2014.04.02 |
날짜와 관련된 문제 해결 사례를 소개합니다 (예고) (0) | 2014.02.24 |
CV_COORD 함수에 대하여 (0) | 2014.02.19 |