본문으로 바로가기

Tcl 커맨드의 확장 TclX

category 카테고리 없음 2025. 2. 12. 22:04

TclX(Extended Tcl)는 NetSoft의 Karl Lehenbauer와 Mark Diekhans가 개발한 Tcl 언어의 가장 유명한 확장 패키지 중 하나입니다. 현재 Tcl 8.4 이상에 대응하는 버전이 공개되어 있으며, TclX 자체의 버전도 Tcl의 버전을 따르고 있습니다. (현재 최신 버전은 8.6.3) TclX는 Tcl 언어의 확장일 뿐, Tk에 대한 확장은 제공하지 않으며, 다음과 같은 기능이 추가되어 있습니다.

  • 재귀 glob와 같은 편리한 명령어
  • 명령어(커맨드) 추적과 같은 디버깅 메커니즘
  • 신호, 파일 속성 조작과 같은 리눅스 시스템 프로그래밍
  • 키가 포함된 리스트

물론 이 외에도 더 있지만, 유용한 기능들만 추려서 소개해 봅니다.

재귀 글로브(glob)

recursive_glob은 리눅스의 find 명령처럼 하위 디렉토리들을 검색해 가며 재귀적으로 파일을 찾는 명령어입니다. 또한, 찾았을 경우 어떠한 조작을 하기 위해 for_recursive_glob이라는 foreach와 같은 제어 커맨드로 제공합니다. recursive_glob은

recursive_glob startdir_list glob_list glob_list

 

위와 같은 방식으로 사용합니다. 예를 들어, 현재 디렉토리에서 재귀적으로 검색하여 *.tcl이라는 이름의 파일 목록을 얻으려면 아래와 같이 합니다.

set files [recursive_glob . *.tcl]

 

또한 for_recursive_glob을 사용하여, 현재 디렉토리에서 재귀적으로 검색하여 *.tcl이라는 이름의 파일을 모두 검색하고, 그중 'require'라는 문자열이 포함된 행의 줄 번호와 함께 모두 출력하려면 다음과 같이 하면 됩니다.

 for_recursive_glob a {.} {*.tcl} {
	set fp [open $a r]
	for {set n 1} {! [eof $fp]} {incr n} {
	    set buf [gets $fp]
	    if {[regexp {require} $buf]} {
		puts "$a\[$n\]:$buf"
	    }
	}
	close $fp
 }

커맨드 추적과 프로파일링 

커맨드 추적은 Tcl 커맨드가 실행될 때마다 그 커맨드를 로그 파일에 기록하는 기능입니다. 또한 프로파일링은 Tcl 스크립트를 실행했을 때, 어떤 커맨드가 몇 번 실행되었는지, 얼마나 시간이 걸렸는지를 기록하여 보고서와 같은 형태의 파일로 만들어 주는 기능입니다. gprof와 같은 프로파일러를 사용해 본 적이 있는 분이라면 익숙할 것입니다. 이 기능들은 스크립트 디버깅에 도움이 됩니다. 그럼 기존 Tcl 스크립트에 이 기능들을 사용해 봅니다.

cmdtrace on [open cmd.log w]
profile -commands -eval on

source "freecell.tcl"

cmdtrace off
profile off ProfResult
profrep ProfResult calls prof.log

cmdtrace

cmdtrace 커맨드는 커맨드를 추적하는 기능으로, 'on'을 지정한 후 'off'로 중단할 때까지 기록됩니다. 'on'을 지정할 때 open 커맨드의 리턴값인 파일 핸들(id)을 함께 지정하면 결과는 해당 파일(위에서는 cmd.log)에 기록됩니다. 물론 생략고 가능한데 생략 시 표준출력(stdout)으로 출력됩니다. 커맨드의 추적 결과는 cmd.log에 아래와 같이 기록됩니다.

 1:  profile -commands -eval on
 1:  source freecell.tcl
 2:    namespace eval FreeCell {\n\nset ImageData(spade) { R0lGODdhEwATAPc...}
 3:      set ImageData(spade) { R0lGODdhEwATAPcAAAAAAAAAVQAAqgAA/wAkAAA...}
 3:      set CANVASWIDTH 380
 3:      set CANVASHEIGHT 380
 3:      clock seconds
 3:      expr srand(930965642)
 3:      proc ProgramInit {} {\nvariable Score\nset Score(win) 0; set ...}

맨 왼쪽의 숫자들은 프로시저 계층(depth) 을 의미합니다.

expr srand([clock seconds])

와 같은 중첩된 명령어가 'clock seconds'와 'expr srand(930965642)'라는 두 단계로 기록되어 있는 것을 볼 수 있습니다.

profile

profile은 프로파일링을 하는 명령어이며, 사용법은 cmdtrace와 거의 비슷합니다. 'on'일 때 -commands나 -eval 옵션을 붙이면 더 세밀하게 검사할 수 있습니다. 결과는 'off'일 때 인수로 지정한 이름의 변수에 array 형태로 저장됩니다. 프로파일링 결과는 prof.log에 아래와 같이 기록됩니다.

-----------------------------------------------------------------------------
Procedure Call Stack                              Calls  Real Time   CPU Time
-----------------------------------------------------------------------------
FreeCell::random                                    380        158        157
    FreeCell::Init
    source
return                                              380          0          0
    FreeCell::random
    FreeCell::Init
    source
llength                                             175         10         11
    FreeCell::Init
    source
list                                                156          0          0
    FreeCell::DrawCardAtCanvasLocationOf
    FreeCell::DrawAll
    FreeCell::Init
    source
...
...

로그상 가장 많이 호출된 프러시저는 FreeCell::random이라는 프로시져이고, 그 프러시저에서 복귀할 때 return 커맨드가  같은 횟수만큼 사용되었기 때문에 이 두 프러시저와 커맨드가 상위권에 정렬되어 있는 것입니다. 아래는 호출된 횟수의 순으로 정렬하여 결과를 볼 수 있습니다. 문자열을 빈 문자열이나 공백으로만 구성된 문자열과 비교할 때, llength의 값이 0인지 아닌지로 비교하는 개발자의 코딩 스타일이 잘 드러나고 있습니다.

profrep 

결과를 정형화된 파일로 출력하기 위해서는 profrep 커맨드를 사용하면 됩니다. 두 번째 인자는 정형화 시 정렬 기준으로 'cpu', 'calls', 'real' 중에서 선택할 수 있습니다. 마지막 인자는 출력 파일 이름입니다.

시스템 프로그래밍 

signal

TclX에는 파일 속성 조작 등 고급 시스템 프로그래밍을 위한 명령어를 많이 제공하고 있지만, 전부 사용할 일은 없을 것입니다. 이유는 스크립트 언어에서 chroot나 select를 자주 사용하는 사람은 많지 않을 것입니다. 그중에서도 사용하면 유용한 커맨드는 signal 커맨드를 이용한 시그널 조작입니다. 시그널을 건너뛰거나 시그널의 일종인 alarm 기능을 사용할 수도 있지만, 시그널을 받는 방법은 다음과 같습니다.

signal trap SIGINT trapINT
signal trap 15     trapTERM

proc trapINT {args} {
    tk_messageBox -message "SIGINT Trapped."
}
proc trapTERM {args} {
    tk_messageBox -message "SIGTERM Trapped."
}

label  .laba -text Hello
button .cmda -text {Send Any Signal} -command exit
pack .laba -side top
pack .cmda -side top

pipe

TclX에서는 파이프 기능도 제공하고 있습니다. 이 커맨드를 활용한 예로서 자식 프로세스를 실행하고 표준 입출력을 서로 연결하여 데이터를 주고받는 스크립트를 소개합니다. 간만에 실용적인 스크립트를 만들고 싶어서, 자식 프로세스는 아래와 같은 Perl 스크립트로 만들어 보았습니다.

#!/usr/local/bin/perl
chomp($s = <STDIN>);
if(/^\d/){
    ($a) = gethostbyaddr(pack("C*", split(/\./, $s)), 2);
} else {
    ($a) = join(".", unpack("C*", gethostbyname($s)));
}
print "[$a]\n";
# end.

이 스크립트는 표준 입력으로 서버 머신의 호스트명 또는 IP 주소를 입력받고, Perl의 gethostbyaddr 함수 또는 gethostbyname 함수를 사용하여 호스트명이라면 IP 주소로, IP 주소라면 호스트명으로 변환하여 표준 출력으로 출력합니다.

 

아래는 상위 프로세스인 Tcl 스크립트입니다.

package require Tclx

pipe fdin1 fdout1
pipe fdin2 fdout2

if {[fork] == 0} {
    dup $fdin1  stdin
    dup $fdout2 stdout
    set cmd "perl ./gethost.pl"
    eval execl $cmd
} else {
    set alt_stdout [dup stdout]
    dup $fdin2  stdin
    dup $fdout1 stdout
    fcntl stdout NONBLOCK 1
    fcntl stdout NOBUF    1

    puts "www.daum.net"
    set buf [gets stdin]
    puts $alt_stdout "*** $buf ***"
    puts $alt_stdout ": [wait]"
}

이 스크립트는 pipe, dup, fork, fork, execl, fcntl의 5가지 시스템 콜을 사용하여 자식 프로세스를 실행하고 그 표준 입력, 표준 출력을 각각 부모 프로세스의 표준 출력, 표준 입력과 연결하고 있습니다. 이러한 시스템 호출과 동일한 이름의 Tcl 커맨드 덕분에 Perl이나 Python과 마찬가지로 저수준의 시스템 프로그래밍도 가능합니다.

DNS

Perl에는 gethostbyname이나 gethostbyaddr처럼 DNS(Domain Name Service)를 이용하여 호스트 이름이나 IP 주소로 도메인 정보를 가져오는 함수가 있는데, TclX에서도 커맨드로 제공하고 있습니다.

package require Tclx

while 1 {
    puts -nonewline ">"; flush stdout
    set a [gets stdin]
    if {[eof stdin]} break
    if {[regexp {^[[:digit:]]} $a]} {
        puts [host_info official_name $a]
    } else {
        puts [host_info addresses $a]
    }
}

이 스크립트는 nslookup 명령어와 같은 간단한 대화형 스크립트로, 입력된 호스트 이름과 IP 주소를 상호 변환하여 표시해 준다. IP 주소가 가리키는 서버 머신의 정식 호스트 이름을 알려면 아래와 같이 사용합니다.

host_info official_name

또한, 해당 머신이 가지고 있는 별칭 목록을 알기 위해서는 아래와 같이 사용합니다.

host_info aliases

반대로 호스트 이름으로 IP 주소를 구하려면 아래와 같이 사용합니다.

host_info addresses

 

작성일: 2025-02-12