VisualWorksTutorial1:Page07

From 흡혈양파의 번역工房
Jump to navigation Jump to search

cincom_tutorial_vwlogo
cincom_tutorial_cincomlogo

웹로그 통계 레슨 5
디렉토리 불러오기

cincom_tutorial_stlogo

| 목차 | 레슨4 | 레슨6 |
cincom_tutorial_openbook 로그파일에서 중복되지 않는 Hit수를 카운트했습니다. 하지만 한 개의 파일이 아닌, 좀 더 많은 파일을 실행하고 싶은 경우에는 어떻게 해야 할까요? 또한, 파일은 웹서버에서 매일 생성됩니다. 전체 디렉토리(로그파일이 존재하는)를 스캔해서 각 주, 월, 년 단위 통계를 내는 것은 어떨까요?
cincom_tutorial_certificate 이 레슨에서는 디렉토리 내용에 대해서 알아보는 방법을 설명하겠습니다. 예시에서는 디렉토리에 있는 모든 로그파일의 웹Hit를 카운트 할 수 있게 코드를 수정하겠습니다.
cincom_tutorial_directions
지금까지의 작업에서 작성한 코드는 읽기 쉬웠을 것이라 생각합니다. 두 개의 블록(한 개의 조건 블록과 한 개의 그룹 블록)으로 구성된 짧고 간결한 문이었습니다. 이 연습이 끝나면 그 코드는 지저분해 질 것이며, 그렇기에 읽기 힘들어집니다. 이것은 보다 효율적으로 코드를 짜기 위한 방법을 습득하기 위한 고의적 장치입니다. 다음 레슨에서는 코드를 수정할 것입니다. 지금은 이와 같은 코드 작성법을 그냥 이해해 주시기 바랍니다.
cincom_tutorial_steps 1. 새로운 작업공간을 열고 아래 텍스트를 입력해주십시오. 그리고 텍스트 전부를 반전시키고 <오퍼레이트 클릭>Inspect를 선택해주십시오.
'c:\vw7.7\image' asFilename directoryContents.

이 예시는 윈도우 환경에서의 경우입니다. 다른 OS를 사용하는 경우엔 사용하시는 시스템에 맞는 디렉토리를 c:\vw7.7 대신해서 지정해주십시오. 가장 좋은 것은 VisualWorks "표준 디렉토리" 혹은 "홈 디렉토리"를 사용하는 것입니다.


2. Inspector 화면의 기본탭을 클릭하고 self를 선택해주십시오.

오른쪽 창은 self 값인 디렉토리의 내용을 표시하고 있습니다. Smalltalk는 an Array로써 파일명(과, 디렉토리명)을 격납합니다.

그림 6-1. An Array로써 디렉토리를 격납
cincom_tutorial_question 문자열(single quotes를 포함해서)을 Filename(파일명)으로 간주하지 않기 위하여 Smalltalk에 전달합니다. 그러므로 인하여 Filename 객체가 돌아옵니다. 그 Filename 객체의 디렉토리 내용을 끌어내기 위해, 메시지를 Filename 객체에 보냅니다.(파일이 아닌 디렉토리였기 때문에) Unix를 잘 아시는 분은 디렉토리(파일시스템)와 파일이 똑같이 취급 되어도 이상하게 생각하지 않으실겁니다. 여기서는 그것과 똑같이 취급합니다.


지금 디렉토리 내용은 Smalltalk 컬렉션의 다른 형태인 an Array(배열) 안에 있습니다. 하지만 이 연습은 ws00에서 시작하여 .log로 끝나는 파일명 밖에 필요 없습니다. 다음과 같은 처리가 필요합니다.


  1. 배열 요소를 반복한다
  2. 패턴에 일치하지 않는 파일명을 삭제한다.


이 처리를 실행한 후, 이 파일들에 대하여 지금까지 작성해온 Hit를 카운트 하는 코드를 사용할 수 있습니다.

cincom_tutorial_steps 3. 아래와 같이 텍스트를 입력해주십시오. 그리고 텍스트 전체를 반전시키고 <오퍼레이트 클릭>실행을 선택해주십시오. 필요에 응해 웹로그 파일이 존재하는 디렉토리에 c:\vw7.7\image를 변환해주십시오.
| contents |
Transcript clear.
contents := 'c:\vw7.7\image' asFilename directoryContents.
contents do: [ :each | Transcript show: each.].

임시변수인 contents는 디렉토리에 있는 파일을 배열로써 대입시킵니다. 배열의 요소를 반복하여 얻는 방법은 do 블록을 사용합니다. do 블록은 두 가지 부분으로 구성되어 있습니다. 첫 부분에는 임시변수를 설정합니다(이 예시의 경우에는 each입니다.) 두 번째 부분은 단순한 Smalltalk문의 집합입니다. 두 개의 부분은 세로선 문자(pipe)에 의해 분할되어 있습니다.


임시변수 each의 선언 앞에 콜론이 있습니다. do 블록의 두 번째 부분에는 익숙한 Transcript show:문이 있습니다. Transcript에 표시하는 것은 do 블록의 임시변수 each의 값입니다. 따라서, contents 배열의 모든 요소는 do 블록의 임시변수 each로 건너갑니다. Transcript show:문은 contents 배열의 전 요소에 대해서 실행됩니다. 이것을 문장으로 표현하면 "contents 배열의 모든 요소를 취득하여 Transcript에 표시하라" 가 됩니다.

그림 6-2. 디렉토리의 내용을 표시한 Transcript.


4. 아래와 같이 텍스트를 수정해주십시오. 그리고 텍스트 전체를 반전시키고 <오퍼레이트 클릭>실행을 선택해주십시오. 그리고, 트랜스크립트 화면을 종으로 살짝 늘려주십시오.

| contents | 
Transcript clear. 
contents := 'c:\vw7.7\image' asFilename directoryContents. 
contents do: [ :each | 
Transcript show: each. 
Transcript cr.].

do 블록 두 번째 부분이 1행에서 2행이 되었습니다. Transcript에 파일명을 표시한 후, 캐리지 리턴을 삽입한 것으로 인해 한층 보기 편해졌습니다. crTranscript에 캐리지 리턴을 삽입하기 위한 Transcript(객체)로의 메시지입니다. 행이 줄바꿈되어 파일명이 표시됩니다.

그림 6-3.- 디렉토리 내용표시가 보기 편해진 Transcript


5. 아래와 같이 텍스트를 수정해주십시오. 그리고 텍스트 전체를 반전시키로 <오퍼레이트 클릭>후 실행을 선택해주십시오. 이 코드는 위의 코드와 같은 작업을 합니다. 같은 객체에 대해 다시 메시지를 보낼 때의 코드 작성법입니다.

| contents | 
Transcript clear. 
contents := 'c:\vw7.7\image' asFilename directoryContents. 
contents do: [ :each | Transcript show: each; cr.].

이것은 cascade의 예입니다. Each 뒤에 세미콜론이 있는 것에 주의해주십시오. 세미콜론은 문이 끝나는 것을 Smalltalk에 전합니다.(피리오드와 같음) 하지만 그 뒤가 있습니다. 이전에 썼던 문에서 사용된 객체(위 예시의 경우 Transcript)에 보낸 메시지가 세미콜론 뒤에 계속됩니다.


6. 파일명 배열요소를 반복함으로써 "log"파일을 구별합니다. 아래와 같이 텍스트를 수정해주십시오. 그리고 모든 텍스트를 반전시키고 <오퍼레이트 클릭>실행을 선택해주십시오.

| contents xFound | 
Transcript clear. 
contents := 'c:\vw7.7\image' asFilename directoryContents. 
contents do: [ :each | 
xFound := each findString: '.log' startingAt: 1. 
xFound > 0 
ifTrue:[ Transcript show: each; cr.].].

만일 로그파일이 있음에도 불구하고 표시되지 않은 경우는 소문자 .log에서 대문자(.LOG)로 변환 시킬 필요가 있을지도 모릅니다. Smalltalk에서는 문자의 대, 소문자를 요주의 하여야 합니다. 이 예문에서는 블록식 안에 또 블록식이 있습니다. 이것은 아래와 같이 작성하면 보기(읽기) 편할지도 모릅니다.


| contents xFound | 
    Transcript clear.
    contents := 'c:\vw7.7\image' asFilename directoryContents. 
    contents do: [ :each | 
                   xFound := each findString: '.log' startingAt: 1. 
                   xFound > 0 
                   ifTrue:[ Transcript show: each; cr.].
                 ].
그림 6-4. 모든 "log"파일이 표시된 트랜스크립트

여기서 닫습니다. 모든 로그파일을 얻었습니다만, 필요 없는 .log파일은 배제합니다. 따라서, 필터의 로직을 다시 한 번 정의해야 합니다


(사용중인 디렉토리에 의해 불필요한 .log파일이 존재하지 않을 수도 있습니다. 확인용으로, 확장자가 .log인 파일을 작성해주십시오.)


7. "문자열조사"를 넣음으로 인해, ws00으로 시작해 .log로 끝나는 모든 로그파일을 찾습니다. 아래와 같이 텍스트를 수정해주십시오. 그리고 텍스트 전체를 반전시키고 <오퍼레이트 클릭>실행을 선택해주십시오.

| contents sFound eFound | 
Transcript clear. 
contents := 'c:\vw7.7\image' asFilename directoryContents. 
contents do: [ :each | 
sFound := each findString: 'ws00' startingAt: 1. 
eFound := each findString: '.log' startingAt: 6. 
(sFound > 0) & (eFound > 0)
그림 6-5. 모든 "ws00*log"파일이 표시된 트랜스크립트


여기서는 추가된 코드 설명을 하겠습니다.

sFound := each findString: 'ws00' startingAt: 1.

여기서 새로운 것은 아무것도 없습니다. ws00로 시작하는 로그파일부터, 즉 문자열 맨 처음에서 문자의 확인을 합니다.

eFound := each findString: '.log' startingAt: 6.

.log로 끝나는 로그파일, 즉 문자열 맨 뒤에서 문자를 확인합니다. 문자 .log가 아홉 번째까지 시작하지 않으므로 7이나 8도 가능합니다.

(sFound > 0) & (eFound > 0)

이것은 이항식이 복수 있습니다. 앰퍼샌드 문자(&)는 두 개의 이항식을 AND로 만들 때 사용합니다. 이 문은 로그파일의 개시가 ws00이며 ,또한 끝이 .log일 경우만 참이 됩니다. 이것에 의해 필요 없는 로그 파일이 제외됩니다.


8. 이제 매우 흥미있는 부분입니다. 이 VisualWorks 연습을 처음부터 계속 진행해왔다면 두 개의 Workspace가 열려있을 것입니다. 첫 Workspace에는 지정된 로그파일에 대해 Hit수를 카운트하는 코드가 있습니다. 두 번째 Workspace에는 할당받은 디렉토리의 모든 "log"파일을 표시하는 코드가 있습니다. 이 코드들을 좀 더 사용하기 편하게 만들어 봅시다.


두 번째 Workspace에서는 로그파일을 발견하였을 경우, Transcript에 파일명을 표시합니다. 그러기 위해 웹Hit를 규정하는 코드를 치환할 필요가 있습니다. 아래와 같이 해주십시오. 보기 편하게 칸에 집어넣겠습니다.


아래와 같이 코드를 입력해주십시오. 그리고 모든 텍스트를 반전시키고 <오퍼레이트 클릭>실행을 선택해주십시오.

| myFile myStream myLine addrIP mySet contents sFound eFound logDirectory | 
    Transcript clear.
    logDirectory := 'c:\vw7.7\image'. 
    contents := logDirectory asFilename directoryContents. 
    contents do: 
             [ :each | 
               sFound := each findString: 'ws00' startingAt: 1.
               eFound := each findString: '.log' startingAt: 7.
               (sFound > 0) & (eFound > 0)
               ifTrue:
               [ mySet := Set new. 
                 myFile := (logDirectory, '\', each) asFilename.
                 myStream := myFile readStream. 
                 [ myStream atEnd ] whileFalse: 
                   [ myLine := myStream upTo: Character cr. 
                     addrIP := myLine copyUpTo: $,. 
                     mySet add: addrIP.
                   ]. 
                 myStream close.
                 Transcript show: each, '...', mySet size printString; cr.
               ].
             ].
그림 6-6. 모든 로그파일에 대한 웹Hit수
cincom_tutorial_question 많은 것이 진행되었습니다. 그리고 새로운 구문이 추가되었습니다. 그러면 예시에 나온 코드를 하나씩 분해해서 이해해 봅시다.
| myFile myStream myLine addrIP mySet contents sFound eFound logDirectory |

이번 코드에서 사용되는 임시변수입니다. 각 변수는 스페이스로 구분되며, 전체를 종선으로 둘러쌉니다.


Transcript clear.

Transcript 내용을 클리어(표시내용을 삭제)하는 단순 메시지입니다.


logDirectory := 'c:\vw7.7\image'.

임시변수에 서버 로그파일의 위치를 대입합니다. 디렉토리명은 사용하시는 시스템, 네트워크상의 폴더, 파일 시스템, 디렉토리 이름이 되도록 해주십시오.


contents := logDirectory asFilename directoryContents.

디렉토리 내용을 수집하여 배열에 파일명을 격납합니다. logDirectory 임시변수에는 디렉토리명이 문자열로써 포함되어 있습니다. 때문에, Filename 객체로 변환하기 위해서는 asFilename 메시지를 logDirectory에 전송합니다. 그리고 Filename 객체가 가지고 있는 directoryContents 메시지에 의해 그 디렉토리에서 모든 파일명을 골라내는 작업을 합니다.


contents do:

루프 개시입니다. 임시변수 contents(지정된 디렉토리의 모든 파일을 일람한)의 내용을 처음부터 끝까지 반복합니다.


[ :each |

첫 블록식의 개시입니다. 블록 첫 부분은 임시변수가 선언된 선언부입니다. 이 경우, :each가 파일명(contents 배열의 각 요소)을 격납하기 위해서 사용되는 임시변수입니다. 선언부는 세로줄로 종료합니다.


sFound := each findString: 'ws00' startingAt: 1.

Contents 배열의 각 요소는 문자열입니다. String 클래스의 인스턴스는 findString:startingAt: 메시지를 인식합니다. 각 이름이 나타내듯, 특정 위치에서 시작하는 문자열 중에서 문자열을 찾습니다. 파일명(readme.txt"와 같이)을 부여하면, 파일명의 첫 문자에서 개시하여 문자열(연속하는 문자)"ws00"를 검색합니다. 이 메서드는 수치를 돌려줍니다. 여기서는 "ws00"문자열의 개시 위치입니다. 그리고, 그렇게 돌아온 값이 임시변수 sFound에 격납됩니다. 만일 돌아온 값이 0(제로)일 경우, 문자열을 찾을 수 없었던 것을 의미합니다. 따라서, 그 파일은 "ws00"파일이 아닌 것은 확인할 수 있습니다.


eFound := each findString: '.log' startingAt: 7.

앞의 코드와 같이, 특정 문자열을 지정위치에서부터 지정문자를 찾아냅니다. 따라서, 파일명의 7번째 문자에서 시작하여 ".log"문자를 찾아냅니다. 메서드의 결과는 수치입니다. - ".log"로 시작하는(이 경우에는 9입니다.) 문자위치 – 그리고, eFound 임시변수에 값을 격납합니다. 돌아온 값이 제로일 경우, 지정 파일이 ".log"파일이 아니었기에 문자열을 찾을 수 없었다는 것을 의미합니다.


ifTrue:

다음 코드 블록의 개시입니다. sFound > 0 메서드에서 돌아온 값이 참이거나 eFound > 0 메서드에서 돌아온 값이 참일 경우, 아래의 코드 블록을 실행합니다. sFound > 0 메서드에서 돌아온 값이 거짓일 경우, eFound > 0 메서드에서 돌아온 값이 거짓일 경우, 아래의 코드 블록을 스킵합니다.


[ mySet := Set new.

두 번째 블록식의 첫 행입니다. 블록파일을 찾았기에, 중복되지 않는 IP 어드레스를 카운트하는 코드가 시작됩니다. 우선 새로운 Set을 작성하는 것부터 시작합니다.


myFile := (logDirectory, '\', each) asFilename.

Smalltalk에서는 파일 위치를 명확히 전달할 필요가 있습니다. 임시변수 contents의 내용은 단순한 파일명의 일람입니다. 디렉토리명을 지정해서 파일명을 취득하고 있음에도, Smalltalk는 디렉토리를 기억하지 못합니다. 따라서, 디렉토리명이 격납된 변수와 파일명을 결합할 필요가 있습니다. 백슬래쉬("\") 문자는 디렉토리명과 파일명 사이에 사용됩니다.(VisualWorks 일본판에서는 엔(円)마크가 "\"가 됩니다.) 콤마 메서드는 문자열끼리 결합합니다. 이 식 전체에서 괄호 안의 내용을 맨 처음 실행합니다. 그리고 파일명에 문자열을 변환하는 asFilename 메시지를 보냅니다. 이것들의 결과를 임시변수 myFile에 대입합니다.


myStream := myFile readStream.

이 구문은 stream에 파일을 변환시켜줍니다. 그리고 변환된 결과를 임시변수 myStream에 대입합니다.


[ myStream atEnd ] whileFalse:

세 번째 (맨 마지막) 블록식의 시작입니다. 블록 맨 처음 부분은 논리식(불 논리 – Boolean logic)입니다. 스트림 맨 마지막(즉 파일 맨 마지막)에 있는지 조사합니다. 그리고 참(스트림의 맨 마지막입니다)인가 거짓(또는 맨 마지막이 아닙니다)을 돌려줍니다. 이 결과는 whileFalse: 메시지에 보냅니다. stream의 맨 마지막이 올때까지 Smalltalk는 이 메시지에 이어지는 블록식을 처리합니다. Stream 맨 마지막에 도달했을 때, Smalltalk는 이 메시지에 이어지는 모든 블록식을 날려버립니다.


[ myLine := myStream upTo: Character cr.

세 번째 블록식의 맨 처음 문입니다. 문자 cr이 블록파일에 대해 행의 마지막을 나타냅니다. 이 행에서는 cr문자에 다다를 때까지 모든 문자를 취득하여 임시변수 myLine에 취득문자열을 대입합니다.


addrIP := myLine copyUpTo: $,.

임시변수 myLine의 내용은 문자열입니다. 로그파일의 IP 어드레스는 콤마 구분 분자열의 맨 처음 필드이기 때문에, 맨 처음 콤마에 다다를 때까지 모든 문자를 복사할 필요가 있습니다. 콤마는 Smalltalk에서 특별한 의미를 지니고 있기에(힌트 : 바로 위 문을 참조해주십시오), 달러마크($) 앞에 붙임으로 인해 특별한 의미를 무효화시키고, 콤마문자로써 취급하도록 Smalltalk에 전달합니다.


].

세 번째 블록식의 끝입니다.(whileFalse: 조건이 파일 끝이 된 상태)


myStream close.

이 문에 다다른 때는, 파일 끝에 달했다는 의미입니다. 여기서는 stream을 닫음으로 인해 파일을 닫습습니다. 파일을 stream으로 변환했기에 stream을 닫을 필요가 있습니다.


Transcript show: each, '...', mySet size printString; cr.

1행 끝에는 코드가 많은 느낌이 듭니다. 하지만 실은 그렇지 않습니다. 지금까지의 작업에서 지정된 파일이 지닌 Hit수를 알고 있습니다. 그렇기에, Transcript에 그 수를 단순히 표시하면 됩니다. 그러면 Transcriptshow:를 설명하겠습니다. each에는 파일명이 포함되어 있습니다. 콤마를 사용하여 파일명과 세 개의 도트 문자열('…')을 결합합니다. 그리고 mySet size printString을 사용해서 Hit 수도 결합합니다. show: 메서드는 수치를 받아들이지 않습니다. 문자열만 받아들입니다. 그렇기에, mySet size식은 수치를 돌려주기에 printString을 사용해서 문자열을 변환하고 있습니다. 맨 마지막에는 cr 메서드를 Transcript 객체(세미콜론)에 cascade합니다. 이 결과, 파일의 웹Hit가 표시될 때마다 개행됩니다.


].

두 번째 블록식의 끝입니다.(ifTrue: 조건이 로그파일을 찾았는지 안찾았는지의 상태)


].

첫 블록식의 끝입니다.(do:가 배열의 요소를 반복하고 있음)

cincom_tutorial_check 정리

다음 워크샵에서는 이 코드를 더 작게 분할하겠습니다.


아래와 같은 내용을 학습하였습니다

  • Smalltalk의 순차적 적용(cascade)구문
  • 다른 문자열에 있는 문자열을 검색
  • AND를 사용한 이항식의 비교
  • 문자열의 결합
  • 디렉토리의 내용을 수집(컬렉션)
  • do: 블록을 사용하여 Collection을 반복

| 목차 | 레슨4 | 레슨6 |