'스크립트'에 해당되는 글 3건

  1. 2008.06.09 Tcl/Tk 문법 - 3부 Extensions
  2. 2008.06.09 Tcl/Tk 문법 - 2부 Tk
  3. 2008.06.09 Tcl/Tk 문법 - 1부 Tcl


Tcl/Tk 문법 - 3부 Extensions

Development 2008. 6. 9. 09:41
출처 : http://terzeron.net/wp/?p=336

1999/09/19

Tcl/Tk 문법설명 3부 확장

3부 Tcl/Tk Extensions

이번 회에는 Tcl/Tk를 확장하여 더 많은 기능을 제공하고 프로그래머들의 다양한 요구 사항을 만족시켜 줄 수 있는 Tcl/Tk의 확장된 형태의 언어(extension)와 Tcl/Tk의 응용 프로그램에 대해서 살펴보기로 한다. 대개의 Tcl/Tk Extension들은 Tcl을 기반 언어로 제공하고 있거나 다른 언어 쪽에서 Tcl의 기능을 추가시킨 것이 주를 이룬다.

다양한 Tcl/Tk 확장 언어들을 보면 Tcl/Tk가 얼마나 널리 사용되고 확장성이나 이식성이 뛰어난지를 알 수 있게 될 것이다. 그러나 모든 확장 언어들을 살펴 보기에는 지면이 허락하지 않으므로, 개발용으로 많은 기능이 s제공되고 발전 가능성이 있다고 생각되는 몇 가지의 extension을 골라서 그것들에 대해서 살펴 보기로 한다.

필자가 골라낸 확장 언어들은 Expect, TclX, SpecTcl, [incr Tcl]이 있고, Java와의 통합을 위한 Jacl과 TclBlend 등이 있다. 그 밖의 확장 언어들에 대해서 간단하게 살펴볼 것이다. 언어의 상세한 문법에 대해서는 다루지 않을 것이지만, 특징적인 기능에 대해 알아보고 사용 예에 대해서 들여다 보기로 한다.

Tcl/Tk의 응용 프로그램들은 각 분야별로 그 수가 엄청나기 때문에 하나씩 언급할 수조차 없다. 그러므로 특이할 만한 응용 프로그램을 필자가 임의로 몇 가지 골라서 그 특징적인 기능들에 대해서 살펴보기로 하겠다.

1장 Expect

Expect는 User-interactive한 Tcl 스크립트를 작성할 수 있는 확장된 형태의 언어이다. 'expect & interact'라고 표현하는 것이 Expect의 핵심이라고 할 수 있는데, 이것은 interactive한 프로그램으로부터 나오는 메시지나 event를 기다렸다가(expect), 사용자의 입력을 받거나 받아두었다가 그 프로그램에 응답을 하는(interact) 특징적인 기능을 단적으로 보여주는 것이다.

Expect는 http://expect.nist.gov에 서 구할 수 있다. Expect는 미국 NIST(National Institute of Standards and Technology)에서 지원하는 문자지향 응용프로그램을 자동화하는 사실상의(de facto) 표준 도구가 되었다. Tcl/Tk 프로그래머라면 한 번 쯤은 들어봤을 만한 유명한 Tcl Extension이라고 할 수 있다.

Expect는 telnet이나 ftp같은 네트웍 프로그램에 사용할 수도 있고, passwd나 fsck같은 unix 프로그램에도 사용할 수 있다. 환경변수를 비롯한 설정 등을 네트웍 프로그램이나 su 같은 명령이 수행될 때 프로그램을 뛰어넘어 가지고 갈 수 있다.

Expect의 주요 명령은 크게 5개가 있다. 그 밖에도 명령들이야 많이 존재하지만, 사용자와 expect 프로세스(process), 사용자가 수행하기를 원하는 프로세스의 3자간의 메시지 통신에 필요한 명령이 5가지인 것이다.

1) 메시지 통신 명령

우선 Expect 메시지 통신에 사용되는 명령은 모두 expect를 기준으로 이름이 붙여져 있다. expect 프로세스가 사용자로부터 메시지를 기다리는 것은 expect_user이고 사용자 프로세스로부터 메시지를 기다리는 것은 expect이다. expect 프로세스가 사용자에게 메시지를 보내는 것은 send_user이고, 사용자 프로세스에 메시지를 보내는 명령은 send이다. 나머지 하나의 메시지 통신 명령은 interact로서, 이 명령은 사용자의 키 입력을 expect가 곧장 사용자 프로세스로 보내는 것이다. [그림 1]은 이러한 메시지 통신의 메커니즘을 도식화한 것이다.


[그림 1]

telnet을 이용한 간단한 접속 프로그램을 작성해 보도록 한다.

#!/usr/local/bin/expect –
expect_user -re “(.*)

set login_id $expect_out(1,string)
spawn telnet localhost
expect “login: ”
send “$login_id

expect “Password: ”
send “dlelql.

interact

이 스크립트를 my_login이라는 이름으로 저장하고 실행 권한(permission)을 지정한 다음, 실행해보도록 하자.

vi my_login
chmod a+x my_login
rehash (Bourne shell계열의 shell에서는 hash -r)
./my_login

첫번째 줄의 expect_user명령은 위에서 설명한 대로, 사용자로부터 메시지를 기다리는 것이다. -re 옵션(option)은 정규 표현식(regular expression)을 의미하므로 newline을 제외한 모든 문자열을 expect_out이라는 변수에 저장하게 된다. 그 다음 줄에서 login_id라는 사용자 변수를 지정하면서 expect_out이라는 변수로부터 substring match된 첫번째 결과를 꺼내어 사용하게 된다.

세번째 줄에서는 telnet이라는 사용자 프로세스를 spawn하게 된다. spawn이란 Expect에서 사용하는 일종의 fork/exec 메커니즘이라고 볼 수 있다. 네번째 줄에서는 사용자 프로세스로부터 login: 이라는 메시지를 기다렸다가 다섯번째 줄에서 이미 지정된 사용자 변수의 값을 사용자 프로세스로 보내게 된다.

마지막 줄에서는 interact명령으로 사용자 프로세스에 대한 제어(control)을 사용자에게 넘겨주게 된다. 간단히 말해서, 사용자의 입력이 직접 사용자 프로세스로 넘어가게 될 것이다. 로그인(login) 과정이 성공적이었다면, 사용자의 telnet으로 localhost의 shell을 하나 더 띄워서 작업을 수행할 수 있게 되는 것이다.

e) expect

기본적인 제어 구문이나 프러시져(procedure) 구문 등은 Tcl의 문법을 따르고 있기 때문에 자세한 설명은 넘어가기로 하겠다. Expect의 명령들은 다양한 옵션과 유연한 문법 구조를 가지고 있기 때문에 위에 제시된 예제와 같은 딱딱한 문법이 아니라 보다 자연스러운(영어식 표현으로 자연스러운) 방식으로 프로그램을 작성할 수 있을 것이다. 다음 예제를 살펴보도록 한다.

expect { busy { puts busy; exp_continue } timeout my_proc1 connected my_proc2 "invalid password" my_proc3 }

expect 명령은 이와 같은 블럭(block)으로 구성할 수도 있는데, 이렇게 되면 각각의 상황에 따라 프러시져(또는 built-in 명령)를 수행하게 할 수 있다. busy한 경우에는 메시지를 출력하고 계속 expect 블럭을 재수행하게 되고, 다른 경우에는 각각 지정된 프러시져를 수행하게 된다. 일종의 분기 구문처럼 생각할 수 있다.

3) interact

interact {
  "/" {
    forward_search
    return
  }
  "?" {
    backward_search
    return
  }
  "Q" inter_return
}

interact 명령도 expect 명령처럼 블럭 구문으로 사용할 수 있는데, '/' 키를 입력하면 forward_search 프러시져를 수행하게 된다. “Q”를 누르면 interact 블럭을 빠져나오게 된다. inter_return은 interact 블럭을 빠져나오는데 반해, return은 블럭을 다시 수행한다는 차이점이 있다. 지정된 세 개의 키 이외의 입력이 들어오면 expect 프로세스가 가로채지 않고 사용자 프로세스로 직접 보내게 된다.

2장 TclX - Extended Tcl

필자는 개인적으로 TclX를 사용하는데, 바로 TclX를 이용한 MP3 연주기의 X 윈도우 인터페이스(interface)를 사용하기 때문이다. MP3 연주기로 유명한 mpg123은 텍스트 기반의 프로그램이기 때문에 X 윈도우 인터페이스는 tk3play라는 패키지가 따로 지원되어야 한다. 바로 이 tk3play는 TclX로 작성된 스크립트이므로, TclX 패키지가 필요한 것이다. tk3play는 [그림 2]와 같은 인터페이스를 보여준다.


[그림 2]

TclX는 Extended Tcl을 뜻하는 확장 언어이다. 이 확장 언어는 Tcl에서 사용하기가 불편하거나 어려웠던 배열(array), 파일(file), 소켓(socket), 파일 이벤트(file event), 날짜와 시간 처리 등에 관한 다양한 기능이 추가된 형태이다.

TclX는 ftp://ftp.neosoft.com/pub/tcl/TclX 에서 구할 수 있는데, 이 패키지를 컴파일(compile)하기 위해서는 Tcl/Tk 패키지도 함께 필요하다. 이것은 지난 1, 2회에 소개했던 대로 ftp://ftp.scriptics.com/pub/tcl 에서 구할 수 있다. 컴파일이 끝나면 wishx라는 이름(Tcl/Tk를 컴파일하면 wish가 생긴다는 것은 지난 회에 이미 살펴보았다.)으로 실행 프로그램을 얻을 수 있다.

애초부터 TclX는 Tcl과는 개발 방향이 달랐다. TclX는 시스템 프로그래밍 작업과 거대한 응용 프로그램을 개발하는데 사용된다. 일반적인 시스템 프로그램에 사용되는 언어들, 예를 들면, C/C++, Java같은 언어들이 제공하는 것처럼, 기반 운영 체제를 다루는 방법과 도구를 제공하며, Tcl보다 훨씬 강력한 디버깅(debugging) 기능을 가지고 있다. 확장된 기능에 대한 자세한 내용은 다음과 같다.

1) 프로세스 제어

TclX의 장점은 프로세스 제어 기능이 Tcl에 비해 상당히 정교하다는 것이다. fork와 execl 명령이 제공되며 system 명령도 이용할 수 있다. 파일 제어 기능에 있어서도 몇 가지 기능이 추가 되었는데, C 라이브러리 수준의 fcntl, flock/funlock, fstat, ftruncate, select등의 명령을 사용할 수 있다. 이진 탐색을 위한 bsearch 명령도 제공되고 있다.

2) 정규표현식

정규 표현식을 다루는 명령인 scan은 scancontext, scanfile, scanmatch등의 명령들이 추가적으로 제공되어 더 강력한 정규 표현력을 가지게 된 것이다. 이것은 정규 표현식을 다루는 기능이 훨씬 뛰어났던 Perl과 같은 기존의 강력한 언어들을 굳이 사용하지 않아도 Tcl 차원에서 작업을 원활히 할 수 있다는 것을 보여주는 것이다.

3) 수학함수와 리스트

수학함수나 리스트 관련 명령들도 추가되었는데, random이나 max, min같은 수학함수와 리스트를 집합처럼 사용할 수 있도록 union, intersect, intersect3같은 명령이 바로 그것이다. 이 밖에도 리스트를 다룰 수 있는 거의 모든 명령(lassign, lcontain, lempty, lmatch 등등)이 추가적으로 제공되고 있다.

4) Keyed List

eTclX에서는 'Keyed List'라는 자료형을 제공하는데, 이것은 C의 struct와 같은 자료형이라고 생각할 수 있다. key/value 쌍을 리스트의 하나의 필드(field)로 다룰 수 있는 것이다. 기존의 Tcl에서는 key/value는 해시(hash)형의 associative array에만 저장할 수 있었고, 필드에는 순서가 존재하지 않았는데 반해, TclX에서는 hash가 아닌 리스트에 저장할 수 있게 된 것이다. keylget, keylkeys, keyldel, keylset 등의 명령을 이용하면 리스트처럼 사용할 수 있는 것이다.

5) 문자열

문자와 문자열을 다루는 기능에서도 많은 명령이 추가되었다. ccollate, cconcate, cequal, cindex, clength, crange, csubstr, ctoken, ctype 등의 명령이 그것이다. C 라이브러리에서 문자를 다루는 isalpha(), islower() 등의 함수들과, 문자열을 다루는 함수들인 strcmp(), strcat(), strlen() 등의 함수를 TclX에 구현해놓은 것이라고 할 수 있다. 이 밖에도 개발을 위해 디버깅 관련 명령들(cmdtrace, profile 등등)이 제공되고 있다.

3장 [incr Tcl]

[incr Tcl]은 “인크리먼트 티클”이나 “잉커 티클”이라고 읽는다. [incr Tcl]을 짧게 줄여서 itcl이라고도 한다. itcl은 C++이 C를 객체지향 언어로 확장시킨 것처럼, Tcl을 객체지향 언어로 확장시킨 것이다. itcl에는 객체지향 언어가 가져야 할 특성인 객체(object)와 클래스(class), 상속(inheritance), namespace 등의 개념이 도입되었다. 뿐만 아니라, 단순한 위짓을 모아서 강력한 기능을 가지는 위짓 셋(set)을 제공하는데, 이것을 megawidget이라 한다.

[incr Tcl]은 http://www.tcltk.com/itcl 에서 구할 수 있으며, 현재는 Scriptics사의 TclPro와 Tcl/Tk Consortium의 Tcl Blast!의 일부로 패키징되고 있다.

1) Object와 Class

[incr Tcl]의 Object Oriented Programming 스타일은 C++이나 Java와 거의 비슷하다. 다만 거기서 사용하는 기호 대신에 예약어를 풀어쓰는 것이 다를 뿐이다. 다음의 예를 보자.

class Member {
  variable name
  variable address   constructor {myname myaddr} {
    set name myname
    set address myaddr
  }
  method show_addr {myname} {
    puts $address
  }
}



보다시피 C++이나 Java의 클래스 정의와 거의 비슷하다. 멤버 변수(member variable)를 선언할 때 명시적으로 variable이라는 예약어를 사용하고, 생성자와 소멸자를 지정할 때 constructor와 destructor를 선언한다. 멤버 함수(member function)를 위해 method라는 예약어가 사용된다. 멤버 변수나 멤버 함수를 사용하는 경우에는 다음과 같이 . 대신에 스페이스를 사용하는 것도 특징적이다.

Member member1 “Kim” “Seoul”
member1 show_addr

object를 제거하기 위해서는 delete object 명령을 사용할 수 있다.

delete object member1

2) Interface와 Implementation

class Tree {
  …
  method add {obj}
  method clear {}
}

body Tree::add {obj} {
  $obj parent $this
  lappend children $obj
}


이와 같은 형식으로 클래스 내부에서는 method를 정의하지 않고 interface만 선언했다가 클래스 외부에서 body를 정의할 수도 있다.

3) Protection

멤버 변수나 멤버 함수 선언을 할 때, public, private, protected의 modifier를 써서 접근을 제어할 수 있다. public은 외부에 완전히 공개하는 것이고, private은 현재 정의가 되고 있는 clas 내부에서만 사용할 수 있으며, protected는 상속받은 sub클래스에서도 사용할 수 있다는 의미로 쓰인다. 공통으로 사용하는 변수는 common으로 지정할 수 있다. 이것은 C++과 Java의 static과 같은 것이라고 볼 수 있다.

class Memeber {
  …
  private common club_name
  public proc list { }
}

이 예에서 club_name은 클래스에 하나만 존재하는 변수로 선언되고, list 함수는 method가 아니라는 사실에 주목해보자.

4) Inheritance

클래스를 선언할 때 inherit 명령을 사용하여 base class(super class)를 지정할 수 있는데, 여러 클래스를 함께 써서 다중 상속을 하는 것도 가능하다.

class FileTree {
  inherit Tree FileMenu
  …
}


FileTree라는 클래스는 Tree와 FileMenu라는 클래스를 상속받고 있다.

5) Namespace

namespace는 재사용 가능한 라이브러리를 패키징하기 위해서 사용되는 명령, 변수, 클래스의 집합이다. 다음의 예를 보면서 설명을 하도록 하자.

namespace filebrowser {
  variable roots
  proc load_dir {cwin dir {selcmd “”}} {
    global roots
    set roots($cwin) [create_node $cwin $selcmd $dir]
  }
  return $roots($cwin)
}

namespace는 이와 같은 형식으로 만들 수 있다. namespace 안에서 선언된 변수와 함수들은 name collision이 발생하는 것을 피할 수 있게 된다.

::filebrowser::load_dir .textwin /usr/local/lib

이런 형식으로 함수를 사용하게 되면, 다른 namespace나 global 영역(scope)에서 사용되는 같은 이름의 함수와 충돌이 일어나지 않는 것이다. 다만 import 명령으로 namespace를 import하게 되면 굳이 ::로 시작하는 절대경로를 지정할 필요는 없다.

import filebrowser
load_dir .textwin /usr/local/lib

6) [incr Tk]와 [incr Widget]

위짓을 다루는 데 있어서도 객체 지향적인 방법이 사용되었는데, 이러한 부분을 [incr Tk]가 담당하고 기본적인 Tk의 위짓을 확장시킨 위짓 집합을 [incr Widget]이라고 한다.

[incr Widget]의 모든 위짓은 itk::Archetype으로부터 상속을 받아서 기능을 확장시킨 클래스라고 생각할 수 있다. itk::Archetype은 다시 두 가지의 위짓 클래스로 분류되는데, 하나는 itk::Widget이고, 또 하나는 itk::Toplevel이다. 모든 위짓은 두 위짓을 상속받아서 새로 만들어낼 수 있다. 전자는 기존의 윈도우에 포함되어 생성이 되지만, 후자는 또 하나의 새로운 윈도우를 만들어 내며 생성이 된다. Combobox가 itk::Widget이라면 Messagedialog는 itk::Toplevel인 것이다.


[그림 3]

spinint .temp -labelpos w -labeltext “Water Temperature:” -fixed 5 -width 5 -range {32 212} -step 2 -wrap yes -orient horizontal
pack .temp -padx 10 -pady 10

다음은 Spinint라는 위짓을 만들어 가는 과정을 간단하게 살펴보기로 한다. 그러나 Spinint 위짓은 이미 만들어져서 제공되고 있기 때문에 다음의 과정을 답보할 필요는 없다. 위와 같이 하나의 위짓을 사용하듯이 선언하고 pack하기만 하면 된다.

class Spinint {
inherit itk::Widget
constructor {args} {

eval itk_initialize $args
}
public method clear {}

}

Spinint라는 새로운 위짓을 만들 때, 가장 먼저 하는 작업은 itk::Widget을 상속받는 것이다. 그리고 생성자에서 argument로 넘겨받은 옵션을 처리한다. 나머지는 위짓을 다루는 method를 정의하면 된다.

위짓의 인스턴스(instance)가 하나 만들어질 때, 생성자가 호출되면 우선은 itk::Archetype의 생성자가 component list와 master option list를 만들게 된다. 다음은 itk::Widget의 생성자가 일종의 container인 hull이라는 컴퍼넌트(component)를 만든다. 이 hull이라는 컴퍼넌트는 화면에 나타나지는 않지만, 이후에 추가될 컴퍼넌트들을 포함하게 되는 가장 기본적인 컴퍼넌트이다.

다음은 Spinint 위짓 클래스의 생성자 내부에서 label 위짓 클래스의 인스턴스인 lab과 entry 위짓 클래스의 인스턴스인 ent를 컴퍼넌트로 등록하는 예제이다.

itk_component add label {
label $itk_interior.lab
} {
rename -background -textbackground textBackground Background
keep -background
keep -foreground
keep -cursor
}

이 예제는 lab이라는 위짓에서 사용하는 배경색, 전경색 등의 옵션을 master option list에 등록하는 것이다. -background 옵션은 -textbackground 옵션으로 이름을 바꾸어 충돌이 발생하지 않도록 하고 있다. master option list에 옵션이 등록되면, Spinint 위짓을 configure할 때, 그 영향이 컴퍼넌트인 lab 위짓까지 미치게 되는 것이다.

itk_component add entry {
entry $itk_interior.ent
} {
usual
ignore -foreground
}

ent 위짓은 usual이라는 명령으로 entry 위짓 클래스의 일반적인 옵션을 모두 master option list에 등록하고 ignore 명령으로 전경색 옵션을 제거하고 있다.

[incr Widget]에서 이미 만들어서 제공하고 있는 위짓 집합에는 Buttonbox, Canvasprintdialog, Combobox, Dialogshell, Dialog, Entryfield, Fileselectionbox,
Fileselectiondialog, Labeledwidget, Messagedialog, Optionmenu, Panedwindow, Promptdialog, Pushbutton, Radiobox, Scrolledcanvas, Scrolledframe, Scrolledlistbox, Scrolledtext, Selectionbox, Selectiondialog, Spindate, Spinint, Spinner, Spintime, Tabnotebook, Toolbar 등이 있다.

지난 Tk 강좌에서 텍스트 창에 스크롤바(scrollbar)를 붙이는 작업을 해보았는데, 그다지 쉬운 일은 아니었다. 이제는 [incr Widget]에서 제공하는 scrolledtext 위짓을 사용하여 간단하게 원하는 작업을 할 수 있다.

scrolledtext .st -labelpos n -labeltext “/etc/passwd” -vscrollmode static -hscrollmode static
pack .st -padx 10 -pady 10 -fill both -expand yes
.st import /etc/passwd


[그림 4]

[incr Tcl]을 사용하게 되면 다양한 위짓 집합과 상속이라는 기능을 가지게 되므로 위짓을 새로 만들고 다루는 일은 훨씬 쉬운 일이 되었다. 이제 더 정교하고 강력한 위짓을 만들기 위해 프로그래머가 할 일은 기존의 위짓들을 잘 배치하는 것 뿐이다.

4장 BLT

BLT는 Tk에 새로운 위짓과 geometry 관리자의 기능을 추가한 것이다. 새로 추가된 위짓으로는 테이블이나 그래프, 곡선, 벡터 등이 있다. BLT는 http://www.tcltk.com/blt 에서 구할 수 있다. 몇 가지 위짓의 예를 살펴보기로 하자. BLT를 설치하여 bltwish를 실행시키면 바로 BLT의 위짓을 사용할 수 있는 것은 아니다.

package require BLT
if { $tcl_version >= 8.0 } {
namespace import blt::*
namespace import -force blt::tile::*
}

패키지로 BLT를 사용하겠다고 선언한 후, namespace를 import하여 blt 관련 위짓을 상대 경로로 이름을 사용하겠다는 것을 지정해야 한다.(namespace는 [incr Tcl]을 참고할 것) 이제부터는 테이블이면 table, 그래프면 graph라고 쓸 수 있다.

다음은 하나의 barchart 예제가 보여줄 수 있는 여러가지 결과의 screenshot이다.


[그림 5]

[그림 6]

[그림 7]

[그림 8]

이 밖에도 차트를 zoom in/out하거나 차트의 방향(orientation)을 바꾸고, 축(axis)을 조정하는 기능까지도 사용할 수 있다. barchart와 비슷한 기능을 하는 위짓에는 graph가 있다. graph는 barchart의 많은 기능 뿐만 아니라, 그래프 상에 찍는 점 대신에 이미지를 사용할 수도 있다. 다음은 그래프 위짓의 예이다.


[그림 9]

5장 Java & Tcl

Java와 Tcl은 개발의 취지조차 아예 다른, 성격이 상이한 언어들이다. Java는 이식성이 뛰어난 강력한 3세대 언어로서 복잡한 응용 프로그램을 개발하는데 사용되고 있고, Tcl은 다른 응용 프로그램에 내장되거나(embedded), 스크립트(script) 언어로서 간단하고 쉽게 사용되고 있다. 그러나 각각의 장점을 취합하여 확장시키려는 시도가 있었고, 그 결과물이 바로 Jacl과 Tcl Blend이다.

우선 Java 쪽에서 Tcl을 이용하는 면을 생각해본다면, Java는 시스템 프로그래밍에 적합하고, 복잡한 자료 구조와 강력한 타입 체킹(type checking)를 하며, 거대한 시스템을 구축하는데 사용될 수 있다. 그러나 자료구조와 algorithm을 표현하고 다루는 것이 복잡하므로 이런 점에서는 스크립트 언어를 사용하는 것이 더 유리하다. 반대로, Tcl은 빠른 개발에 유리하고 컴퍼넌트간의 연결이 간단하다. 게다가 library형태로 제공되므로 다른 언어에 내장될(embedded) 수 있다.

여기에서 착안하여 Java에 Tcl의 scripting 능력을 추가하여 Java를 VBA(Visual Basic for Application)처럼 사용할 수 있는 것이다. 다시 말하면, VBA가 그러하듯이 응용 프로그램들 위에서 그것들을 연결시키는 기능을 Java가 수행할 수 있게 하는 것에 Tcl을 이용하는 것이다.

다음으로 Tcl쪽에서 Java를 이용하는 면을 고려한다면, 물론 Tcl도 이식성이 뛰어난 언어이긴 하지만, Java로 작성됨으로써 아주 다양한 시스템(Unix가 아닌)에까지 널리 이식될 수 있을 것이다. 이것이 바로 Jacl로서, Java 언어를 이용하여 Tcl을 구현한 것이라고 볼 수 있다.

Tcl Blend는 C로 작성된 Tcl 해석기가 JVM(Java Virtual Machine)을 load/interact할 수 있도록 만든 프로그램으로 C로 작성되어 있는 코드들을 조금씩(incrementally) Java 코드로 옮겨서 이식성을 높이는데 사용할 수 있다.

Jacl과 Tcl Blend이라는 Java Package는 Java의 reflection API를 이용하여 Java의 class를 Tcl에서 사용할 수 있을 뿐만 아니라, JavaBean를 스크립트에서 쉽게 다룰 수 있으므로 Java의 강력한 기능까지도 Tcl에서 아주 쉽게 사용할 수 있다는 장점을 가지게 된다. 새로운 컴퍼넌트를 작성하기 위해 Java Studio를 이용할 수도 있다. 이러한 특징들은 Tcl을 더욱 강력하게 만드는 무기가 될 것이 분명하다.

다음의 예제 코드를 참조하도록 하자. 낯익은 Java의 method들이 Tcl에서 사용되고 있지 않은가!

set f [java::new java.awt.Frame]
$f setTitle “Hello World”
set text [java::new java.awt.TextArea]
$text setText “Simple example”
$f {add java.awt.Component} $text
$f setLocation 100 100
$f pack
$f toFront

이 예제는 Java AWT의 Frame을 생성하고 그 위에 TextArea를 붙여서 그 컴퍼넌트들을 다루는 작업을 Tcl로 수행하고 있는 것이다. 다음의 예제는 Java Beans와 관련하여 callback 함수를 Beans에 등록하는 작업을 간단하게 수행하는 것이다.

set f [java::new java.awt.Frame]
set b [java:: new java.awt.Button "Exit"]
java::bind $b actionPerformed {set done yes}
$f {add java.awt.Component} $b
$f setLocation 100 100
$f pack
$f toFront
set done “no”
vwait done
$f dispose

Java에서 callback 함수를 등록하려면, 등록하기 전에 객체 인터페이스의 정확한 타입을 가져야 하므로 두 객체를 붙이기 위해 새로운 클래스(adaptor class)를 작성해야 하는 부담이 있으나, Tcl에서는 java:bind 명령만을 사용하여 아주 간단하게 callback 함수를 등록하고 있다.

Jacl과 Tcl Blend는 http://www.scriptics.com/java 에서 구할 수 있다.

6장 Tcl/Tk 응용 프로그램

1) Comanche

Comanche는 Apache GUI 프로젝트의 일환으로 추진되는 프로젝트인데, Tcl/Tk를 기반으로 하여 Apache 웹 서버(web server) 설정을 할 수 있도록하는 X 윈도우 인터페이스를 제공하는 프로젝트의 이름이다. 텍스트로 작성된 설정 파일을 사용자가 직접 고치는 부담을 덜어주려는 의도에서 출발한 이 프로젝트는 최근에도(99년 4월) 계속 새로운 버전의 패키지를 발표하고 있다. 프로그램의 인터페이스는 [그림 10 - 13]과 같다.


[그림 10]

[그림 11]

[그림 12]

[그림 13]

Comanche는 Apache 웹 서버의 설정을 윈도우 상에서 할 수 있도록 여러 위짓(widget)을 이용하고 있는데, Tcl/Tk를 기반으로 했기 때문에, 단순히 Unix의 X 윈도우 시스템 뿐만 아니라 MS 윈도우 시스템에도 이식(porting)이 어렵지 않다. 이러한 이식성 때문에 사용자들이 쉽게 강력한 기능을 가진 Apache 웹 서버를 시스템에
구애받지 않고 사용할 수 있게 되는 것이다.

Comanche는 Configuration Manager for Apache의 줄임말로서, 미국 서부에 거주하는 토착민(인디언)의 한 부족의 이름이기도 하다. Apache도 마찬가지이다. Comanche 홈페이지에서 미국 내 인디언 부족들에 관련된 정보를 제공하는 사이트에 연결된 링크를 볼 수 있다.

http://comanche.com.dtu.dk/comanche 에서 더욱 자세한 내용을 살펴볼 수 있을 것이다.

2) Tcl Debugger

Tcl Debugger에는 여러 가지가 있다. TclPro 패키지에 들어가는 TclPro Debugger는 거의 DDD(Data Display Debugger)의 수준까지 제공을 하고 있으나, Freeware가 아닌 관계로 여기서 다루지는 않도록 하겠다.

1) Expect Debugger

텍스트 환경에서 디버깅을 하려면, Expect를 사용하는 것이 도움이 된다. Expect는 Debugger를 포함하고 있는 관계로 -D 옵션을 사용하여 에러 발생시 내장 디버거가 작동하도록 할 수 있다.

% expect -D 1
expect1.1> set 1 hello
1: history add {set 1 hello
}
dbg1.0> w
*0: expect {-D} {1}
1: unknown {history} {add} {set 1 hello
}

Expect debugger의 명령들은 다음 표와 같다.

이름 설명
s step into procedure : 프러시져 안으로 컨트롤을 옮긴다.
n step over procedure : 프러시져를 건너 뛴다.
r return from procedure : 프러시져에서 빠져나온다.
b set, clear, or show /td : 중지점
c continue : 수행을 계속한다.
w show stack : 스택을 조사한다.
u move scope up : 스택의 scope에서 위쪽으로 컨트롤을 옮긴다.
d move scope down : 스택의 scope에서 아래쪽으로 컨트롤을 옮긴다.
h help : 자세한 옵션 설명을 볼 수 있다.

Expect debugger를 사용하는 자세한 방법은 생략하도록 하겠다. GNU Debugger에 어느 정도 익숙한 개발자라면 충분히 이해할 수 있을 만큼 간단한 기능으로 국한되어 있기 때문이다.

2) Tcl Debuggger

윈도우 인터페이스를 가지고 있는 디버거도 있는데, TDB(Tcl Debugger)이다. http://members.ping.at/risc/tdb.html 에서 구할 수 있다.

TDB는 TdChoose.tcl이라는 윈도우 인터페이스와 TdDebug.tcl이라는 디버거로 구성된 2개의 Tcl/Tk 스크립트인데, wish로 읽어들여서 수행가능하다.

wish TdChoose.tcl ~/bin/mpgplay.tcl

~/bin/mpgplay.tcl이라는 Tcl/Tk 스크립트를 Debugging할 때 위와 같은 방법으로 Tcl Debugger를 사용할 수 있다.


[그림 14]

[그림 15]

[그림 14]처럼 메인 화면에 뜨고, 프러시져 내부로 들어가면 [그림 15]와 같은 윈도우가 떠서 그 안에서 중지점(breakpoint)을 잡아서 디버깅을 수행할 수 있다.

3) SpecTcl

SpecTcl은 Tcl/Tk 프로그램의 GUI(Graphical User Interface)를 제작하는 것을 돕는 개발 환경을 제공하는 프로그램이다. Grid Geometry Manager를 사용하여 Tcl/Tk 응용 프로그램의 개발 환경을 제공하는데, constraint-based이기 때문에 하나의 위짓을 Grid 안에 위치한 하나의 컬럼으로 다루게 되어 매우 간단하게 사용할 수 있다.

Tcl/Tk 응용 프로그램에서 GUI를 만들게 되면 대부분의 시간을 packing하고 layout을 잡는데 허비하게 되는데, SpecTcl을 이용하게 되면, Grid안에 위짓을 하나씩 위치시킬 수 있으므로, 위짓들의 geometry를 관리하는 아주 번거로운 작업들을 자동화하고 일관성있게 조작할 수 있다는 장점이 있다.

SpecTcl은 http://www.scriptics.com/spectcl 에서 구할 수 있다.

SpecTcl은 Tcl/Tk 응용프로그램 뿐만 아니라, Java, Perl등의 X 윈도우 응용 프로그램을 개발할 수 있다. SpecTcl을 실행시키면 Target 언어를 선택하라는 메뉴가 뜨고, 그에 따라 각각의 언어로 된 응용 프로그램의 GUI 개발 환경이 실행된다.

SpecTcl을 실행하여 Java를 선택해본다.


[그림 16]

[그림 17]

[그림 16]과 같은 개발 환경이 화면에 나타난다. 여기서 왼쪽 툴바(toolbar)에 있는 버튼을 드래그(drag)하여 중앙 화면에 있는 Grid 안의 임의의 컬럼으로 옮긴다. 그러면 버튼이 생성될 것이다. [그림 17]과 같이 배치될 것이다.

버튼 위짓 위에 커서를 위치시키고 더블 클릭하여 속성 창을 띄워보자. [그림 18]과 같은 속성 창이 뜰 것이다. 속성 창에서 action을 test2();라고 지정한다. 버튼을 클릭하면 test2()라는 callback 함수를 호출하겠다는 의미이다. 그리고 Edit 메뉴에서 Edit Code를 선택하여 test2() 함수를 정의한다. [그림 19]는 함수를 정의하는 편집기 역할을 하는 창이다.

public int test2() {
System.out.println(”Hello”);
return 0;
}


[그림 18]

[그림 19]

File 메뉴에서 Save를 하고, Commands 메뉴에서 Build를 하면 Java 프로그램이 저장된다. test1이라는 프로그램으로 저장했다고 하면, test1.java가 생성되었을 것이다. 이제 Java 소스 프로그램을 컴파일하고 실행시켜보자.

javac test1.java
java test1


[그림 20]

[그림 20]와 같이 아주 간단한 프로그램이 실행되었다. 버튼을 눌러 “Hello”라는 메시지가 나오는 것을 확인하도록 하자.

SpecTcl의 수많은 기능들은 스스로 코드를 작성하면서 직접 확인해 볼 수 있을 것이다. 현재는 SpecTcl과 SpecJava 뿐만 아니라, SpecPerl까지 지원되고 있다. SpecTcl은 free로 배포되지만 개발이 중단되었고, SpecJava또한 아직까지는 JDK 1.0의 코드만을 생성할 따름이다. SpecPerl을 조금 변형시켜서 SpecPython이 개발되기도 하였다.

4) MS-Windows용 Tcl/Tk

COSH는 윈도우즈용 wish이다. http://home.t-online.de/home/dshepherd/cosh.htm 에서 구할 수 있다.

Active Tcl은 Tcl 8.0을 위한 ActiveX의 wrapper이다. http://www.bgi-sa.com/tcl-en.htm 에서 구할 수 있다.

http://qwerty.maxwell.syr.edu/nt-tcl 에는 NT 관리자에게 유용한 Tcl 스크립트를 제작할 수 있는 프로그램을 제공하고 있다.

이 밖에도 COM이나 OLE를 다루는 Tcl 확장 언어들이 있다. 관련 패키지들은 http://www.scriptics.com/resource/software/extensions/windows 에서 확인할 수 있을 것이다.

서울대학교 전산과학과 데이타베이스연구실 조영일

:

Tcl/Tk 문법 - 2부 Tk

Development 2008. 6. 9. 09:39
출처 : http://terzeron.net/wp/?p=245

1999/07/16

1장 소개

1) X Window System과 Tk의 소개

X 윈도우 시스템은 MIT와 DEC가 공동으로 개발한, 하드웨어와 OS에 독립적인 윈도우 시스템이다. 하드웨어와 OS에 독립적일 수 있는 이유는, X 클라이언트와 X 서버로 각각의 기능이 분리되어 있기 때문이다. X 서버는 장치구동기(device driver)를 통해 하드웨어와 OS를 제어하고, X 클라이언트는 X 프로토콜을 이용해 X 서버에게 윈도우 작업을 요청하게 되는 것이다. X 프로토콜은 사람이 이해하기 어렵기 때문에, Xlib을 이용하여 C로 X 윈도우 프로그래밍을 할 수 있도록 지원하고 있다.

그러나 Xlib조차 프로그래밍하기가 쉽지 않다. 아주 간단한 윈도우를 하나 만드려고 해도 프로그래머가 지정해야 할 것들이 너무 많기 때문이다. 그래서 이미 Xlib 수준에서 구현된 위짓(widget)을 재사용하게 된다. 이것을 바로 툴킷(Toolkit)이라고 한다. 툴킷에도 여러 단계가 있어서 가장 하위 레벨은 X Toolkit Intrinsics이다. 이것은 여러 X 응용프로그램에서 자주 사용되는, 아주 핵심적인 위짓을 미리 만들어놓은-정의해놓은-라이브러리라고 할 수 있다. Xt라고 불리우는 X Toolkit Intrinsics 이외에, 좀 더 세련된 위짓들의 집합을 제공하는 것으로는 Motif와 Qt, Gtk 등이 있다.

Tk는 일반적인 뜻으로는 툴킷(Toolkit)을 의미한다. 그러나 여기서 말하는 Tcl/Tk는 Tcl에서 사용하는 툴킷으로 그 의미가 한정된다. 우리가 이제부터 살펴볼 것은 Tcl에서 윈도우를 어떻게 생성하고 다룰 것인가에 관한 것이다. Tk를 중심으로 Tcl/Tk를 익혀보기 위해서는 wish(windowing shell)가 필요하다. 1부에서 밝힌대로 Tcl/Tk를 설치했다면 wish가 존재할 것이다. /usr/X11R6/bin이나 /usr/openwin/bin 등의 X 응용프로그램 디렉토리에서 찾아서 실행해보자. 제대로 설치가 되어 있다면 다음 명령을 수행했을 때, 버튼 위에 쓰여진 환영 인사를 만날 수 있을 것이다.


button .b -text "Hello, world!"
pack .b

pack은 사용자가 만든 위짓 인스턴스를 화면에 디스플레이하는 명령이다.

2) X resource

X 윈도우 시스템에서 폰트나 색상, 사이즈와 같이 위짓의 속성을 바꿀 수 있는 변수들을 X resource라고 한다. 리소스(resource)는 X 클라이언트가 요청하면 X 서버가 할당해주는 형태로 되어 있다. X 윈도우 시스템에는 여러가지 위짓이 존재하고, 각각의 위짓은 또 다양한 리소스를 필요로 하게 되므로 상당히 많은 종류의 리소스가 존재한다. 그러나 여기서는 Tcl/Tk에서 중요하게 다루는 몇 가지 리소스에 대해 짧게 살펴보고 지나갈 것이다. X 윈도우 프로그래밍에 관심있는 독자라면 Xlib 프로그래밍이나 X 툴킷 프로그래밍에 관련된 책을 읽어보는 것이 좋겠다.

우선 가장 많이 사용되는 것은 색상이다. 아주 간단한 RGB방식으로 빨간색, 파란색, 녹색을 1 바이트(0에서 255까지)씩 빛을 혼합하는 것이다. 예를 들면 흰색은 #ffffff이고, 검정색은 #000000이다. 빨간색은 #ff0000이고, 녹색은 #0000ff이다. 보라색은 #ffff00이 되겠다. 그러나 프로그래머가 매번 광원의 색을 혼합하여 색상을 만들어내는 것은 번거로운 일이 될 것이다. X 윈도우 시스템에서는 showrgb라는 명령으로 이미 지정되어 있는 색상의 이름을 확인할 수 있다. k에서는 이렇게 정의되어 있는 색상의 이름을 그대로 사용할 수 있다.

다음으로 많이 사용하는 리소스는 폰트이다. 폰트는 XLFD(X Logical Font Description)을 따라서 그 이름을 지정하면 되는데, 복잡한 설명보다는 xlsfonts를 이용하여 폰트 리스트에서 마음에 드는 폰트명을 골라서 사용하면 된다. 그러나 폰트명만 가지고는 폰트의 생김새를 알 수 없으므로, xfontsel이라는 프로그램으로 각각의 폰트가 어떻게 생겼는지 확인하는 것이 좋겠다. 어떻게 사용하는지는 차차 살펴보기로 하자.

2장 Tk Widget

Tk 위짓은 Motif의 스타일을 제공하는 위짓이다. 각각의 위짓은 클래스(class)와 인스턴스(instance)로 그 개념을 나누어 볼 수 있다. 클래스는 특정 위짓의 종류를 지칭하고, 인스턴스는 그 위짓 클래스에 속하는 위짓을 하나 만들어 이름을 붙여준 것을 가리킨다. 위짓 인스턴스는 계층을 이루어서 하나의 윈도우를 구성하게 된다. Xterm을 예로 보면, Xterm은 왼쪽이나 오른쪽에 스크롤바(scrollbar)를 가지고 있고, 나머지 부분에 텍스트입력창이 존재한다. 이런 경우에는 최상위의 윈도우 아래에 스크롤바와 텍스트입력창이 존재하는 계층구조라고 할 수 있을 것이다. 메뉴(menu)를 가지고 있는 X 응용프로그램이라면 메뉴 안에 메뉴 아이템이 존재하고, 그 아이템이 다시 메시지창을 가진다든가 해서 아주 복잡한 계층구조를 형성할 것이다.

Tk에서 제공하는 위짓에는 프레임(frame), 레이블(label), 버튼(button), 체크버튼(checkbutton), 라디오버튼(radiobutton), 메시지(message), 리스트박스(listbox), 스크롤바(scrollbar), 스케일(scale), 엔트리(entry), 메뉴(menu), 메뉴버튼(menubutton) 등이 있다.

특별한 위짓으로는 top-level 위짓이 있는데, 이것은 “.”(dot)으로 표현된다. “.”을 쓰면 최상위의 위짓이고, “.a”라 하면 top-level 위짓 바로 아래 레벨에 위치하는 위짓 인스턴스 a가 되는 것이다.

1) 프레임(frame)

프레임은 위짓을 그룹으로 묶을 때 사용하는 위짓이다. 나중에 살펴 볼 pack 명령만으로는 위짓들을 관리하기가 어려워서 프레임을 이용하여 위짓을 그룹단위로 사용하게 된다. 프레임은 -relief 옵션을 주어 5가지의 형태로 만들 수 있는데, 다음의 예는 각각의 형태를 한 윈도우에 모아서 보여주는 것이다.


foreach re { raised sunken flat groove ridge } {
frame .$re -width 15m -height 10m -relief $re -bg gray40 -borderwidth 4
pack .$re -side left -padx 2m -pady 2m
}

[그림 1]

2) 버튼(button)

버튼은 마우스 버튼을 누르면 움푹하게 들어가고, 손가락을 떼면 다시 올라오는 위짓이다. 가장 흔히 만날 수 있는 위짓이 바로 버튼이다. 버튼도 프레임과 마찬가지로 -relief 옵션을 이용하여 5가지의 형태로 만들 수 있다.

다음은 버튼 2개를 하나의 프레임 안에 넣는 예이다. “.f.b1″ 또는 “.f.b2″는 f라는 프레임 인스턴스 아래에 두 개의 버튼 인스턴스가 존재한다는 의미이다. 물론 위에서 설명한 바와 같이, 프레임 f는 top-level 위짓 바로 아래 레벨에 위치하는 프레엠 위짓 클래스의 f라는 이름을 가진 인스턴스가 되는 것이다.


frame .f
button .f.b1 -text button1
button .f.b2 -text button2
pack .f.b1 .f.b2

아무 것도 나오지 않는다. 이상할 것은 없다. 바로 프레임 f를 pack하지 않았기 때문이다. pack에 대해서는 뒤에서 자세히 다루어 보도록 한다. 다음 명령을 추가하면 버튼 2개가 나타날 것이다.


pack .f

3) 체크버튼(checkbutton)과 라디오버튼(radiobutton), 리스트박스(listbox)

체크버튼은 여러 개의 버튼을 눌러서 동시에 여러가지를 선택할 수 있게 하는 버튼이다. 반면에 라디오버튼은 라디오에서 채널을 맞추듯 한 번에 단 하나만을 선택할 수 있게 하는 버튼이다. 체크버튼과 라디오버튼은 그 버튼이 체크되는 순간에 특정 변수에 특정 값을 대입할 수 있도록 -variable, -value 옵션을 제공한다.(-value 옵션은 라디오버튼에서만 사용된다.) 리스트박스는 체크버튼을 확장해 놓은 것으로 생각할 수 있는 위짓으로 하나 이상의 아이템을 선택할 수 있다.


checkbutton .red -text Red -variable color_red
checkbutton .green -text Green -variable color_green
checkbutton .blue -text Blue -variable color_blue
pack .red .green .blue -side top -fill x

radiobutton .red -text Red -variable color -value red
radiobutton .green -text Green -variable color -value green
radiobutton .blue -text Blue -variable color -value blue
pack .red .green .blue -side top -fill x<
[그림 2]
[그림 3]

위의 두 예제는 체크버튼과 라디오버튼이 어떻게 다른가를 알려주는 예이다. 체크버튼의 경우에, 몇 개를 선택하고서 puts $color_red $color_green $color_blue를 실행하여 각각의 값을 확인해 보자. 체크되어 있는 색상의 변수에는 이, 아닌 경우에는 0이 대입되어 있는 것을 알 수 있다. 라디오버튼의 예제는 색상을 선택한 후에, puts $color 해 보면 값이 설정되어 있는 것을 확인할 수 있다.


listbox .name -selectmode browse
pack .name
set f [open /etc/passwd]
while {[gets $f line] >= 0} {
set item [lindex [split $line :] 0]
.name insert end “$item”
}
close $f

[그림 4]

이 예제를 -selectmode 옵션의 값을 single, multiple, extended로 바꾸어가며 테스트해보자. 그러면 마우스의 바인딩(binding)이 어떻게 바뀌는지 알 수 있다. 예를 들면, extended로 바꾸어 놓고 실행하면 마우스의 첫번째 버튼을 누른 채로 드래그할 때 해당 아이템들 모두를 선택할 수 있다.

4) 메시지(message)

메시지는 길이가 긴 텍스트를 보여주는 메시지 창을 의미한다. 다음의 예는 오른쪽 정렬로 텍스트를 보여준다. 폰트를 지정하고, 배경색과 전경색을 지정하는 방법을 눈여겨 보아두자.


message .m -justify right -font "-*-helvetica-*-*-*--*-120-*" -bg white -fg black -text "Message widgets are similar to labels except that they display multiline strings."
pack .m

[그림 5]

5) 스크롤바(scrollbar) & 스케일(scale)

스크롤바는 대개 수직으로 세워져 있는 위짓인 반면에, 스케일은 수평으로 눕혀져 있는 위짓이다. 물론 각각의 위짓은 옵션을 이용하여 그 모양을 반대로 바꿀 수 있다. 스크롤바는 많은 아이템을 검색할 때 사용되고, 스케일은 슬라이더(slider)를 이용하여 정수값을 변화시킬 때 사용된다.


listbox .li -yscrollcommand ".sc set"
scrollbar .sc -command ".li yview"
pack .li -side left
pack .sc -side right -fill y
foreach it [lsort [glob *]] {
.li insert end $it
}

[그림 6]

스크롤바를 상하로 움직이면 리스트박스 인스턴스인 .li에 yview명령을 수행하게 되어 리스트박스가 상하로 움직이게 되는 것이다.

scale .percent -from 0 -to 100 -orient horizontal -command showvalue
pack .percent
proc showvalue parameter {
puts [.percent get]
}

[그림 7]

이 예는 슬라이더를 움직이면 그 해당 정수값을 출력하는 것이다. horizontal을 vertical로 바꾸면 슬라이더가 수직으로 나타날 것이다. 스케일에 -command 옵션으로 값에 변화가 있을 때마다 프러시져를 호출할 수 있다. 잘 살펴보면 프러시져 안에서 스케일의 현재 값을 얻기 위해 .percent get하고 있음을 알 수 있다.

6) 엔트리(entry)

엔트리는 텍스트 입력창이다. 엔트리에 텍스트를 입력한 후에 puts $var로 변수의 값을 확인해보자.


entry .e -relief groove -textvariable var
pack .e

7) 메뉴(menu)와 메뉴버튼(menubutton)

메뉴에는 체크버튼, 라디오버튼, 메뉴버튼, 명령(command), 구분선(separator) 등을 아이템으로 가질 수 있다. 체크버튼과 라디오버튼은 위에서 설명한 바와 같은 방식으로 작동하고, 명령은 프러시져를 호출하기 위해 사용하며, 구분선은 아이템을 시각적으로 그룹화하기 위해 사용된다.


menubutton .mb -text "Menu" -menu .mb.m
pack .mb
menu .mb.m
.mb.m add radiobutton -label Red -variable red
.mb.m add radiobutton -label Blue -variable blue
.mb.m add separator
.mb.m add command -label "Hello" -command hello
proc hello {} {
puts "Hello, world!"
}

다만 메뉴 자체는 pack 명령을 사용하지 않는다. 메뉴가 속해있는 다른 위짓이 그 작업을 대신 수행해준다. 여기서는 메뉴버튼을 pack해서 화면에 디스플레이하고, menu인 .m을 연결시킨다.

메뉴의 한 항목이 다시 메뉴를 부르는 것을 캐스케이딩 메뉴(cascading menu)라고 하는데, 이것은 add하는 항목에 cascade라고 쓰고 -menu option을 이용하여 새로운 메뉴를 연결시키면 가능하다.


.mb.m add cascade -labe "Another menu" -menu .mb.cm
menu .mb.cm
.mb.cm add radiobutton -label Green -variable green

메뉴바를 만들고 거기서 다시 서브메뉴를 만드는 경우가 있는데, 이런 경우에는 우선 메뉴바를 하나 만들고, 메뉴바에 메뉴버튼을 몇 개 붙인 후, 각각의 버튼에 메뉴를 연결하는 것이다. 메뉴바는 메뉴버튼을 그룹으로 만드는 것이므로 프레임으로 선언한다.


frame .menubar -relief raised
menubutton .menubar.file -text File -menu .menubar.file.menu
menubutton .menubar.edit -text Edit -menu .menubar.edit.menu
pack .menubar -fill x -expand true
pack .menubar.file .menubar.edit -side left

menu .menubar.file.menu
.menubar.file.menu add command -label “Open”
.menubar.file.menu add command -label “Save”
.menubar.file.menu add command -label “Exit”
File과 Edit 메뉴인 .menubar.file.menu와 .menubar.edit.menu는 위에서 설명했던 대로 사용자가 만들면 된다. 중요한 것은 메뉴는 pack하지 않고, 메뉴를 연결하는 위짓(여기서는 메뉴바와 메뉴버튼)은 반드시 pack해야 한다는 것이다. 메뉴의 단축키를 지정하거나 특정 문자에 밑줄을 긋기 위해서는 -accelerator와 -underline 옵션을 사용하면 된다.


.menubar.file.menu add command -label "Open" -underline 0 -accelerator "Ctrl+O" -command open
proc open {} {
puts "open"
}

이 예에서 -underline 옵션의 값이 0인데, 이것은 Open이라는 label의 첫글자 O에 밑줄을 긋겠다는 의미이다. 단축키는 Ctrl+O로 지정했지만, 실제로 Ctrl+O를 누른다고 해서 그 항목이 선택되지는 않는다. 단축키와 그 항목을 연결하기 위해서는 bind 명령을 사용해야 한다. 이것은 뒤에서 다루기로 한다.

3장 Pack

pack 명령을 이용하면 위짓들의 위치를 조정할 수 있다. pack 명령에서 사용되는 옵션에는 -after, -anchor, -before, -expand, -fill, -in, -ipadx, -ipady, -padx, -pady, -side 등이 있는데, 가장 일반적으로 쓰는 옵션은 -side라고 할 수 있다. -side 옵션의 값으로 줄 수 있는 것은 left, right, top, bottom이 있다.


button .a -text a
button .b -text b
button .c -text c

세 개의 버튼을 생성한 후에, 다음의 두 명령을 각각 실행하여 위짓들의 배치를 확인해보자.

pack .a .b .c -side left
pack .a .b .c -side top

[그림 8]
[그림 9]

위짓들을 배치하다보면 가끔 상하나 좌우로 조금씩 공간이 생기는데, 이것을 없애주려면 -fill 옵션을 사용하면 된다. -fill 옵션에는 x, y, both로 각각 값을 줄 수 있다.


button .a -text aaaaaaaa
button .b -text b
button .c -text cccc
pack .a .b .c -fill x

-fill 옵션없이 pack 명령을 수행한 것과 결과가 어떻게 다른지 비교해보자. -fill 옵션말고도 빈 공간을 채우는 방법으로 -expand 옵션을 사용하는 것이 있는데, -expand는 모든 위짓이 함께 공간을 채우는 것이 아니라, 특정 위짓에게 남은 공간을 모두 할당해준다. 그러나 -fill 옵션처럼 그 위짓이 빈 공간을 모두 채우는 것이 아니라, 잠재적으로 그 위짓에게 공간을 사용할 권리가 부여된 것이다. -fill 옵션을 함께 사용하여 빈 공간을 채울 수 있다. 다음의 예제를 실행하고 윈도우 크기를 늘여보자.


button .a -text aaaaaaaa
button .b -text b
button .c -text cccc
pack .a .b .c -side left

[그림 10]

오른쪽에 빈 공간이 생긴다. 이제 다음 각각의 pack 명령을 수행하여 -expand 옵션이 어떤 효과를 내는지 살펴보도록 하자.


pack .a .b .c -expand 1
pack .a .b -side left -expand 0
pack .c -expand 1 -fill x

[그림 11]
[그림 12]
[그림 13]

이제 위짓들을 정렬하는 방법에 대해 알아보자. 정렬을 위해서는 -anchor 옵션을 사용할 수 있는데, 옵션의 값으로 center(중앙), n(북), ne(북동), e(동), se(남동), s(남), sw(남서), w(서), nw(북서)의 방향값을 줄 수 있다.

pack .a -anchor nw

명령을 수행한 후, 윈도우의 크기를 늘여서 위짓이 어떻게 정렬되어 있는지 확인해보자.

-padx, -pady 옵션은 위짓과 다른 위짓(또는 윈도우 경계) 사이의 간격을 조정할 때 사용하고, -ipadx, -ipady 옵션은 위짓 내부의 텍스트나 이미지와 위짓 경계와의 내부 간격을 조정할 때 사용하는 옵션이다.

pack .a .b .c -padx 2m -pady 3m -ipadx 2m -ipady 2m

4장 이벤트(event)와 액션(action)

다음 표는 이벤트와 그 이벤트의 의미이다.

Button 마우스 버튼이 눌러졌음
ButtonPress 마우스 버튼이 눌러졌음
ButtonRelease 마우스 버튼이 눌러졌다가 다시 놓아졌음
Key 키가 눌러졌음
KeyPress 키가 눌러졌음
KeyRelease 키가 눌러졌다가 다시 놓아졌음
Enter 마우스 포인터가 윈도우 안으로 들어왔음
Leave 마우스 포인터가 윈도우 바깥으로 나갔음
Motion 마우스가 윈도우 내에서 움직임
Expose 윈도우가 다시 그려짐
Map 윈도우가 화면에 나타남
Unmap 윈도우가 화면에서 없어짐
FocusIn 키보드 포커스가 윈도우 안으로 들어왔음
FocusOut 키보드 포커스가 윈도우 바깥으로 나갔음
Gravity 윈도우의 gravity가 변화했음
Circulate 윈도우가 쌓여있는 순서가 변했음
Configure 윈도우의 위치가 크기가 변했음
Destroy 윈도우가 제거되었음
Property X 윈도우 속성이 윈도우에 쓰여졌음
Visibility 윈도우의 visibility가 변했음
Reparent 윈도우 매니저에 의해 윈도우의 parent가 바뀌었음
Colormap 윈도우의 컬러맵이 변화했음

일반적인 X 윈도우 시스템의 이벤트는 위와 같고, 마우스의 첫번째 버튼이 눌러진다든가 하는 특정한 이벤트는 ButtonPress-1과 같이 지정할 수 있다. 키보드의 경우, Control-1, Shift-M, Alt-V, Mod5와 같이 특정 키를 가리킬 수 있다.

이벤트에 액션을 연결시키는 명령은 bind 명령인데, 이벤트가 발생하는 범위를 지정할 수 있다.


bind all {puts "Entering %W at (%x, %y)"}

all 대신에 위짓의 이름을 쓰면 그 위짓 내부에서만 이벤트와 액션을 연결하는 바인딩(binding)이 효과를 가지게 된다. %W는 이벤트가 발생한 윈도우의 이름이고, %x, %y는 xy좌표이다. %K는 키 이벤트의 이름이고, %A는 키 이벤트를 ASCII코드로 바꾸어 주는 변수이다. 윈도우의 너비와 높이를 알려주는 것으로 %w, %h가 있고, 이벤트 종류를 알려주는 %T를 사용할 수도 있다.

5장 캔버스(canvas) 위짓

캔버스(canvas)는 그림을 그릴 수 있는 위짓이다. 캔버스에 그릴 수 있는 것으로는, line(선), oval(원과 타원), rectangle(사각형), polygon(다각형), arc(원호), text(텍스트), bitmap(비트맵), image(이미지), window 등이 있다.


canvas .can -width 300 -height 200
pack .can
.can create arc 130 40 50 120

이것은 (130, 40)부터 (50, 120)에 걸쳐 원호를 그리는 것이다. -fiil 옵션으로 내부를 특정 색으로 채울 수도 있다.


.can create bitmap 200 20 -bitmap error
.can create image 100 100 -image earth.gif

이 예제들은 비트맵과 일반 이미지를 그리는 것인데, -bitmap이나 -image 옵션의 값은 비트맵 파일의 이름이나 이미지의 이름이다. error의 경우에는 Tk에서 이미 정의된 비트맵인데, 이밖에도 gray25, gray50, hourglass, info, questhead, question, warning 등이 있다.

이미지를 다루기 위해서는 image를 먼저 만드는 과정이 필요하다. image라는 명령을 사용하는 것인데, 다음의 예를 참고하자.


image create photo x -file earth.gif
label .y -image x
pack .y

[그림 14]

x라는 이름으로 earth.gif를 이미지로 만들고, label에다가 얹는다. 버튼 위에도 얹을 수 있다. 그러면 이미지가 있는 버튼을 만들 수 있는 것이다. Tk에서는 GIF와 PPM/PGM 포맷의 image를 사용할 수 있다.


.can create line 100 120 150 130 -arrow first

다시 캔버스로 돌아가서 선을 그려보자. -arrow 옵션의 값으로는 last와 both를 줄 수도 있는데, 화살표모양을 어디에 달 것인가를 결정하는 것이다. 좌표를 더 많이 주고 -smooth 옵션의 값을 1로 주면 베지어 곡선(Bezier curve)을 그릴 수 있다.


.can create line 230 140 150 60 100 110 120 30 -smooth 1

원이나 타원의 경우 oval 오브젝트를 만들면 되는데, 원이나 타원이 들어갈 사각형의 두 대각점을 지정하면 된다. 사각형을 만드는 방법도 마찬가지이다. 다각형의 경우, 좌표를 나열하면 다각형을 그려준다.


.can create polygon 30 50 60 40 20 100 120 180 -fill orange

위짓을 넣기 위해 윈도우를 만들어 본다. 캔버스내에 윈도우의 위치가 정해지므로 위짓을 pack하지는 않는다.


button .can.b -text hello -relief raised
.can create window 100 100 -window .can.b

캔버스에 그림을 다 그렸으면 출력을 해야 하는 경우가 있다. postscipt 명령을 주면 캔버스의 그림을 PS형식의 파일로 저장한다.


.can postscript -colormode color -file result

그러나 필자가 실험해 본 결과로는, 안타깝게도 버튼 위짓이 출력되지 않았다.

[그림 14.5]

6장 텍스트 위짓(text widget)

텍스트 위짓은 텍스트를 보여주기 위한 위짓이다. 다른 위짓도 크기가 커지면 스크롤바를 달아주어야 하는데, 텍스트 위짓은 텍스트의 분량이 많을 것으로 예상을 하고 미리 스크롤바를 달아주는 것이 좋다.


text .t -yscrollcommand ".sc set"
entry .e -textvariable var
scrollbar .sc -command ".t yview"
pack .sc -side right -fill y
pack .t .e -side top -expand 1 -fill x

이제 텍스트 위짓에 파일의 내용을 올리도록 한다. 이것은 리스트박스에서 했던 것과 비슷한 작업이다. 리스트박스의 예제를 참고하도록 하자.


proc print filename {
set f [open $filename]
while {![eof $f]} {
.t insert end [read $f 100]
}
close $f
}
print /etc/passwd

텍스트와 관련하여 텍스트의 일부를 잘라 내어, 복사하고 붙이는 작업을 할 수 있는데, 이렇게 잘라 내거나 복사하려는 텍스트는 클립보드(clipboard)나 selection 메카니즘을 이용하게 된다. selection은 X 응용프로그램간의 텍스트의 저장을 Tcl에서 지원하게 위해 제공되는 것이고, 클립보드는 Tk에서 사용하는 데이터의 저장공간이다. selection은 화면 상에서 사용자에게 보이지만, 클립보드는 보이지 않는다. 위의 예제를 실행하고 마우스로 텍스트를 긁어서 엔트리 위짓에 복사해보자. 텍스트를 선택하게 되면 selection에 자료가 복사되는데, 다음과 같은 명령으로 확인할 수 있다.


selection get

클립보드에 자료를 복사하고 꺼내는 것은 다음과 같다.


clipboard append "Hello, world!"
selection get -selection CLIPBOARD

selection이나 클립보드에 저장된 자료를 제거하는 것은 get이나 append 대신 clear를 사용하면 가능하다.

텍스트를 입력할 때, 포커스를 지정하는 명령으로 focus가 제공된다. 다음은 frame1 위짓의 entry 위짓에 포커스를 맞추는 예이다.


focus .frame1.entry

포커스를 없애려면 focus none을 수행하면 된다.

7장 Dialog Window

대화 상자(dialog window)를 만드는 것은 tk_dialog를 이용하면 가능하다. 다음은 OK, Cancel, Quit라는 세 개의 버튼을 가지는 대화 상자를 생성한다. 디폴트(default)로 선택되는 버튼은 0번째 OK버튼이다. 비트맵으로 info를 사용하였다.


tk_dialog .dlg "Hello" "This is a hello." info 0 OK Cancel Quit

[그림 15]

사용자가 직접 대화 상자를 만들기 위해서는 toplevel 명령을 이용하여 toplevel 위짓을 구성하고, 그 위짓을 Diaglog 클래스로 선언하는 것이다. toplevel 위짓은 기본 윈도우 바깥에 윈도우를 하나 더 생성하는 역할을 한다.


toplevel .top -class Dialog
label .top.l -relief flat -text "My own dialog box"
button .top.b -relief ridge -text "OK" -command { puts "OK" }
pack .top.l .top.b -side left

[그림 16]

이벤트가 발생하면 그 이벤트는 그 윈도우와 그 하위에 있는 위짓에만 전달이 된다. 이것을 grab이라고 하는데, 이벤트를 특정 윈도우와 연결시키려면 다음과 같이 grab을 설정하면 된다.


grab set .dlg

반대로 이 설정을 풀려면 set 대신 release를 사용한다.

Tk에서는 변수의 값이 변하거나 윈도우의 visibility 상태가 바뀌거나, 또는 윈도우가 사라지게 될 때까지 기다리게 하는 tkwait 명령이 있다.


tkwait variable hello
tkwait visibility .top
tkwait window .dlg

다음은 OK 버튼과 Cancel 버튼을 가진 대화상자를 만들고, 대화상자에서 임의의 버튼을 선택할 때까지 기다리는 예제이다.


toplevel .top
grab set .top
button .top.ok -text OK -comman { destroy .top }
button .top.cancel -text Cancel -comman { destroy .top }
pack .top.ok .top.cancel -side left
tkwait window .top

8장 기타

위짓에 속한 기본 리소스가 아닌 윈도우 매니저가 관리하는 리소스는 wm 명령을 이용하면 된다. 예를 들어, 윈도우의 타이틀을 바꾸려면 위짓의 리소스를 바꾸어 해결할 수 있는 것이 아니라, 윈도우 매니저에게 요청해야 한다.


wm title .top "My dialog window"

이 윈도우를 아이콘으로 만들어(minimize) 보자.


wm iconify .top

윈도우 매니저가 관리하는 리소스를 바꾸거나 그 정보를 알아내기 위해서 wm 명령을 사용할 수 있다. 자세한 내용은 man -s n wm을 참조해야 할 것이다.

tkwait의 예제에서 나왔던 destroy 명령은 윈도우를 제거하는 명령이다. 윈도우를 제거하면서 관련된 명령이나 윈도우 상태 또한 제거한다. 윈도우가 쌓여있는 순서를 바꾸기 위해 raise나 lower 명령을 사용할 수 있다.

예제파일

MP3 연주기로 유명한 Mpg123은 텍스트 기반의 프로그램이다. 이것의 대표적인 X 인터페이스는 Tk3play이다. 다시 말하면, Mpg123은 텍스트 상에서 MP3 파일을 디코딩(decoding)해서 연주해주는 작업을 하고, Tk3play는 버튼이나 레이블을 이용하여 Mpg123을 제어하는 역할을 맡는다. 아래 예제는 필자가 Tk3play를 몰랐을 때, Mpg123을 X 윈도우 상에서 제어하는 인터페이스가 필요해서 작성했던 Tcl/Tk 스크립트이다. Tk3play의 아주 기본적인 몇 가지 기능을 가지고 있는 인터페이스라고 할 수 있다.


#!/usr/local/bin/wish -f
#
# mpgplay
#
# 이 스크립트는 wish를 이용한 mpg플레이어입니다.
# This script run mpeg3 audio player with wish.

# 아규먼트로부터 디렉토리명을 제외한 MP3 파일의 이름을 구한다.
set filenamelist [split [lindex $argv 0] /]
set listend [expr [llength $filenamelist] - 1]
set filename [lindex $filenamelist $listend]

# 타이틀과 MP3 파일명을 위한 레이블을 만들고
# Play, Stop, Quit을 위한 버튼을 만든다.
label .title -text ” Terzeron’s mp3 player ” -foreground black
-background pink -relief ridge
label .name -text $filename -foreground black -background white
-relief ridge
frame .ground -relief sunken
button .ground.play -text Play -foreground blue -background white
button .ground.quit -text Quit -foreground red -background white
button .ground.stop -text Stop -foreground purple -background white

# 레이블과 버튼 등의 위짓을 배치한다.
pack .title -side top -fill x
pack .name -fill x
pack .ground -fill x
pack .ground.play .ground.stop .ground.quit -side left -expand 1 -fill both

# 버튼과 키입력에 대한 이벤트 처리 프러시져를 연결시킨다.
bind .ground.play { playproc }
bind .ground.quit { quitproc }
bind .ground.stop { stopproc }
bind . { playproc }
bind . { stopproc }
bind . { quitproc }

# 기본값 지정
set pidno 0
set playing 0
set stopping 0

# Play 프러시져
proc playproc {} {
global argv pidno playing stopping fileloc
# 이미 연주 중이면 무시
if {$playing == 1} {
return
}
# 버튼의 색상과 relief형태를 바꾼다.
.ground.play configure -background yellow -foreground black -relief sunken
.ground.stop configure -background white -foreground purple -relief raised
if {$stopping == 1} {
# 연주 중에 멈춘 경우에는 CONT 시그널을 보내 mpg123을 동작시킨다.
exec /usr/bin/kill -CONT $pidno
} else {
# 아직 시작하지 않은 경우에는 mpg123을 fork/exec하여 실행시킨다.
set pidno [exec /usr/local/bin/mpg123 [lindex $argv 0] >& /dev/null &]
}
set playing 1
set stopping 0
}

# Stop 프러시져
proc stopproc {} {
global argv pidno stopping playing
# 연주가 멈춰 있는 경우나 아직 시작하지 않은 경우에는 무시한다.
if {$stopping == 1 || $playing == 0} {
return
}
# 버튼의 색상과 relief 형태를 바꾼다.
.ground.stop configure -background yellow -foreground black -relief sunken
.ground.play configure -background white -foreground blue -relief raised
set stopping 1
set playing 0
# STOP 시그널을 mpg123으로 보내 잠시 프로세스를 멈추게 한다.
exec /usr/bin/kill -STOP $pidno
}

# Quit 프러시져
proc quitproc {} {
global argv pidno playing stopping
# KILL 시그널을 보내 프로세스를 종료시킨다.
if [catch {if {$pidno != 0} { exec /usr/bin/kill -9 $pidno } } ] {
;
}
# top-level 위짓을 없앤다.
destroy .
# 끝낸다.
exit 0
}
# 스크립트가 실행하면 바로 Play 프러시져를 호출하여 연주를 시작한다.
playproc
[그림 17]

이 인터페이스는 Tk3play에 비하면 기능도 부족하고 깔끔한 위짓 배치를 보여주지도 못한다. 그러나 독자들이 ‘왜 Tcl/Tk 인가?’하는 의문에 답할 수 있는 한 가지 사례를 보여주는 예제 프로그램이 될 수 있을 것이다. 이 기사를 읽은 독자라면 누구나 Tcl/Tk를 이용하여 손쉽게 X 윈도우 프로그래밍을 할 수 있을 것이다.

Mpg123과 Tk3play는 다음 URL에서 구할 수 있다.
http://www-ti.informatik.uni-tuebingen.de/~hippm/mpg123.html
http://www.msc.cornell.edu/~bef2

서울대학교 전산과학과 데이타베이스 연구실 조영일

:

Tcl/Tk 문법 - 1부 Tcl

Development 2008. 6. 9. 09:34
출처 : http://terzeron.net/wp/?p=244

1999/05/15

소개

Tcl은 Tool Command Language, Tk는 Toolkit의 약자로서, 응용프로그램을 제어하고 확장하는 것을 돕는 프로그래밍 언어의 하나이다. Tcl의 장점은 Tk와 함께 사용되어 응용프로그램이 X Window를 손쉽게 다룰 수 있도록 할 수 있다는 데 있다. 물론 요즘에는 Scheme, Lisp, Perl 등의 강력한 경쟁자들이 생기긴 했지만, 아직도 많은 개발자들이 Tcl/Tk에 대해서 친근감을 느끼고 있다. 게다가 Tcl/Tk는 C source로 embed하여 사용할 수 있다는 강점을 가지고 있다.

Tcl/Tk는 [티클티케이]라고 읽는다고 개발자 J. K. Ousterhout가 말했다. Ousterhout는 Tcl의 장점으로 쉽고, 강력한 스크립트 언어이며, 다른 패키지와 연결하기 쉽다는 점을 밝히고 있다.

필자가 꼽는 여타의 프로그래밍 언어에 대한 Tcl/Tk의 장점은 쉽다는 것이다. 아주 간단하게 문법을 익힐 수 있어서 초보자들이 X Window 상에서 실행되는 프로그램을 개발하는 것이 아주 용이하다. 그러나 이 장점은 역으로 단점이 되기도 한다. Tcl자체만으로는 시스템 프로그래밍에 적합하지 않다는 점이 다. 그러나 프로그래밍 언어는 모든 기능을 가질 수도 없고, 가진다고 해도 널리 쓰일 수 있는 것은 아니기 때문에 Tcl 나름대로 유용하게 쓰일 곳이 있기 마련이다. 그러나 미리부터 실망할 필요는 없다. Tcl을 C로 embed시켜 서 시스템 프로그램을 개발한다면 Tcl의 장점을 살리면서 C의 강력한 기능 을 지원받을 수 있으니 말이다. 자, 이제부터 티클티케이가 당신의 개발도 구가 될 수 있도록 가볍게 시작하도록 하자.

설치 및 실행

Tcl/Tk에 관련된 거의 모든 패키지는 http://www.scriptics.com/software에서 구할 수 있다. 윈도우즈(Windows)와 매킨토시(Macintosh)환경에서 사용할 수 있는 바이너리 배포본도 제공된다. 필자가 권하는 버전은 Tcl/Tk 8.1b3이다.

ftp://ftp.scriptics.com/pub/tcl/tcl8_1/tcl8.1b3.tar.gz

ftp://ftp.scriptics.com/pub/tcl/tcl8_1/tk8.1b3.tar.gz

에서 소스를 직접 내려받을 수도 있다. 컴파일 과정은 아주 간단하다. Tcl과 Tk 소스를 모두 풀어놓고 tcl8.1b3/unix 디렉토리에서 configure;make를 실행하면 컴파일까지 끝난다. 수퍼유저 권한으로 make install을 실행하면 /usr/local/bin에서 tclsh과 wish를 실행할 수 있을 것이다. 이전 Tcl/Tk버전을 이미 설치했다면 tclsh과 wish가 최신 버전(tclsh8.1과 wish8.1)을 링크하고 있지 않을 테니, 확인을 해야 하는 번거로움이 있다. tclsh은 Tcl 명령만을 실행할 수 있는 인터프리터이고, wish은 Tcl/Tk 명령을 실행할 수 있는 인터프리터인데, 1부에서는 tclsh로도 충분히 모든 기능을 확인할 수 있을 것이다.

이 문서는 총 3부로 구성되어 있고, 1부는 Tcl의 문법, 2부는 Tk의 문법, 3부는 Tcl/Tk의 확장에 대해 다루고 있다.

1부 Tcl의 문법


1장 기초 문법과 변수, 표현식, 리스트

1) 기초 문법과 변수

가장 처음에 하고 싶은 일은 출력일 것이다. 모든 프로그래밍 언어가 그렇 게 하듯이 세상에 인사부터 하는 게 예의일 것이다.

puts "Hello, World!"

인사를 여러 번 할 수 있도록 문자열을 변수에 저장하려면,

set hello "Hello, World!"
put $hello

csh과 비슷하게 대입에는 set, 변수의 값 사용(dereference)에는 $를 사용한다. 더욱 재미있는 점은 csh처럼 unset 명령도 제공된다는 점이다.

다소 불편한 점이라면, 표현식을 처리하기 위해서는 반드시 expr을 사용해 야 한다는 것이다. 이것은 Tcl이 변수 타입에 대해서 느슨한 태도를 취하기 때문인데, 만약 다음과 같은 명령을 준다면,

set j 3; set i $j+1

i에는 문자열로 $j+1이 대입될 것이다. 이것은 원하는 바가 아닐 수도 있다. $j+1을 표현식(expression)으로 간주하고 처리하기 위해서는 앞에서 밝힌 바와 같이 expr을 써서,

set i [expr $j+1]

이라고 명령을 주어야만 한다. 다소 번거로운 일이긴 하지만, 거꾸로 생각해보면 표현의 자유로움과 다양성을 보장받을 수 있다는 것을 의미하기도 한다. 이 예에서 우리는 ;이 Tcl문장의 구분자(separator)로 사용된다는 것과 [ ]이 하나의 Tcl명령의 결과값을 반환한다는 것을 덤으로 알게 되었다.

Tcl에서 사용되는 특수문자는 일반적으로 셸(shell)에서 사용하는 a, ,
, f 등을 그대로 사용할 수 있다. 예를 들어, 다음의 실행 결과는 My name is “Young-il Cho”. 가 될 것이다.

set firstname "Young-il"
set lastname "Cho"
set myname "My name is "$firstname $lastname".
";
puts $myname

사실 Tcl의 각 문장 하나하나는 결과값을 매번 반환하기 때문에 마지막 줄의 puts는 필요하지 않을 수도 있다. 그렇기 때문에, puts $myname 대신에 set myname 이라고 입력해도 같은 결과를 얻을 수 있다.

변수는 타입이 정해지지 않았기 때문에, 다른 타입의 값을 대입할 수 있을 뿐만 아니라, 일반 변수를 배열처럼 사용할 수도 있다. 다음은 1월과 2월의 이름을 배열에 넣은 후에, 1월의 이름을 구하는 예제이다. 배열이라고는 하지만, 마치 Perl의 해시(hash) 변수처럼 사용할 수 있다.

set month(1) Jan
set month(2) Feb
set month(1)

다차원 배열도 가능하다. 다음은 4월 3일부터 5일까지의 요일을 다차원 배열에 대입해보는 아주 간단한 예이다.

set mday(4,3) Sat
set mday(4,4) Sun
set mday(4,5) Mon
puts $mday(4,4)

정수값을 가지고 있는 변수는 incr 명령을 이용하여 증감시킬 수 있는데, 그 변수에 들어 있는 값이 정수값으로 읽을 수 있는 값이기만 하면 된다.

set x "1"
incr x

여기에 다시 100배를 해서 3을 빼는 작업을 해보도록 하자. 100를 하기 위해서는 앞에서와 같이 expr $x*100도 가능하겠지만, append 명령을 이용하여 정수값 뒤에 0을 두 개 붙이는 것도 가능하다. 다음 예의 결과값은 2 * 100 - 3이니까 197이 될 것이다.

append x "00"
incr x -3
puts $x

강력하게 타입을 검사하는 컴파일 프로그래밍 언어에서는 상상도 하지 못할 엉뚱한 작업들을 태연하게 해내고 있다. 그만큼 융통성이 있다고 볼 수도 있고 거꾸로 사용자들이 에러를 내기 쉬울 수도 있다고 볼 수도 있다.

변수 중에서 예약되어 있는 변수 두 가지가 있다. 하나는 프로그램으로 넘겨지는 argument 리스트에 관련된 argv0, argv, argc이고, 또 하나는 환경 변수를 해시 형태로 가지고 있는 env이다. argv0은 Tcl로 작성된 스크립트의 이름을 가리키고, argv는 argument 리스트 전체를 배열로 가지고, argc는 argument의 개수를 정수값으로 가지고 있다. 다음의 예를 실행파일로 만들어서 argument로 hello 1 2 world를 사용해 실행해보면 이해가 어렵지 않을 것이다.

#!/usr/local/bin/tclsh
puts "This program $argv0 has $argc arguments : $argv
";
puts "User's ID is $env(USER).
";

2) 표현식

표현식은 수식이나 문자열 처리에 관한 것이다. Tcl에서 제공하는 연산자는 ANSI C의 것과 거의 동일하다. 사칙 연산자(+, -, *, -)와 나머지 연산자(%)를 비롯하여 관계연산자(>, <, >=. <=, !=, ==)와 논리연산자(!, &&, ||), 비트 연산자(!, ^, |, ~, <<, >>), 3항 연산자(? :)가 모두 지원된다.

또한 C에서 제공하는 수학 함수(삼각함수, 지수함수 등)를 거의 모두 사용할 수 있다. 기본적으로 프로그래밍의 경험이 조금이라도 있는 독자라면 여기에서 자세히 다루지는 않고 이름만 나열해도 쉽게 man page를 이용해 쉽게 찾아볼 수 있을 것으로 간주하고 넘어가도록 하겠다.

abs, acos, asin, atan, ceil, cos, cosh, double, exp, floor, fmod, hypot, int, log, log10, pow, round, sin, sinh, sqrt, tan, tanh

tclsh 실행 도중, man abs를 입력해보면 C의 man page를 볼 수 있다. abs, labs, llabs가 C에서 제공되고 있지만, Tcl에서는 long 타입의 변수를 사용할 수 없는 관계로 labs와 llabs는 사용할 수 없다. 물론 Tcl을 C에서 라이브러리로 사용할 때는 long이 사용되어야 하므로 long를 다루는 방법이 제공된다.

리스트나 문자열 형태의 값을 Tcl 명령으로 인식하여 작업을 수행하는 eval이라는 명령이 있다. eval {puts $tcl_version}라고 입력하면 puts $tcl_version이 실행될 것이다. 그러면 굳이 eval 명령을 사용해야 하는 이유에 대한 의구심이 생길 지도 모르겠다. 다음의 예를 보자.

set y 3
foreach f {set unset} {
eval $f y
}
puts $y

우선 변수 y에 3을 대입하고 foreach 문에서 변수 f에 set과 unset을 대입하면서 매번 eval $f y를 실행하기 때문에, set y와 unset y가 실행된다. unset에 의해 y를 변수로 사용할 수 없게 되었으므로 마지막 명령 puts는 에러를 발생하게 되어 있다.

문자열 처리에 관련된 명령들은 뒤에서 다시 설명하도록 하겠다.

3) 리스트

리스트는 [, ] 또는 “, “을 이용해 만들 수 있다.

set x {hello world}
set x "hello world"

는 같은 결과를 가지게 된다. 주의할 것은 set x [hello world]는 hello를 명령으로 인식하여 에러를 발생하게 된다는 점이다.

리스트를 다루기 위해 제공되는 명령에는 리스트를 붙이거나 인덱스를 이용해 특정 아이템을 꺼내거나 쪼개거나 검색하는 명령이 있다. 리스트 안에 리스트를 둘 수도 있는데, 이런 경우 lindex를 두번 사용하여 특정 단어를 지정할 수 있다. 다음은 New York의 York를 가리키는 예를 든 것이다.

set x {California Chicago {New York} Washington Texas}
lindex [lindex $x 2] 1

리스트를 붙이는 방법에는 리스트에 존재하는 모든 아이템을 모아서 붙이는 concat 명령과 각 리스트를 하나의 아이템으로 유지시키면서 붙이는 list 명령이 있는데, 다음을 비교해보자.

set y {Miami {Salt-lake City} Alaska}
concat $x $y
list $x $y

이 명령들의 결과는 다음과 같다.

California Chicago {New York} Washington Texas Miami {Salt-lake City} Alaska
{California Chicago {New York} Washington Texas} {Miami {Salt-lake City} Alaska}

이제는 리스트를 쪼개고 합치고 하는 작업을 하기로 한다. passwd파일의 일부을 변수 a에 담고, :을 구분자(delimiter)로 삼아서 : 단위로 쪼개어 변수 b에 리스트로 저장한다. 그리고는 다시 하나의 아이템으로 합쳐보도록 하자. 결과적으로 변수 c는 a와 같은 내용을 포함하게 될 것이다.

set a root:x:0:1:Super-User:/:/sbin/sh
set b [split $a :]
set c [join $b :]

이번에는 리스트의 일부를 찾거나 치환하거나 리스트를 정렬하는 명령에 대해 알아보도록 하자. 아이템을 찾는 것은 lsearch, 치환하는 것은 lreplce와 lrange, 정렬하는 것은 lsort이다. 리스트가 포함하고 있는 아이템 수를 반환하는 llength명령은 지나가도록 한다. 치환하거나 아이템을 찾을 때 사용되는 인덱스(index 또는 offset)를 설명하는 일은 필자에게나 독자에게나 모두 머리 아픈 일이 될 테니, 간단한 예로 대신하고자 한다. 다음은 리스트 변수 x의 2번째 위치부터 1 빼기 1개(0개)의 아이템과 Detroit라는 새로운 아이템을 바꿔치기 하고 2번째 위치부터 3 빼기 1개(2개)의 아이템을 Detroit와 Boston으로 치환하는 것이다. 그 다음은 3번째 공백 위치에 Illinois과 Detroit를 끼워넣는 것이다. 리스트의 일부를 꺼내는 lrange 명령도 있으나 여기서는 설명을 생략하기로 한다.

lreplace $x 2 1 Detroit
lreplace $x 2 3 Detroit Boston
linsert $x 3 Illinois Detroit
lrange $x 1 2

주의할 것은 이런 명령이 리스트 변수 x에 대해서 물리적인 효과를 가지지는 못한다는 점이다. x에 물리적인 효과를 주려면 set x [linsert $x 3 Illinois Detroit] 처럼 새로 대입해야만 한다. 위 명령의 결과는 다음과 같다.

California Chicago Detroit {New York} Washington Texas
California Chicago Detroit Boston Texas
California Chicago {New York} Illinois Detroit Washington Texas
Chicago {New York}

lappend는 위의 명령들과는 조금 다르다. 변수를 새로운 아이템을 추가한 결과로 바꾸어주기 때문이다.

lappend x Hawaii

이제 정렬해보자. lsort에 사용되는 옵션(option)에는 -ascii, -dictionary, -integer, -real, -command, -increasing, -decreasing, -index가 있는데, 디폴트(default) 값은 -ascii와 -increasing이다. Tcl/Tk가 버전 8로 올라오면서 -dictionary와 -index가 추가되었는데, -dictionary 옵션은 -ascii 옵션과 거의 동일하지만, 대소문자가 구분되지만 비교시에는 무시되고 단어 중간에 나오는 숫자는 문자로서 취급되지 않고 수치로 취급된다는 점이다. man page에 나오는 예를 적어보았다.

lsort -dictionary {bigBoy bigbang bigboy}
lsort -dictionary {x10y x9y x11y}

직접 실행하여 결과를 얻어보길 바란다. -index 옵션은 다음과 같은 예를 보면 쉽게 이해할 수 있을 것이다. 각각의 아이템은 리스트 형태로 되어있고, 그 리스트 중에서 index가 가리키는 아이템이 정렬할 때 키(key)로 사용되는 것이다.

lsort -integer -index 1 {{First 24} {Second 18} {Third 30}}

2장 제어문, 프러시져

1) 제어문

제어문에는 if, while, for, foreach, switch, continue, break 등이 있다. source나 이전에 다루었던 eval등이 제어문에 속하기는 하지만, source의 설명은 다음 장으로 미룬다. 제어문은 대체로 프로그래밍의 기본에 속하기 때문에 프로그래밍의 경험이 조금이라도 있는 독자라면 쉽게 이해할 것으로 간주하여 간단히 설명하고 다음으로 넘어가도록 하겠다. 다만 Tcl에서 주의해야 할 점은 C와는 달리 비교문은 {과 }로 둘러싸야 한다는 것이다.

if {$x < $y} {
puts "X is less than Y."
} else if {$x == $y} {
puts "X is equal to Y."
} else {
puts "X is greater than Y."
}

위의 예제는 x와 y의 수치값을 비교하여 각각의 경우에 대해 설명을 출력한다. 다음의 while, for, foreach는 모두 동일한 작업을 수행한다.

set i 0
while {$i > 5} {
puts $i
if {$i == 3} {
break;
}
incr i
}

for {set i 0} {$i > 5} {incr i} {
puts $i
if {$i == 3} {
break;
}
}

foreach i {0 1 2 3 4 5} {
puts $i
if {$i == 3} {
break;
}
}

2) 프러시져(procedure)

프러시져는 C의 함수(function)과 같다고 할 수 있다. 문법은 “proc 프러시져이름 매개변수리스트 프러시져본체”로 되어 있다. 명시적으로 return을 이용해서 결과를 반환할 수도 있고, return을 사용하지 않으면 마지막으로 사용된 표현식의 결과가 return값으로 사용된다.

proc average parameters {
set sum 0.0
foreach i $parameters {
set sum [expr $sum + $i]
}
return [expr $sum / [llength $parameters]]
}

프러시져의 호출은 다음과 같은 방법으로 할 수 있다.

average {1 2 3 4 5 6 7 8 9 10}

{1 2 3 4 5 6 7 8 9 10}이 parameters라는 리스트형 변수로 넘겨지는 것이다. 그러나 매개변수로 하나만 사용될 수 있는 것은 아니고 여러 개를 지정하거나 기본값을 지정할 수도 있다.

proc sum {x {y 3}} {
return [expr $x + $y]
}

변수 y의 기본값으로 3이 정해져 있기 때문에, sum 3 4 과 같은 형태의 호출이 가능하고, sum 3 과 같은 형태로도 호출을 할 수 있다. 그 결과값은 7과 6일 것이다.

프러시져 내부로 제어가 넘어가면 기본적으로는 외부나 상위의 프러시져의 변수를 사용할 수 없다. 매번 매개변수를 사용할 수는 없는 노릇이므로 global이나 upvar를 사용하여 상위레벨의 변수에 접근하게 된다. global 명령은 외부의 변수들을 전역변수로 지정하여 현재 프러시져 내부에서 지역변수처럼 자연스럽게 사용할 수 있게 한다. upvar는 Tcl의 “참조에 의한 호출”(call-by-reference)을 지원하기 위해 사용되는 명령으로 상위레벨의 프러시져에서 사용되는 변수를 새로운 이름으로 지정해서 지역변수로 사용하게 한다.

set virtual_average 30.0
proc average parameters {
global virtual_average
set sum 0.0
foreach i $parameters {
set sum [expr $sum + $i]
}
return [expr $virtual_average + $sum / [llength $parameters]]
}

이 예제는 평균을 구하는 위의 예제에 가평균을 사용하기 위해 약간 손을 본 프러시져이다. virtual_average가 외부에서 정의되었기 때문에 proc average에서는 사용할 수가 없어서 global 명령으로 virtual_average를 지역변수화한 것이다. 다음의 예는 upvar를 이용해서 새로운 지역변수로 상위레벨의 변수를 참조하는 것을 보이는 것이다.

proc print array_name {
upvar $array_name arr
foreach item [lsort $arr] {
puts $item
}
}

upvar는 정수값을 이용해서 참조의 대상이 되는 변수를 어느 레벨까지 찾을 것인가를 결정할 수 있는데, #0을 쓰면 무한대로 거슬러 상위로 올라갈 수 있다. 다음의 예는 바로 상위의 프러시져의 array_name이나 혹은 그 프러시져를 호출한 더 상위의 프러시져에서 사용된 array_name을 arr로 참조하겠다는 의미이다. 그 다음 줄은 상위로 계속 거슬러 올라가면서 array_name이라는 변수를 찾겠다는 의미이다.

upvar 2 $array_name arr
upvar #0 $array_name arr

upvar와 비슷한 명령으로 uplevel이라는 명령이 제공되는데, 이 명령은 eval과 upvar를 합쳐놓은 기능을 한다. 다음 명령이 프러시져 내부에서 사용되면 이 프러시져를 호출한 프러시져의 x변수를 참조해다가 43을 대입하는 것을 실행할 수 있게 한다.

uplevel 1 {set x 43}

3장 문자열 처리

Tcl은 8 bit-clean하다고 한다. 이 말은 ASCII 코드 이외의 문자를 다룰 수 있다는 의미이다. 그래서 tclsh에서 한글 출력이 가능하다. wish의 경우에는 조금 사정이 달라서, 폰트만 지정하면 출력은 문제가 없지만,아직까지도 입력서버에 관련된 사항이 미비하여 한글입력은 가능하지 않다.

문자열을 다루는 것은 리스트를 다루는 것과 비슷한 형태의 명령을 통해 가능하다. 다만 문자열 처리 명령은 string과 기타 명령을 조합한 형태이다. 예를 들면, string length는 문자열의 길이를 출력하는 명령이고, string index는 문자열의 몇번째 글자를 지정하는 명령이다.

1) 정규 표현식(regular expression)

문자열에 대해서 설명하기 전에 정규 표현식(regular expression)에 대해서 설명을 해야겠다. 다소 안타깝지만, Tcl의 정규 표현식은 Perl의 정규 표현식이 만들어내는 것보다는 표현력이 약하다. 그러나 사용하는데 별 무리는 없을 것이다.

[HTML]

. 임의의 한 글자
* 바로 앞 글자 또는 부분문자열(substring)이 0번 이상 반복될 수 있음
+ 바로 앞 글자 또는 부분문자열이 1번 이상 반복될 수 있음
? 바로 앞 글자 또는 부분문자열이 존재하거나 존재하지 않을 수 있음
^ 문자열의 처음 위치(첫 글자 자체를 가리키는 것은 아님)
$ 문자열의 마지막 위치(마지막 글자 아님)
특수문자 특수문자를 정규 표현식에서의 의미가 아닌 문자 그 자체로 인식
(정규표현식) ()를 이용하여 표현식과 일치하는 문자열을 부분문자열로 간주
[문자열] []안의 문자들 중 임의의 한 글자(예제 참고)
정규표현식|정규표현식 두 정규 표현식 중 임의의 한 표현식

[/HTML]

정규 표현식은 regexp나 regsub와 같은 명령을 통해 사용될 수도 있고, 문자열 관련 명령 중의 패턴(pattern)으로도 사용될 수 있다. 다음에 나올 예는 regexp와 regsub가 각각 패턴의 일치성을 검사하거나 패턴을 치환하는 것이다.

regexp {[^a-z].*} The
regexp {[^a-z].*} them
regexp {[^a-z].*} 123

[] 내부 문자열의 첫 글자로 ^가 나오면 [] 내부의 문자열을 제외한 모든 글자가 해당되도록 되어 있으므로 위의 예는 a부터 z까지의 소문자를 제외한 글자로 시작하는 단어만 참(true)로 간주하여 패턴 매치(pattern match)의 결과를 1이나 0으로 돌려주게 된다. 그러므로 두번째 단어를 제외한 두 단어는 패턴 매치가 성공하여 1이 반환된다. 매치된 부분문자열을 변수에 저장할 수도 있는데, 다음의 예를 보자.

regexp {([A-Z][a-z]+) ([a-z]+)} “King died!” line noun verb

line에는 King died가 noun에는 King이 verb에는 died가 저장될 것이다. 옵션으로 -nocase와 -indices가 지원되는데, -nocase는 대소문자를 구별하지 않으면서 패턴 매치를 수행하는 옵션이고, -indices는 일치하는 부분문자열을 변수에 저장하는 대신, 부분문자열의 첫 글자와 마지막 글자의 인덱스(index 또는 offset)을 대입해준다. 이 옵션을 써서 위의 예를 실행하게 되면 line은 0 8, noun은 0 3, verb는 5 8을 가지게 될 것이다.

다음은 regsub를 이용해 잘못된 스펠링 teh를 the로 바꾸고 그 바뀐 문자열을 x에 저장하는 것이다.

regsub teh "I can't believe teh truth of teh events" the x
puts $x

결과는 예상과는 달리 앞 쪽의 teh만 제대로 고쳐질 것이다. 매번 teh가 나올 때마다 고치려면 -all옵션을 사용하면 된다.

2) format

C의 printf는 너무 유명해서 모르는 프로그래머가 없을만한 함수이다. printf의 최대 장점은 문자열의 format을 정해서 프로그래머가 원하는 형태로 출력이 가능하다는 점인데, Tcl에서는 이런 format을 위해 format명령과 scan명령을 제공한다. format 명령은 printf와 비슷하고 scan은 sscanf과 비슷하다.

puts [format "My name is %s and my age is %d." Terzeron 25]

이런 식으로 해서 ANSI C의 printf가 제공하는 모든 기능을 Tcl에서도 사용 가능하다. 거꾸로 sscanf도 가능한데, 다음의 예를 보자. scan의 결과는 매치되는 부분문자열의 개수이고, 마지막에 사용된 변수에 매치되는 부분문자열이 저장된다.

scan "My name is Terzeron and my age is 25." "My name is %s" name

3) 문자열 명령

드디어 문자열 자체를 다루는 명령들에 대해서 알아볼 시간이다. 앞에서도 언급했다시피 string 명령은 compare(비교), first(첫번째 매치되는 글자의 index), last(마지막으로 매치되는 글자의 index), index(그 인덱스에 위치한 글자), length(문자열의 길이), match(패턴 매치 결과), range(부분문자열), tolower(소문자로 바꾸기), toupper(대문자로 바꾸기), trim, trimleft, trimright(해당 글자를 문자열 앞쪽이나 뒤쪽에서 잘라내기), 등의 명령과 조합하여 사용해야 한다.

string length "hello"
string range "hello" 3 4
string compare "hello" "hello"
string compare "hello" "Hello"
string index "hello" 4
string first l "hello"
string last l "hello"
string match {[a-z]*} “hello”
string trim aaabbcccaaa a
string trimleft aaabbcccaaa a
string trimright aaabbcccaaa a
string toupper hello
string tolower Hello

위 예제를 한 줄씩 설명하기는 시간 낭비일테니까, 각자 직접 실행하여 결과를 얻어보길 바란다. 필자는 string match가 잘 되지 않아서 다소 애를 먹은 경험이 있다. 위 예제의 결과를 차례대로 나열해보면 이렇다. 5, lo, 0, 1, o, 2, 3, 1, bbccc, bbcccaaa, aaabbccc, HELLO, hello

4장 파일 입출력, 프로세스 관리

이전까지는 어떤 시스템에서도 잘 실행되었을 법한 명령들에 대해서 다루어 보았다. 그러나 파일 입출력이나 프로세스 문제는 시스템마다 다르기 때문에, 지금부터는 POSIX를 따르는 유닉스 시스템에서 실행되는 명령들에 대해서 다루어보기로 한다.

1) 시스템

디렉토리를 지정하는 방법으로 C shell에서 사용하는 ~도 사용가능하다. ~terzeron이라라고 하면 terzeron이라는 사용자의 홈 디렉토리를 의미하고, ~라고 하면 지금 프로그램의 실행자의 홈 디렉토리를 의미한다. cd 명령으로 디렉토리를 옮길 수 있고, pwd 명령은 현재 디렉토리를 확인할 수 있고, file 명령으로 디렉토리에 관한 문자열 작업을 가능하게 돕는다. file 명령의 옵션으로는 다음과 같은 것들이 있다.

[HTML]

dirname 파일이 위치한 디렉토리 이름
rootname 파일의 확장자를 제외한 이름 전체
extension 파일의 확장자
tail 파일의 전체 경로에서 디렉토리 부분을 제외한 나머지
atime 최종 접근 시간(access time)
mtime 최종 변경 시간(modified time)
size 파일의 바이트(byte) 크기
type 파일의 타입
readlink 심볼릭 링크(symbolic link)가 가리키는 파일의 이름
exists 파일이 존재하는가?
isdirectory 파일이 디렉토리인가?
isfile 파일이 일반 파일인가?
executable 실행가능한가?
readable 읽기가능한가?
writable 쓰기가능한가?
owned 프로그램 실행자의 소유인가?
stat 파일의 atime, ctime, dev, gid, ino, mode, mtime, nlink, size, uid를 키로 가지는 배열을 반환한다.
lstat stat과 같으나 심볼릭 링크에 대해서는 링크 자체의 정보를 반환한다.

[/HTML]

glob을 이용하면 shell에서처럼 *이나 ?같은 메타 문자를 사용할 수 있다. glob 명령으로 파일이름 리스트를 구하고 file 명령으로 각 파일에 대한 정보를 구하거나 테스트를 수행할 수 있다. 다음의 예는 /usr/include/sys 디렉토리에 존재하는 헤더 파일 중 x로 시작하는 이름을 가진 파일들을 읽어서 그 크기를 조사해보는 것이다.

set filelist [glob /usr/include/sys/x*.h]
foreach f $filelist {
puts “$f [file size $f]”
}

2) 파일 입출력
파일을 열고 닫고 읽고 쓰기 위해 제공되는 명령에는 open, close, gets, puts, read, flush, eof, seek, tell 등이 있다. 파일을 다루기 위해서 가장 먼저 해야 할 일은 파일을 여는(open) 작업이다. 파일을 열면 파일id가 반환되고 이 id를 가지고 읽고 쓰는 작업을 수행할 수 있는 것이다.

set filename /usr/include/limits.h
set f [open $filename r]
while {[gets $f line] >= 0} {
puts $line
}
close $f

이 예제는 가장 간단한 파일 입출력을 보여주고 있다. 한 줄씩 읽어서 line이라는 변수에 저장하고 다시 출력하고 있다. 이제 다른 파일로 복사하는 프로그램으로 고쳐보자. 위의 예에서는 읽기 위해서 모드(mode)를 r(읽기)로 주었는데, 이번에는 강제적으로 덮어쓰기 위해 w+(강제로 덮어쓰기) 모드로 열어야 한다.

set infile /usr/include/limits.h
set outfile ~/limits.h
set inf [open $infile r]
set outf [open $outfile w+]
while {[gets $inf line] >= 0} {
puts $outf $line
}
close $outf
close $inf

flush는 puts가 버퍼링을 하기 때문에 출력이 지연되는 것을 강제적으로 막기 위해 사용되는 명령이다. 위의 예제에서 puts 명령 다음에 flush $outf라고 써주게 되면 puts가 버퍼를 다 채우지 못했더라도 강제적으로 버퍼를 비워서 출력이 빠르게 진행된다. 이 밖에 파일의 변위(offset)를 가지고 특정 위치로 이동할 수 있도록 seek 명령이 제공되고, 현재 파일의 어느 부분을 읽거나 쓰고 있는지 알기 위해 tell 명령이 제공된다. 이렇게 랜덤한 접근을 하는 경우 파일의 끝을 알아야 하는데, 이럴 때 eof 명령으로 파일의 끝인가를 검사할 수 있다.
3) 프로세스 관리

프로세스는 파일 상태로 디스크에 존재하는 프로그램이 실행되어서 메모리에 올라와 있는 것을 말한다. 프로세스를 만들기 위해서는 exc 명령으로 프로그램을 실행시켜야 한다. 다음 예는 stdio.h에서 printf라는 단어를 포함하고 그 중에서 FILE이라는 단어를 포함하지 않는 줄을 찾아주는 프로세스를 만드는 것이다.

exec grep printf /usr/include/stdio.h | grep -v FILE

현재 실행되고 있는 Tcl 스크립트의 출력을 다른 프로그램(필터)으로 넘길 수 있는데, 이것은 파일 입출력에서 다루었던 open 명령을 이용하는 것이다. 다음 예는 stdio.h에서 printf 라는 단어가 나오는 모든 줄을 test라는 파일로 저장하는 것을 보인다.

set in [open /usr/include/stdio.h r]
set out [open {| grep printf > test} w]
while {[gets $in line] >= 0} {
puts $out $line
}
close $out
close $in

open 명령에 사용될 수 있는 파이프(pipe)나 입출력 재지정(redirection)을 위해 사용할 수 있는 기호에는 >>파일명(출력을 기존 파일에 덧붙임), >@파일id(파일id로 출력), >&파일명(표준출력과 표준에러를 파일로 보냄), 2>파일명(파일로 표준에러를 보냄), <파일명(파일로부터 표준입력을 받음), <<값(값으로부터 표준입력을 받음), <@파일id(파일id를 통해 표준입력을 받음) 이 있다. 이 기호들은 exec에서도 사용할 수 있다.

exec find / -name core -exec rm -f "{}" ; 2> /dev/null > result

find를 실행하게 되면 유저 권한으로는 들어갈 수 없는 디렉토리가 종종 있다. 그래서 이럴 때 뜨는 에러 메시지를 모아서 /dev/null로 보내는 방법을 이용한다. find의 실행 결과는 result라는 파일로 모은다.

close하기 전에 pid $out을 실행해보면 grep 프로세스의 프로세스 id가 출력된다. 그리고 pid라고 실행하면 현재 Tcl 스크립트나 tclsh의 프로세스 id가 출력된다.

프로세스를 종료할 때, exit를 통해서 상태값(status)을 반환할 수 있는데, 기본 상태값은 0이다. 물론 exit 명령 자체의 결과값은 없다.

4) 스크립트 실행하기

source 명령을 이용하면 C shell의 source 명령처럼 Tcl 스크립트를 불러다가 실행할 수 있다.

source /usr/local/lib/tcl8.1/history.tcl

5장 에러와 예외사항(exception)

에러(error)라는 표현은 독자들도 많이 알고 있을 것이지만, 예외사항(exception)은 Java가 널리 퍼지기 전에는 프로그래밍 이론 과목에서나 접했을 법한 용어이다. 예외사항은 아주 간단하게 말하면 에러의 상위집합(superset)이다. 다시 말해서 에러는 예외사항의 부분집합이다. 에러라고 하기에는 큰 문제가 발생한 것은 아니고 그렇다고 정상 종료도 아닌 결과가 예외사항이 되는 것이다.

예외사항은 Java에서처럼 발생시키는 것과 그것을 잡아서 처리한다는 개념이 존재하는데, 에러나 예외사항을 발생시키기 위해서는 error 명령와 return 명령을 사용할 수 있다. 반대로 발생한 예외사항을 감지해내기 위해서는 catch 명령을 사용해야 한다.

unset zzz

은 에러가 발생할 것이다. zzz라는 변수를 이전에 선언한 적이 없기 때문이다. 그러나 이렇게 발생한 에러를 잡으려면 다음과 같이 할 수 있다. message 변수를 지정하면 변수에 에러 메시지가 저장된다.

catch {unset x}
catch {unset x} message

에러를 발생시킬 때, 에러 메시지는 반드시 지정해야 하지만, 에러 정보와 에러 코드는 선택하게 되면 Tcl에서 지정해서 사용하는 errorInfo나 errorCode 변수에 저장할 수 있다. error 명령의 문법은 “error 에러메시지 에러정보 에러코드”이다.

error "can't unset the variable: no such variable"

예외사항을 발생시키는 것은 return 명령을 이용하는 것이다. return도 error 명령처럼 에러코드와 에러정보, 에러메시지등을 지정할 수 있는데, 문법은 “return -code 결과값 -errorinfo 에러정보 -errorcode 에러코드 에러메시지”이다. 결과값의 기본값은 0이다. 다음의 예는 전역 변수인 errorInfo와 errorCode를 이용해 return하는 것이다. 프러시져 내에서 실행되는 경우에는 errorInfo와 errorCode를 global 명령을 이용해 지역 변수처럼 사용할 수 있도록 처리해 주어야 한다는 사실을 잊지 않아야 한다.

return -code 4 -errorinfo $errorInfo -errorcode $errorCode

6장 기타

1) 배열

앞에서 Tcl에서의 배열이란 Perl에서의 해시 변수와 비슷하다고 언급한 적이 있다. 배열을 다루는 명령은 문자열 처리 명령처럼 array 명령에 다양한 옵션을 결합시켜서 사용해야 한다. 배열의 키를 모두 찾기 위해서는 names라는 옵션을, 배열의 키의 수를 구하기 위해서는 size 옵션을 사용한다.

array names my_array
array size my_array

배열에 대해서 검색을 할 수도 있는데, 이 때는 다음과 같은 형태로 가능하다. 배열을 만들고, search id를 구해서 배열에 원소가 아직 남아 있는지 검사하고 남아 있으면 매번 키를 구해다가 키를 이용해 배열을 다루게 된다. 검색이 끝나면 search에 관련된 정보를 반환한다.

set my_arr(1) hello
set my_arr(2) world
set my_arr(name) terzeron
set my_arr(age) 25
set search_id [array startsearch my_arr]
while {[array anymore my_arr $search_id]} {
set key [array nextelement my_arr $search_id]
puts $my_arr($key)
}
array donesearch my_arr $search_id

2) 정보(info)와 내부 명령

현재 Tcl 스크립트와 현재 버전의 Tcl/Tk의 정보를 볼 구할 수 있는 명령은 info이다. info 명령의 옵션을 다음과 같이 정리해 보았다.

[HTML]

args 프러시져명 프러시져의 아규먼트(argument)를 리스트로 반환한다.
body 프러시져명 프러시져의 몸체 부분을 반환한다.
cmdcount 현재 인터프리터(interpreter)에서 사용된 명령의 수를 반환한다.
commands 패턴 실행가능한 명령을 리스트로 반환한다. 패턴을 지정하는 경우에는 매치되는 명령만 반환할 수도 있다.
complete 명령 버전 8에서 추가된 옵션으로 명령이 완전한 명령인가를 검사한다.
default 프러시져명 아규먼트명 변수명 프러시져의 해당 아규먼트가 기본값을 가지는지 검사하여 변수에 결과값을 써준다.
exists 변수명 변수가 존재하는지 검사한다.
globals 패턴 전역 변수의 리스트를 반환한다. 패턴 매치도 가능하다.
hostname 현재 프로세스가 실행되고 있는 컴퓨터의 이름을 반환한다.
level 번호 현재 스택(stack)의 레벨를 반환한다.
library 현재 Tcl의 라이브러리의 절대 경로를 반환한다.
locals 패턴 지역 변수의 리스트를 반환한다.
nameofexecutable 현재 실행 프로그램(tclsh)의 절대경로를 반환한다.
patchlevel 패치된 레벨을 반환한다.
procs 패턴 현재 정의되어 있는 프러시져를 패턴 매치하면서 반환한다.
script 스크립트의 이름을 반환한다.
sharedlibextension 공유라이브러리의 확장자명을 반환한다.
tclversion Tcl의 버전을 반환한다.
vars 패턴 현재 사용가능한 변수명의 리스트를 반환한다. 패턴 매치도 가능하다.

[/HTML]

내부명령으로 사용할 수 있는 것은 rename, time, trace 등이 있다. rename은 변수나 프로시져의 이름을 바꿀 수 있는데, 바꾸는 것 뿐만 아니라 {}로 바꾸어서 사용하지 못하게 제거할 수도 있다. time은 스크립트을 반복 수행하여 그 평균 수행시간을 계산해준다. 다음과 같이 사용할 수 있다.

time test.tcl 300

trace 명령은 변수의 값 변화를 추적할 수 있는 명령이다. 다음의 예를 보면서 설명하도록 하자.

trace variable age w p_age1
trace vinfo age
trace vdelete age w p_age1

이 예는 age라는 변수에 대해서 쓰기 작업이 이루어지면 p_age1이라는 프러시져를 매번 호출하고, age에 대한 trace 정보를 구하고, 추적 작업을 종료하는 것이다.

3) 히스토리(history)

C shell에서와 같이 history 명령을 실행하면 여태까지 실행했던 명령들을 볼 수가 있다.

history

history 명령에 keep 옵션을 주어 특정 개수만큼 히스토리를 유지할 수도 있고, nextid 옵션을 주어 다음 명령이 히스토리에서 몇 번째 명령이 될 것인가를 알아볼 수도 있다. 또한 redo 옵션으로 저장된 명령을 다시 실행할 수도 있다. 히스토리는 substitute 옵션을 통해 아이템을 변경할 수 있다.

history redo
history redo 3

set x helo
history substitute lo llo -1

마지막 명령은 바로 이전 명령행에서 lo를 llo로 바꾸게 하는 것이다. 조심해야 할 것이 하나 있다. 버전 8에서는 이 옵션이 없어지고 대신 change 옵션이 추가되었는데, 이 옵션은 명령행 전체를 치환한다. 비슷한 기능을 하는 add 옵션도 추가되었다. 히스토리를 지우는 clear 옵션도 제공된다.

set x helo
history change {set x hello} -1

!!는 history redo의 단축어로 제공된다. !번호 같은 형식으로도 히스토리를 이용할 수가 있다. substitute대신에 ^old^new와 같은 형식을 이용할 수도 있다. 이런 단축어들은 C shell에 기반한 것이다.

4) 매뉴얼(manual)

tclsh에서는 매뉴얼을 볼 수가 있다. 다른 키워드와 겹치지만 않는다면 man 키워드 로 매뉴얼을 볼 수 있다. 그러나 history만 해도 유닉스의 명령과 같기 때문에 man history로는 Tcl명령인 history를 공부할 수가 없다. 이럴 때는

man -s n history

로 문제를 해결할 수 있을 것이다.

서울대학교 전산과학과 데이타베이스 연구실 조영일

: