TCL/TK 기본 문법 Tcltk 2016. 6. 20. 17:28

[펌] http://cafe.naver.com/gaury.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=13504


1.  TCL 기본

○ Tcl은 간단한 구조와 문법을 가지고 있어 배우고 사용하기 쉬운 스크립트 언어이다.

○ Tcl의 모델은 다른 언어들과 다른 면이 많으므로 기본 개념을 이해할 필요가 있다.


1.1.  Tcl 시작하기

○ Tcl을 설치하면 tclsh과 wish의 두 가지 shell 프로그램을 사용할 수 있다.

○ tclsh는 csh나 sh 등의 대용으로 사용할 수 있는 명령어 해석기이다.

○ wish는 Tk 명령어들을 해석할 수 있도록 확장된 Tcl 해석기이다.

○ GUI를 가진 응용 프로그램 개발을 위해 wish를 사용할 수 있다.

○ tclsh나 wish를 실행시키면 % 모양의 프롬프트가 나타난다.

○ 프롬프트가 나타나면 명령을 입력해 원하는 작업을 할 수 있다.

○ 여러 명령을 파일에 넣어 두고 실행시킬 수도 있다.

○ 파일의 명령을 실행시키는 방법에는 여러 가지가 있다.

○ 먼저 Tcl의 source 명령을 사용해 실행시킬 수 있다.

source filename

○ 쉘 스크립트와 같이 독립적인 스크립트로 작성할 수도 있다.

○ 스크립트로 작성하기 위해서는 소스 코드의 맨 첫 줄에 다음을 추가하여야 한다.

#!/usr/local/bin/tcl

또는

#!/usr/lcoal/bin/wish

○ Tcl/Tk가 설치된 디렉토리가 /usr/local/bin이 아닌 경우는 다른 경로를 지정해야 한다.

○ Tcl의 라이브러리 기능을 사용할 수 있다.

○ Tcl의 라이브러리 기능은 나중에 자세히 설명.


1.2.  Tcl 명령

○ Tcl 명령의 기본적인 형태는 다음과 같다.

command arg1 arg2 arg3 ...

○ 'command'는 내부 명령이나 Tcl 프로시져의 이름이다.

○ 명령어와 인자를 구분하기 위해 스페이스나 텝 문자를 사용한다.

○ 한 명령줄의 마지막을 나타내기 위해 newline이나 ';'을 사용한다.

○ 위에서 설명한 기본적인 문법은 하나의 인자에 여러 단어를 허용하는 grouping, 그리고 변수나 중첩 명령 호출과 함께 사용되는 치환을 이용해 확장시킬 수 있다.

○ 인자는 모두 문자열이다.


1.3.  Hello World

puts stdout {Hello, World!}

○ 이 예제에서 puts가 command이고 인자는 stdout가 {Hello, World!}의 두 개다.

○ puts 명령어는 I/O 스트림에 스트링을 출력하고 newline 문자를 출력한다.

○ stdout은 출력될 I/O 장치의 이름이다.

○ Hello, World!는 {}에 의해 Grouping되었다.


1.4.  변수

○ 변수에 값을 대입하기 위해서는 set 명령을 사용한다.

○ set 명령은 두 개의 인자를 받는다.

○ 첫 번째 인자는 변수의 이름이며 두 번째 인자는 값.

○ 변수 이름은 길이에 제한이 없으며 대소문자를 구별.

○ 변수 사용 이전에 미리 변수를 정의할 필요는 없다.

○ 변수로부터 값을 얻어내기 위해서는 변수 이름 앞에 $를 붙인다.

set var 5

=> 5

set b $var

=> 5

○ 변수 이름 앞에 $를 붙이는 것을 '변수 치환'이라고 한다.

○ 명령어 해석기는 $var를 5로 대체(substitute)하게 된다.


1.5.  명령 치환

○ 중첩된 명령은 ‘[ ]’로 둘러싸게 되는데 이를 '명령 치환'이라고 한다.

○ ‘[ ]’ 부분은 ‘[ ]’ 안에 포함된 명령의 실행 결과로 대체된다.

○ 일반 shell에서의 ‘`’와 비슷한 의미이다.

set len [string length inclab]

=> 6

○ 위의 예에서 string 명령은 스트링에 대한 여러 작업을 하는 명령이다.

○ 여기서는 inclab이라는 스트링의 길이를 얻어낸다.

○ 해석기는 [string length inclab]을 'string length inclab' 명령의 실행 결과인 6으로 대체하게 된다.


1.6.  수식

○ 수식을 계산하기 위해서는 expr 명령을 사용한다.

expr 4.8 / 3

=> 1.6

○ expr은 정수, 실수, boolean 값에 대한 연산을 처리한다.

○ boolean 값에 대한 연산은 0이나 1을 돌려준다.

○ 정수는 필요한 경우 실수로 변환된다.

○ 8진수는 수 앞에 0을 붙여 표시한다.

○ 16진수는 수 앞에 0x를 붙여 표시한다.

set len [expr [string length inclab] + 3]

=> 10

○ 위의 예에서처럼 eval문은 명령 치환과 함께 사용되는 경우가 많다.

○ eval 명령은 기본 내장 수학 함수들을 지원한다.

set pi [expr 2*asin(1.0)]

=> 3.14159

○ 유효 자릿수는 tcl_precision 변수 값에 원하는 유효 자릿수를 대입해 바꿀 수 있으며 기본 값은 6이다.

expr 1 / 3

=> 0

expr 1 / 3.0

=> 0.333333

set tcl_precision 17

=> 17

expr 1/3.0

=> 0.33333333333333331

○ Tcl에서 사용할 수 있는 연산자와 함수는 다음과 같다.

- -, ~, !: Unary minus, bitwise NOT, logical NOT

- * / % + -

- <<, >> : Left or Right shift

- < > <= >=

- == !=

- &, ^, |, &&, ||

- x?y:z

- acos(x): Arc-cosin of x

- asin(x): Arc-sine of x

- atan(x): Arc-tangent of x

- atan2(y, x): Rectangular (x, y) to polar (r, th). atan 2 gives th.

- ceil(x): Ceiling of x

- cos(x): Cosine of x

- cosh(x): Hyperbolic cosine of x

- exp(x): Exponential

- floor(x): Floor of x

- fmod(x, y): Floating point remainder of x/y

- hypot(x, y): sqrt(x*x + y*y)

- log(x): Natural log of x

- log10(x): Log base 10 of x

- pow(x, y): x to the y power

- sin(x): Sine of x

- sinh(x): Hyperbolic sine of x

- sqrt(x): Square root of x

- tan(x): Tangent of x

- tanh(x): Hyperbolic tangent of x

- abs(x): Absolute value of x

- double(x): x의 실수형 값

- int(x): x의 정수형 값

- round(x): x를 내림한 정수값


1.7.  역슬래쉬 치환

○ 지금까지 변수 치환과 명령 치환의 두 가지 치환에 대해 알아 보았다.

○ 마지막 형태의 치환은 역슬래쉬 치환이다.

○ 해석기에게 특별한 의미를 가지는 문자를 표현하기 위해 사용된다.

○ 예를 들어 $는 변수 치환을 위해 사용되므로 명령어 상에 $를 사용하면 해석기는 이를 변수 치환으로 해석하여 할 것이다.

○ 이러한 문제를 해결하기 위해 역슬래쉬 치환을 사용한다.

set dollar \$

=> $

set x $dollar

=> $

○ 문자의 ASCII 코드를 사용하고 싶을 때에는 \ 이후에 16진수나 8진수로 ASCII 코드를 넣는다.

○ 역슬래쉬 치환의 또다른 사용 예는 한 명령 줄이 너무 긴 경우 다음 줄과 연결됨을 표시하기 위해 한 줄의 마지막에 \을 쓰는 것이다.

set totalLength [expr [string length $one] + \

      [string length $two]]

○ 역슬래쉬 치환의 예는 다음과 같다.

- \a: Bell(0x7)

- \b: Backspace(0x8)

- \f: Form feed(0xc)

- \n: Newline(0xa)

- \r: Carriage return(0xd)

- \t: Tab(0x9)

- \v: Vertical tab(0xb)

- \\: \

- \0XX: 8진수

- \xhh: 16진수

- \c: c 문자


1.8.  Grouping

○ 여러 단어를 하나로 Grouping하기 위해서는 '{ }' 와 ‘“’가 사용된다.

○ 인용 부호를 사용하는 경우는 치환을 허용한다.

○ { }를 사용하는 경우는 치환이 일어나지 않는다.

set s Hello

=> Hello

puts stdout "The length of $s is [string length $s]."

=> The length of Hello is 5.

puts stdout {The length of $s is [string length $s].}

=> The length of $s is [string length $s].

○ 인용부호가 자주 사용되는 예는 format 문이다.

○ format 명령은 C의 printf 함수와 유사하다.

○ format 명령의 첫 번째 인자는 format specifier이다.

puts [format "Item: %s\t%5.3f" $name $value]


1.9.  프로시져

○ 프로시져를 정의하기 위해서는 proc 명령을 사용한다.

proc name arglist body

○ 첫 번째 인자는 새로 정의할 프로시져의 이름이다.

○ 프로시져의 이름은 대소문자를 구분한다.

○ 두 번째 인자는 프로시져에게 전달할 파라미터의 리스트이다.

○ 세 번째 인자는 프로시져의 몸체 부분이다.

○ 정의된 프로시져는 다른 내장 명령과 똑같은 방법으로 사용한다.

proc diag {a b } {

      set c [expr sqrt ($a * $a + $b * $b)]

      return $c

}

○ diag 프로시져는 직각 삼각형의 두 변을 주었을 때 빗변의 길이를 구하는 프로시져이다.

○ 변수 c는 이 프로시져에서만 사용되는 변수이다.

○ 변수 c를 이용하지 않고 바로 값을 계산해 리턴할 수도 있다.

return [expr sqrt ($a * $a + $b * $b)]

○ Tcl에서는 프로시져 가장 마지막 명령의 결과를 리턴하므로 사실 마지막 줄의 return 명령은 불필요하다.

proc diag {a b} {

      expr sqrt ($a * $a + $b * $b)

}


1.10.  While 루프

set a 1; set product 1

whiel {$a <= 10} {

      set product [expr $product * $i]

      incr a

}

set product

=> 3628800

○ ';'을 사용해 두 개 이상의 명령을 한 줄에 쓸 수 있다.

○ while 명령의 첫 번째 인자는 boolean 수식이다.

○ boolean 수식이 참인동안 두 번째 인자에서 정의한 몸체 부분을 수행하게 된다.

○ while의 첫 번째 인자에 expr은 쓰지 않아도 무방하다.

○ incr 명령은 변수의 값을 1 증가시킨다.

○ 예에서 while의 첫 번째 인자를 { }를 사용해 grouping한 것을 알 수 있다.

○ { }를 써서 grouping함으로써 치환으로 인한 문제를 방지한다.

set a 1; while $a<=10 {incr a}

○ 위의 예는 무한 루프를 돌게 된다.

○ 이 명령의 수행 이전에 치환이 일어나 위의 while 문장은 while 1<=10 {incr a} 와 같은 의미가 되어 버린다.


1.11.  주석

○ Tcl에서 주석을 포함하는 줄은 #으로 시작한다.

○ 한 줄의 뒷부분에 주석을 달기 위해서는 ;으로 한 줄을 마치고 #을 달 수 있다.

set lab inclab       ;# comment

○ 주석문은 \에 의해 다음줄에 계속될 수 있다.

○ 주석문 안의 ;은 아무런 의미가 없다.

#this is the start of a Tcl comment \

and some more of it; still in the comment


1.12.  Command line argument

○ Tcl 쉘은 명령행에서 준 인자를 argv 변수를 통해 스크립트에게 전달한다.

○ argc 변수에는 인자의 개수가 들어간다.

○ 스크립트의 이름은 argv0 변수에 들어간다.

set progname $argv0

set first [lindex $argv 0]

set seconf [lindex $argv 1]


2.  스트링과 패턴 매칭

○ 스트링은 Tcl에서 사용하는 기본 자료 구조이다.

○ Tcl은 스트링을 다루는 많은 명령들을 가지고 있다.

○ 스트링에서 특정 패턴을 찾아내는 패턴 매칭을 쉽게 할 수 있다.

○ Tcl은 Glob 매칭과 Regular Expression 매칭의 두 가지 패턴 매칭 방법을 제공한다.


2.1.  string 명령

○ string 명령의 기본 문법은 다음과 같다.

string operation stringvalue ?otherargs?

○ operation 인자는 string 명령으로 무엇을 할 것인지를 나타낸다.

○ stringvalue 인자는 스트링 값이다.

○ 그 외에 명령에 따라 인자가 더 올 수도 있다.

○ string 명령의 여러 형태는 다음과 같다.

- string compare str1 str2: 두 스트링을 비교한다. 같으면 0을 돌려 주고 str1이 사전식 순서로 str2보다 앞이면 -1을, 그리고 그 외의 경우는 1을 돌려 준다.

- string first str1 str2: str2에서 str1이 처음으로 나타나는 위치를 돌려 준다. str1이 str2에 포함되어 있지 않으면 -1을 돌려 준다.

- string index string index_num: index_num번째 문자를 돌려 준다.

- string last str1 str2: str2에서 str1이 마지막으로 나타나는 위치를 돌려 준다. str1이 str2에 포함되어 있지 않으면 -1을 돌려 준다.

- string length str: str에 포함된 문자수를 돌려 준다.

- string match pattern str: str에 pattern이 포함되어 있으면 1을 돌려 주고 그렇지 않으면 0을 돌려 준다.

- string range str i j: str의 i번째부터 j번째까지의 스트링을 돌려 준다.

- string tolower str: str을 모두 소문자로 바꾼 스트링을 돌려 준다.

- string toupper str: str을 모두 대문자로 바꾼 스트링을 돌려 준다.

- string trim str ?chars?: str의 양 끝에서 chars에 포함된 문자를 모두 지운다. chars는 기본값이 whitespace 문자들(space, tab 등)이다.

- string trimleft str ?chars?: trim의 경우와 동일하지만 str의 시작 부분에 있는 문자만 지운다.

- string trimright str ?chars?: trim의 경우와 동일하지만 str의 마지막 부분에 있는 문자만 지운다.

- string wordend str ix: ix번째 문자가 포함된 단어가 끝난 곳의 index를 돌려 준다.

- string wordstart str ix: ix번째 문자가 포함된 단어가 시작된 곳의 index를 돌려 준다.


2.2.  append 명령

○ 여러 스트링을 합친다.

set foo z

append foo a b c

=> zabc


2.3.  format 명령

○ format 명령은 C의 printf나 FORTRAN의 format 명령과 유사하다.

○ format 명령의 형식은 다음과 같다.

format spec value1 value2 ...

○ spec은 포맷 지정자이다.

○ spec은 화면에 찍힐 문자들과 키워드들로 구성된다.

○ 키워드는 %로 시작한다.

○ spec에는 보통 whitespace를 포함하므로 인용부호를 사용하여 grouping하는 것이 일반적이다.

○ spec에서 % 뒤에 붙여 사용할 수 있는 포맷 지정자로는 다음과 같은 것들이 있다.

- d: 정수

- u: 부호없는 정수

- i: 정수. 16진수나 8진수를 쓸 수 있다.

- o: 부호없는 8진수

- x 또는 X: 부호없는 16진수. x를 쓰면 결과가 소문자로 찍히고 X를 쓰면 대문자로 찍힌다.

- c: ASCII 문자

- s: 스트링

- f: 실수

- e 또는 E: 지수형 표현. a.bE+-c와 같은 형태

- g 또는 G: f나 e중 더 짧게 표현할 수 있는 형태로 표현


○ 위치 지정자는 a$의 형식으로 %와 포맷 지정자의 사이에 쓴다.

set lang 2

format "%${lang}\$s" one un uno

=> un


2.4.  scan 명령

○ scan 명령은 C의 sscanf 함수와 유사하다.

○ scan 명령의 문법은 다음과 같다.

○ scan string format var1 var2 ...

○ scanf는 [ ]를 사용해 입력 문자의 필터링이 가능하다.

scan abcABC {%[a-z]} result

=> 1

set result

=> abc


2.5.  스트링 매칭

○ string match 명령은 glob 형태의 패턴 매칭을 수행한다.

○ * 기호는 어떤 문자열과 대응된다.

○ ? 기호는 하나의 문자와 대응된다.

○ [ ]으로 문자를 묶으면 [ ] 안에 포함된 임의의 한 문자와 대응된다.

○ [ ]은 명령 치환을 위해 사용되는 기호이기도 하므로 치환을 방지하기 위해서는 { }를 이용해 grouping을 해 줘야 한다.

string match a* alpha

=> 1

string match ?? XY

=> 1

string match {[ab]*} cello

=> 0


2.6.  Regular Expression

○ Regular Expression을 사용하면 더 강력한 패턴 매칭이 가능하다.

○ Regular Expression은 패턴을 정의할 때 사용하는 구문이다.

○ Regular Expression에서 사용되는 문법은 다음과 같다.

- .: 어떤 임의의 한 문자와 대응된다.

- *: 이전 패턴 항목을 0 또는 1회 이상 반복

- +: 이전 패턴 항목을 1회 이상 반복

- ?: 이전 패턴 항목을 0 또는 1회 반복

- ( ): 패턴을 묶는다.

- |: 두 가지 패턴 중 임의의 하나와 대응

- [ ]: [ ] 안에 포함된 문자 중 임의의 하나아 대응. [ ] 안에 나오는 첫 번째 문자가 ^인 경우는 [ ] 안의 문자를 제외한 모든 다른 문자와 대응된다.

- ^: 문자열의 처음과 대응

- $: 문자열의 마지막과 대응

Regular Expression의 예를 들어 보자.

- ..: 임의의 두 글자

- [Hh]ello: Hello 또는 hello

- [^a-zA-Z]: 알파벳을 제외한 모든 문자

- ba*: b, ba, baa, baaa, baaaa, ...

- (ab)+: ab, abab, ababab, ...

- .*: 모든 스트링과 대응

- hello|Hello: Hello 또는 hello


2.7.  regexp 명령

○ regexp 명령은 regular expression을 이용한 패턴 매칭을 하는 명령이다.

○ regexp 명령의 문법은 다음과 같다.

regexp ?flags? pattern string ?match sub1 sub2 ... ?

○ string에서 pattern과 일치하는 부분이 발견되면 1을 돌려 주고 그렇지 않으면 0을 돌려 준다.

○ pattern 안에 [나 $를 포함하는 경우에는 치환이 발생하지 않도록 { }로 grouping을 하거나 [, $ 앞에 \을 붙여 역슬래쉬 치환을 이용하도록 한다.

○ match 이후의 인자는 옵션이다.

○ 발견된 패턴은 match 변수에 저장된다.

○ pattern에 포함되는 sub 패턴들은 match 다음에 오는 변수에 차례로 저장된다.

○ sub 패턴은 ( )로 구분된다.

○ flags는 옵션이다.

○ flags 인자에 올 수 있는 값은 다음과 같다.

- -nocase: 대소문자 구분을 하지 않는다.

- -indices: 이 플래그를 사용하면 match 이후의 변수에 스트링을 저장하는 대신 string 변수에서 해당 패턴이 시작하는 위치를 저장한다.

○ pattern이 -로 시작하는 경우는 플래그를 패턴과 구분하기 위해 --를 쓰면 된다.

set env(DISPLAY) brutus:0.1

regexp {([^:]*):} $env(DISPLAY) match host

=> 1

set match

=> corvina:

set host

=> corvina

○ 위의 예는 DISPLAY 환경 변수를 brutus:0.1로 저장한 후 그 중 호스트 이름 부분만을 추출하는 프로그램이다.

3.  Tcl의 자료 구조

○ Tcl의 기본적인 자료 구조는 스트링이다.

○ Tcl에는 스트링 이외에 리스트와 배열의 두 가지 자료 구조가 더 있다.

○ 리스트는 스트링으로 구현된다.

○ 리스트의 구조는 스트링의 문법에 의해 정의된다.

○ 문법은 일반 명령의 경우와 같으며, 사실은 명령도 리스트의 일종이다.

○ 배열은 index를 가지는 변수이다.

○ index도 역시 스트링 값이므로 배열은 하나의 스트링(index)으로부터 다른 스트링(배열 원소의 값)에의 대응이라고 생각할 수 있다.

○ 작은 자료의 경우에는 리스트가 적당하며 큰 자료의 경우에는 배열이 적당하다.


3.1.  변수의 사용

set var {the value of var}

=> the value of var

set name var

=> var

set name

=> var

set $name

=> the value of var

○ set 명령에서 값을 주지 않으며 변수의 값만을 출력한다.

○ 변수 이름 앞에 $를 붙이는 경우와 붙이지 않는 경우를 구분하여야 한다.

○ unset 명령을 사용해 변수를 없앨 수 있다.

unset varName varName2 ...

○ info exist 명령을 통해 변수가 존재하는지 확인할 수 있다.

if ![info exists foobar] {

      set foobar 0

} else {

      incr foobar

}


3.2.  리스트

○ 다른 언어에서의 리스트 자료 구조와 달리 Tcl에서의 리스트는 스트링의 다른 표현에 불과하다.

○ Tcl 리스트는 Tcl 명령어와 동일한 구조를 가지고 있다.

○ 리스트는 whitespace로 구분된 스트링이다.

○ { }나 ""가 grouping을 위해 사용될 수 있다.

○ 리스트는 스트링으로 표현되기 때문에 수행 속도가 느려질 수 있으므로 긴 리스트를 사용하는 것은 피해야 한다.

○ 자주 사용되는 긴 길이의 리스트는 배열로 바꾸는 것이 바람직하다.

○ 리스트와 관련된 명령어는 다음과 같다.

- list arg1 arg2 ...: 리스트를 생성한다.

- lindex list i: list의 i번째 원소를 얻어 낸다.

- llength list: list의 길이를 알아 낸다.

- lrange list i j: 리스트의 i번째부터 j번째 원소들을 돌려 준다.

- lappend listVar arg arg ...: listVar에 원소를 추가한다.

- linsert list index arg arg ...: list의 index번째 원소 앞에 새로운 원소를 추가한다.

- lreplace list i j arg ag ...: list의 i부터 j까지의 원소를 새로운 원소들로 바꾼다.

- lsearch mode list value: list에서 value와 일치하는 원소를 찾아 위치를 돌려 준다. mode는 -exact, -glob, -regexp 중의 하나이며 -glob가 기본값이다.

- lsort switches list: list를 소트한다. switches에는 소트 방법을 주게 되는데, -ascii, -integer, -real, -increasing, -decreasing, -command 중의 하나이다.

- concat arg arg arg: 여러 리스트를 하나로 합친다.

- join list joinString: list의 원소를 joinString으로 구분해 합친다.

- split string splitChars: splitChars를 원소간의 경계로 생각해 string을 리스트 원소로 쪼갠다.


3.3.  리스트 만들기: list, lappend, concat

○ list 명령은 인자들을 원소로 가지는 새로운 리스트를 생성한다.

굳이 앞에 list라는 키워드를 사용하지 않아도 whitespace로 구분된 스트링은 리스트를 생성하므로 list 명령은 필요없는 기능으로 여겨질 수 있지만 list 명령은 만들어진 리스트를 돌려 주므로 리스트가 제대로 생성되었는지를 확인하는 데 사용할 수 있다.

set x {1 2}

=> 1 2

set x

=> 1 2

list $x \$ foo

=> {1 2} {$} foo

○ 위의 예제에서처럼 리스트 원소를 구분하기 위해 { }를 써서 나타내 주므로 만들어진 리스트를 확인할 수 있다.

○ lappend 명령은 리스트의 마지막에 원소를 추가하기 위해 사용한다.

lappend new 1 2

=> 1 2

lappend new 3 "4 5"

=> 1 2 3 {4 5}

set new

=> 1 2 3 {4 5}

○ concat 명령은 리스트들을 합치는 데 사용된다.

set x {4 5 6}

set y {2 3}

set z 1

concat $z $y $x

=> 1 2 3 4 5 6

○ list 명령과 concat 명령을 구분할 필요가 있다.

○ list나 lappend 명령은 리스트 구조를 그대로 유지하는 반면 concat 명령은 list 구조를 한 겹 벗겨 낸 후 합치게 된다.


3.4.  리스트의 원소 얻기: llength, lindex, lrange

○ llength 명령은 리스트의 원소 개수를 돌려 준다.

llength {a b {c d} "e f g" h}

=> 5

○ lindex 명령은 리스트의 특정 원소를 돌려 준다.

○ lindex 명령은 첫 번째 인자로 index를 받는다.

○ index는 0에서부터 시작한다.

○ index 대신에 end라는 키워드를 사용해 리스트의 마지막 원소를 지칭할 수 있다.

○ end 키워드는 lindex, linsert, lrange, lreplace 명령에 유효하다.

lindex {1 2 3} 0

=> 1

lindex {1 2 3} end

=> 3

○ lrange 명령은 리스트의 특정 범위의 원소를 돌려 준다.

lrange {1 2 3 {4 5}} 2 end

==> 3 {4 5}


3.5.  리스트의 수정: linsert, lreplace

○ linsert 명령은 리스트의 특정 위치에 원소를 삽입한다.

○ index가 0 이하이면 리스트의 맨 처음에 삽입한다.

○ index가 리스트의 크기보다 크면 리스트의 마지막에 추가된다.

○ index가 0보다 크고 리스트의 크기보다 작으면 index번째 원소의 앞에 삽입된다.

○ lreplace는 리스트의 특정 범위에 있는 원소들을 새로운 원소들로 변경한다.

○ 새로운 원소를 인자로 주지 않으면 특정 범위의 원소가 삭제된다.

○ linsert나 lreplace는 기존의 리스트를 변경하지는 않고 변경된 리스트를 돌려 주기만 한다.

linsert {1 2} 0 new stuff

=> new stuff 1 2

set x [list a {b c} e d]

=> a {b c} e d

lreplace $x 1 2 B C

=> a B C d

lreplace $x 0 0

=> {b c} e d


3.6.  리스트에서 원소 찾기

○ lsearch는 리스트에서 특정 원소의 index를 돌려 준다.

○ 존재하지 않는 경우는 -1을 돌려 준다.

○ glob 모드의 패턴 매칭이 기본 값이다.

○ 패턴 매칭 방법을 바꾸기 위해서는 다른 패턴 매칭을 나타내는 -exact나 -regexp 등의 옵션을 사용하면 된다.

lsearch {here is a list} l*

=> 3

○ lreplace 명령은 새로 넣고 싶은 원소가 이미 리스트에 포함되어 있는지를 확인하기 위해 보통 lsearch 명령과 같이 쓰인다.

○ 다음 프로시져는 리스트에서 특정 원소를 삭제한다.

proc ldelete { list value } {

      set ix [lsearch -exact $list $value]

      if {$ix >= 0} {

              return [lreplace $list $ix $ix]

      } else {

              return $list

      }

}


3.7.  리스트의 소트

○ lsort 명령을 사용하면 다양한 방법으로 리스트를 소팅할 수 있다.

○ 세 가지 기본적인 소팅 방법은 -ascii, -integer, -real이다.

○ -increasing이나 -decreasing은 오름차순으로 소트할 것인지 또는 내림차순으로 소트할 것인지를 나타낸다.

○ 기본 옵션은 -ascii -increasing이다.

○ 특별한 목적의 소팅을 위해 자신만의 소팅 함수를 제작할 수도 있다.

○ 예를 들어 이름의 리스트를 갖고 있고 이름은 세 글자로 이루어진 리스트라고 가정하자.

○ 기본적인 소트는 이름의 첫 번째 글자로 소팅할 것이다.

○ 다음 예제는 이름의 마지막 글자로 소팅한다.

proc mycompare {a b} {

      set alast [lindex $a end]

      set blast [lindex $b end]

      set res [string compare $alias $blast]

      if ($res != 0} {

              return $res

      } else {

              return [string compare $a $b]

      }

}

set list {{Eung Do Kim} {Hyo Jun Im} {Woo Joo Lee}}

=> {Eung Do Kim} {Hyo Jun Im}  {Woo Joo Lee}

lsort -command mycompare $list

=> {Hyo Jun Im} {Eung Do Kim} {Woo Joo Lee}


3.8.  split와 join 명령

○ split 명령은 스트링을 입력받아 특정 문자를 구분자로 생각하여 리스트를 만들어 낸다.

○ split 명령은 사용자 입력을 Tcl이 처리하기 쉬운 형태로 바꾸는 데 사용할 수 있다.

set line {welch:*:3116:100:Brent Welch:/usr/welch:/bin/csh}

split $line :

=> welch * 3116 100 {Brent Welch} /usr/welch /bin/csh

set line {this is "not a tcl list}

index $line 1

=> is

index $line 2

=> unmatched open quote in list

lindex [split $line] 2

=> "not

○ split 명령의 기본 구분자는 공백 문자이다.

○ 구분 문자가 두 개 이상 연달아 오면 그 사이는 빈 리스트로 표시하게 된다.

set line "\tHello, world."

split $line \ ,.\t

=> {} Hello {} world {}

○ join 명령은 split 명령의 반대이다.

○ join 명령은 리스트를 스트링으로 변환한다.

join {1 {2 3} {4 5 6}} :

=> 1:2 3:4 5 6


3.9.  배열

○ 배열은 Tcl에서 사용하는 중요한 자료 구조이다.

○ 배열의 인덱스는 스트링이다.

○ 배열은 내부적으로 해쉬 테이블을 사용해 구현되었으므로 탐색 시간이 짧다.

○ 배열의 인덱스 부분은 괄호로 구분된다.

○ 인덱스는 명령 substituion을 포함해 어떤 스트링 값도 가질 수 있다.

○ 배열의 원소값도 역시 변수 치환을 통해 얻어낼 수 있다.

set arr(0) 1

for (set i ) {$i <= 10} {incr i} {

      set arr($i) [expr $i * $arr([expr $i-1])]

}

○ 위의 예제는 arr(x)의 값을 x!로 만드는 프로그램이다.

○ 첫줄의 arr(0) 초기화는 arr을 배열 변수로 만든다.

○ 어떤 변수를 배열 변수로도 사용하고 일반 변수로도 사용해서는 안된다.

○ 예를 들어 다음은 오류이다.

set arr 3

==> can't set "arr": variable is array

○ 배열 변수의 한 원소는 일반 변수와 똑같이 사용할 수 있다.

○ 인덱스가 복잡한 경우는 인덱스의 다른 부분을 구분하기 위해 ','를 사용할 수 있다.

○ 괄호는 grouping 기능을 제공하지 않으므로 배열의 인덱스에 공백이 들어가서는 안된다.

○ 인덱스 부분에 공백을 넣기 위해서는 역슬래쉬 치환이나 grouping을 사용해야 한다.

set index {I'm asking for trouble}

set arr($index) {I told you so.}

○ 배열의 이름만을 다른 변수에 저장하고 사용할 수도 있다.

set name TheArray

=> TheArray

set ${name}(xyz) {some value}

=> some value

set x $TheArray(xyz)

=> some value

set x $(name}(xyz)

=> TheArray(xyz)

set x [set ${name}(xyz)]

=> some value


3.10.  array 명령

○ array 명령은 배열 변수에 대한 정보를 돌려 준다.

○ 배열의 원소들을 모두 검색하기 위해 array 명령을 사용할 수도 있다.

○ array names 명령을 foreach 루프와 함께 사용해 배열의 모든 원소에 대해 어떤 작업을 하게 할 수 있다.

foreach index [array names arr] {command body}

○ array names 명령에 의해 리턴되는 원소의 순서는 해쉬 테이블의 구현 방법에 따라 달라지므로 임의적이다.

○ array get과 array set 명령은 배열과 리스트간의 변환을 위해 사용된다.

○ array get 명령에 의해 리턴되는 리스트는 짝수개의 원소를 가지게 되는데, 먼저 배열의 인덱스 부분이 나오고 그 다음에 그 값이 나오는 순서로 모든 원소에 대해 반복하게 된다.

○ array set 명령에게 주는 인자도 array get에 의해 리턴되는 배열의 구조와 같아야 한다.

set fruit(best) kiwi

set fruit(worst) peach

set fruit(ok) banana

array get fruit

=> ok banana best kiwi worst peach


4.  순서제어 명령

○ Tcl에서의 실행 순서 제어도 역시 명령어를 통해 이루어진다.

○ 루프를 위한 명령으로는 while, foreach, for가 있다.

○ 조건문으로는 if와 switch가 있다.

○ 에러 처리 명령으로는 catch가 있다.

○ 순서를 잘 제어하기 위한 다른 명령으로 break, continue, return, error가 있다.

○ 순서제어 명령 뒤에는 보통 나중에 수행할 명령들을 모은 몸체 부분이 따르게 된다.

○ 몸체 부분은 치환을 방지하기 위해 { }로 묶어야 한다.

○ 순서제어 명령은 마지막에 수행한 명령의 리턴값을 돌려주게 된다.

○ { }의 또하나의 용도는 Newline 문자를 포함한 스트링을 묶을 수 있다는 것이다.

○ if, for, while 등의 명령은 참/거짓을 돌려주는 boolean 수식을 포함한다.

○ if, for, while 명령은 내부적으로 expr 명령을 자동으로 수행하기 때문에 expr을 명시적으로 적어주지 않아도 된다.


4.1.  If Then Else

○ if는 가장 기본적인 조건 명령이다.

○ 조건이 참이면 뒤에 따르는 첫 번째 몸체 부분을 실행하고 그렇지 않으면 두 번째 몸체 부분을 실행한다.

○ 이 명령의 문법은 다음과 같다.

if boolean then body1 else body2

○ then과 else 키워드는 옵션이므로 사용하지 않아도 된다.

if {$x == 0} {

      puts stderr "Divide by zero!"

} else {

      set slope [expr $y/$x]

}

○ Newline 문자는 한 명령의 끝을 나타내지만 grouping 되어 있는 경우는 예외이다.

○ if 명령의 첫 번째 인자는 boolean 수식이다.

○ 이후에 코드를 수정하게 되는 경우를 대비해 조건문과 몸체 부분은 아무리 간단해도 항상 { }로 묶어 주는 것이 좋다.

○ elseif 명령은 이어진 조건문을 구성하는 데 사용된다.

if {$key < 0} {

      incr range 1

} elseif {$key == 0} {

      return $range

} else {

      incr range -1

}

○ 그러나 이을 조건문의 내용이 길어지는 경우는 switch 명령을 쓰는 것이 좋다.


4.2.  Switch

○ switch 명령은 수식의 값에 따라 어떤 특정 몸체를 수행하게 하는 데 사용된다.

○ 조건은 간단한 비교문에서 패턴 매칭에 이르기까지 다양하게 사용할 수 있다.

○ switch문의 문법은 다음과 같다.

○ switch flags value pat1 body1 pat2 body2 ...

○ switch flags value { pat1 body1 pat2 body2 ... }

○ flags에는 다음과 같은 4가지 값 중의 하나가 들어갈 수 있다.

-exact: 정확하게 일치하는 패턴만을 찾는다.

-glob: glob 형태의 패턴 매칭을 수행한다.

-regexp: regular expression 패턴 매칭을 수행한다.

--: 플래그를 사용하지 않거나 플래그 부분의 마지막을 의미

○ 패턴과 몸체 부분을 grouping하는 방법에는 여러 가지가 있다.

○ 첫 번째 방법은 패턴과 몸체 부분을 하나의 인자로 묶는 것이다.

switch -exact -- $value {

      foo {doFoo; incr count(foo) }

      bar {doBar; return $count(foo) }

      default {incr count(other) }

}

○ 마지막 패턴인 default는 다른 일치되는 패턴이 없는 경우에 사용함을 의미한다.

○ default가 마지막 패턴이 아닌 경우는 default라는 문자열과 일치하는지를 검사하게 된다.

○ 두 번째 방법은 grouping을 하지 않는 것이다.

○ 이 경우 Newline 문자를 무시하게 하기 위해 역슬래쉬 치환을 사용해야 할 것이다.

○ { }로 grouping을 하지 않았으므로 치환이 이루어진다.

switch -regexp -- $value \

      ^$key {body1} \

      \t### {body2} \

      {[0-9]*} {body3}

○ 마지막 방법은 치환을 허용하면서 Newline 문자를 허용하기 위해 '"'를 쓰는 것이다.

switch -glob -- $value "

      $(key}* { puts stdout \"Key is $value\" }

      X* -

      Y* - {takeXorYaction $value }

"

○ 몸체 부분에 -를 쓰면 다음 패턴의 몸체 부분을 수행하게 된다.

○ 주석은 명령어가 나올 수 있는 위치에 써야 하므로 switch 문에서 주석을 달 때에는 유의해야 한다.

○ 다음은 주석을 잘못 단 예이다.

switch -- $value {

      # this comment confuses switch

      pattenr { # this comment is ok}

○ 위의 예에서 Tcl 해석기는 첫 번째 주석 부분을 패턴으로 인식하게 된다.


4.3.  Foreach

○ foreach 명령은 리스트에 있는 모든 값들을 어떤 변수에 대입해 가면서 명령 몸체 부분을 반복 수행하게 된다.

○ foreach 명령의 문법은 다음과 같다.

foreach loopVar valueList commandBody

○ valueList 부분에 직접 리스트를 넣을 수 있다.

set i 1

foreach value {1 3 5 7 11 13 17 19 23} {

      set i [expr $i*value]

}

set i

=> 111546435

○ 다음 예제에서는 리스트 값을 가지는 변수를 사용한다.

# argv is set by the Tcl shells

foreach arg $argv {

      switch -regexp -- $arg {

              -foo {set fooOption 1}

              -bar {barRelatedCommand}

              ([0-9]*) {scan -%d $arg intValue

      }

}


4.4.  While

○ while 명령은 테스트 부분과 몸체 부분의 두 인자를 가진다.

      while booleanExpr body

○ while 명령은 반복적으로 booleanExpr 부분을 검사해 참인 경우에 몸체 부분을 반복 수행한다.

○ while을 쓸 때는 치환이 잘못 일어나는 경우가 없도록 주의하여야 한다.

set i 0; while $i<10 {incr i}

○ 위의 예에서 $i는 변수 치환이 일어나 0<10 과 같은 조건문이 되어 항상 참이 되어 버린다.

○ 다음의 예는 { }를 사용해 변수 치환을 방지하였다.

set i 0; while {$i < 10} {incr i}


4.5.  For

○ for 명령은 C의 for 명령과 유사하다.

○ for 명령은 4개의 인자를 취한다.

for initial test final body

○ initial은 루프를 돌기 이전에 수행할 초기화 부분이다.

○ test는 루프의 반복 여부를 결정하기 위한 조건이다.

○ final은 루프의 수행이 끝난 후에 수행할 명령이다.

for {set i 0} {$i < 10} {incr i 3} {

      lappend aList $i

}

set aList

=> 0 3 6 9


4.6.  Break와 Continue

○ break 명령은 현재 수행 중인 루프에서 빠져 나간다.

○ continue는 다음 루프를 즉시 수행한다.

○ Tcl에는 goto 명령이 없다.


4.7.  Catch

○ catch 명령은 명령 수행 중 발생한 오류를 잡아 내기 위해 사용한다.

○ catch 명령의 문법은 다음과 같다.

catch command ?resultVar?

○ command는 수행할 몸체 부분이다.

○ 두 번째 인자는 명령 수행의 결과나 오류 메시지를 저장할 변수의 이름이다.

○ catch 명령은 오류없이 수행된 경우 0을 돌려 주고 오류가 발생한 경우는 1을 돌려 준다.

○ 다음은 catch 명령을 수행해 오류 처리를 한 예이다.

if [catch {

      command1

      command2

      command3

} result] {

      global errorInfo

      puts stderr $result

      puts stderr "*** Tcl TRACE ***"

      puts stderr $errorInfo

} else {

      #command body ok, result of last command is in result

}


4.8.  Error

○ error 명령은 catch로 잡아내지 않는 경우 스크립트를 종료시키는 오류를 임의로 발생시킨다.

○ error 명령의 문법은 다음과 같다.

error message ?info? ?code?

○ message는 catch 명령에게 전달될 에러 메시지이다.

○ info는 errorInfo 전역 변수에 넣을 값이다.

○ 보통 errorInfo는 보통 에러가 발생한 위치에서의 스택의 상태를 수집하기 위해 사용된다.

○ info 인자를 설정하지 않으면 error 명령 수행 상태가 errorInfo에 들어가게 된다.

proc foo {} {

      error bogus

}

foo

=> bogus

set errorInfo

=> bogus

      while executing

“error bogus"

      (procedure "foo" line 2)

      invoked from within

"foo"

○ code 인자는 오류의 종류에 대한 설명이 들어 간다.

○ code 인자는 errorCode라는 변수에 저장된다.


4.9.  Return

○ return 명령은 프로시져로부터 종료하기 위해 사용된다.

○ return 명령은 프로시져의 수행 중간에 프로시져 수행을 마치기 위해 사용된다.

5.  프로시져와 스코프

○ 프로시져는 자주 사용되는 일련의 명령들을 묶어 쉽게 사용할 수 있게 해 준다.

○ 각 프로시져는 변수에 대한 새로운 스코프를 가지게 된다.

○ 변수의 스코프란 그 변수가 정의되는 명령의 영역을 의미한다.

○ 본 절에서는 Tcl의 proc 명령과 변수의 스코프에 대해 살펴 본다.


5.1.  proc 명령

○ Tcl 프로시져는 proc 명령으로 정의한다.

○ proc 명령은 세 인자를 받는다.

proc name params body

○ name은 프로시져의 이름이다.

○ 프로시져의 이름은 대소문자를 구분하며 어떤 문자도 포함할 수 있다.

○ params는 파라미터 이름의 리스트이다.

○ body는 프로시져의 몸체 부분이다.

○ 정의된 프로시져는 다른 Tcl 명령과 똑같은 방식으로 사용한다.

○ 프로시져가 돌려주는 값은 프로시져의 마지막 명령이 수행한 명령의 결과값과 같다.

○ 특정 값을 돌려 주기 위해 return 명령을 사용할 수도 있다.

○ 프로시져의 파라미터 이름 리스트는 기본값을 가질 수 있다.

proc p2 {a {b 7} {c -2} } {

      expr $a / $b + $c

}

p2 6 3

=> 0

○ 프로시져 p2는 1개, 2개 또는 3개의 인자로 불릴 수 있다.

○ 만약 1개의 인자만을 주어 호출한다면 b와 c는 각각 기본값인 7과 -2를 가지게 된다.

○ 만약 2개의 인자만을 준다면 c만 기본값을 가지게 되고 나머지는 주어진 인자를 사용한다.

○ 프로시져는 args 키워드를 사용해 임의 개수의 파라미터를 사용하게 할 수도 있다.

proc argtest {a {b foo} args} {

      foreach param {a b args} {

              puts stdout "\t$param = [set $param]"

      }

}

argtest 1

=> a = 1

    b = foo

    args =

argtest 1 2

=> a = 1

    b = 2

    args =

argtest 1 2 3

=> a = 1

    b = 2

    args = 3

argtest 1 2 3 4

=> a = 1

    b = 2

    args = 3 4


5.2.  rename으로 명령어의 이름을 바꾸기

○ rename 명령은 명령어의 이름을 변경한다.

○ rename은 기존 프로시져에 기능을 추가하기 위해 사용할 수 있다.

rename foo foo.orig

○ 이렇게 하면 foo 프로시져를 새로 정의할 때 foo.orig 프로시져를 호출할 수 있게 된다.

○ rename의 두 번째 인자로 빈 스트링을 줌으로써 명령어를 사용할 수 없게 만들 수도 있다.

rename exec { }


5.3.  스코프

○ Pascal 등의 언어에서는 중첩된 프로시져의 경우 정의된 프로시져 내에서만 프로시져를 사용할 수 있지만, Tcl에서는 다른 프로시져에 중첩되어 정의된 프로시져도 어느 곳에서나 사용할 수 있다.

○ 변수 이름과 프로시져 이름은 다른 이름 공간을 사용하므로 프로시져와 변수에 같은이름을 사용하더라도 상관없다.

○ 각 프로시져 내에서 정의된 변수는 그 프로시져 안에서만 사용할 수 있다.

○ 프로시져가 리턴되면 그 변수는 더 이상 유효하지 않다.

○ 프로시져의 외부에서 정의된 변수는 upvar나 global 스코프 명령어를 사용하지 않으면 프로시져 내에서 사용불능.

○ 프로시져 외부의 이름과 일치하는 변수가 프로시져 내에서 다시 정의되면 외부 변수는 프로시져 내에서의 값 변경에 영향을 받지 않는다.

set a 5

set b -8

proc p1 {a} {

      set b 42

      set {$a < 0} {

              return $b

      } else {

              return $a

      }

}

p1 $b

=> 42

p1 [expr $a*2]

=> 10

○ 위의 예제에서 외부 변수 a는 프로시져 p1의 인자인 a와는 다르다.

○ 외부에서 정의된 변수 b도 내부에서 정의된 b 변수와는 다르다.


5.4.  global 명령

○ 프로시져 외부에서 정의된 변수를 프로시져 내부에서 사용하기 위해서는 global 키워드를 사용해 프로시져 내에서 사용할 수 있도록 하여야 한다.

○ global 명령의 문법은 다음과 같다.

global varName1 varName2 ...

○ global 명령은 외부 변수를 사용하고자 하는 프로시져의 내부에 포함되게 된다.

○ global 명령은 프로시져 내부에서만 유효하므로 여러 개의 프로시져에서 외부 변수를 사용하고자 하는 경우는 모든 프로시져에서 global 명령을 사용해야 한다.

○ 변수가 정의되기 이전에 global 명령을 사용해도 상관없지만, 이 경우는 변수가 정의되는 순간에 모든 프로시져에서 변수에 접근할 수 있게 된다.

○ 다음 예제는 랜덤하게 발생된 숫자의 상태를 기억하기 위해 global 변수를 사용한다.

proc randomInit { seed } {

      global rand

      set rand(ia) 9301;# Multiplier

      set rand(ic) 49297;# Constant

      set rand(im) 233280;# Divisor

      set rand(seed) $seed;# Last result

}

proc random {} {

      global rand

      set rand(seed) \

              [expr ($rand(seed)*$rand(ia) +

                      $rand(ic)) % $rand(im)]

      return [expr $rand(seed)/double($rand(im))]

}

proc randomRange { range } {

      expr int([random]*$range)

}

randomInit [pid]

=> 5049

random

=> 0.517687

random

=> 0.217177

randomRange 100

=> 17


5.5.  전역 상태 유지를 위해 배열 사용하기

○ Tcl 배열은 인덱스 값에 아무런 제한이 없기 때문에 무척 유연하게 사용할 수 있는 자료 구조이다.

○ 배열의 한 좋은 사용예는 다른 언어의 레코드처럼 관계있는 변수들을 한데 묶는 것이다.

○ 배열 이름에 global 명령을 사용하면 배열을 구성하는 모든 원소가 global 스코프를 가지게 되므로 무척 편리하다.

○ 예를 들어 Tk 응용 프로그램을 개발한다고 할 때 전역적으로 관리되어야 하는 변수를 하나의 배열에 넣어두고 프로시져에서는 그 배열에 대해서만 global 명령을 사용해 간단하게 전역 변수를 접근할 수 있게 된다.

○ 다음 예는 어떤 물체의 위치를 추적하기 위해 배열을 사용한다.

proc ObjInit { o x y } {

      global obj

      set obj($o, x) $x

      set obj($o, y) $y

      set obj($o, dist) [expr sqrt($x * $x + $y * $y)]

}

proc ObjMove { o dx dy } {

      global obj

      if ![info exists obj($o, x)] {

              error "Object $o not initialized"

      }

      incr obj($o, x) $dx

      incr obj($o, y) $dy

      set obj($o, dist) [expr sqrt($obj($o, x) * $obj($o, x) + \

              $obj($o, y) * $obj($o, y))]

}

○ 위의 예제는 전역 상태 변수로서 배열 obj를 사용하고 있다.


5.6.  upvar명령을 이용한 “Call by Name” 구현

○ 프로시져에게 변수의 값이 아닌 이름을 넘겨 주고 싶은 경우는 upvar 명령을 사용한다.

○ 보통 이 명령은 배열 변수에 사용된다.

○ upvar 명령의 문법은 다음과 같다.

upvar ?level? varName localVar

○ level 인자는 옵션으로서 기본값은 1로, Tcl 호출 스택의 한 단계 위를 의미한다.

○ level 인자에 숫자를 주면 그 숫자만큼 호출 스택의 단계를 넘어간 스코프를 의미하게 된다.

○ level 인자를 #number의 형태로 주게 되면 어떤 절대값을 가지는 특정 스코프를 의미한다.

○ #0은 global 스코프를 의미하므로 global foo 명령은 다음 명령과 동일한 의미가 된다.

upvar #0 foo foo

○ 상위 단계 스택의 변수는 일반 변수, 또는 배열 변수의 원소값, 또는 배열의 이름일 수가 있다.

○ 변수가 일반 변수이거나 배열 변수의 원소값인 경우 지역 변수는 일반 변수로 취급된다.

○ 변수가 배열의 이름인 경우는 지역 변수가 배열로 취급된다.

○ 다음 예제의 프로시져 PrintByName은 변수의 이름을 인자로 주었을 때 그 값을 출력해 준다.

proc PrintByName { varName } {

      upvar $varName var

      puts stdout "$varName = $var"

}

○ upvar 명령은 incr 명령을 개선하기 위해 사용할 수 있다.

○ incr 명령은 변수가 존재하지 않는 경우에 에러를 발생시킨다.

○ 다음 예제는 변수가 존재하지 않는 경우 변수를 새로 생성하는 새로운 incr 프로시져이다.

proc incr { varName {amount 1}} {

      upvar $varName var

      if [info exists var] {

              set var [expr $var + $amount]

      } else {

              set var $amount

      }

      return $var

}

○ upvar 명령은 배열에 대해 사용할 수도 있다.

○ 다음 예제는 upvar 명령을 사용해 스택을 구현하고 있다.

proc Push { stack value } {

      upvar $stack S

      if ![info exists S(top)] {

              set S(top) 0

      }

      set S($S(top)) $value

      incr S(top)

}

proc Pop { stack } {

      upvar $stack S

      if ![info exists S(top)] {

              return {}

      } 

      if {$S(top) == 0} {

              return {}

      } else {

              incr S(top) -1

              set x $S($S(top))

              unset S($S(top))

              return $x         }       }

6.  Eval

○ 어떤 변수나 명령을 해석하기 위해서는 치환을 필요로 한다.

○ 좀 더 고급 기능의 치환은 eval이나 subst, 그리고 after, uplevel, send 명령에 의해 수행된다.


6.1.  리스트로 명령어 조합하기

○ eval 명령은 Tcl 해석기에게 또다른 호출을 하게 만든다. / 명령어를 동적으로 조합하기 위해서는 eval 명령을 사용해야 한다.

○ 예를 들어 다음과 같은 명령을 조합해 두고 나중에 수행시키고 싶은 경우를 생각해 보자.

puts stdout "Hello, World!"

○ 이러한 경우에는 다음과 같이 하면 된다.

set cmd {puts stdout "Hello, World!"}

=> puts stdout "Hello, World!"

# sometime later...

eval $cmd

=> Hello, World!

○ 외부에 출력할 스트링이 string이라는 변수에 저장되어 있다고 가정해 보자.

○ eval 명령의 수행 시점에서 string 변수에 값이 없다고 하자.

set string "Hello, World!"

set cmd {puts stdout $string}

unset string

eval $cmd

=> cant't read "string": no such variable

○ 위와 같은 상황을 해결하기 위해서는 명령어를 list 명령을 사용해 조합하면 된다.

set string "Hello, World!"

set cmd [list puts stdout $string]

=> puts stdout {Hello, World!}

unset string

eval $cmd

=> Hello, World!

○ 다음은 잘못된 예이다.

set cmd "puts stdout $string"

=> puts stdout Hello, World!

eval $cmd

=> bad argument "World!": should be "nonewline"

○ concat는 리스트 구조를 유지하지 않으므로 문제가 된다.

set cmd [concat puts stdout $string]


6.2.  eva에서 수행하는 concat 기능 이용

○ eval 명령은 하나 이상의 인자를 받는 경우에 자동으로 concat 명령을 수행하게 된다.

eval list1 list2 list3 ...

○ concat 명령의 효과는 모든 리스트들을 하나의 리스트로 합치는 것이다.

○ 새로운 단계의 리스트 구조가 추가되지는 않는다.

○ 이러한 기능은 명령어가 여러 조각으로 분리되어 있는 경우에 유용하다.

○ 이러한 형태의 eval 명령은 프로시져의 args와 같이 사용하는 것이 일반덕이다.

○ 옵션 인자를 다른 명령에게 전달하기 위해 args 파라미터를 사용한다.

○ 다른 명령어를 호출할 때 eval을 사용하면 $args가 concat 기능에 의해 합쳐지므로 명령어에서 올바른 인자로 파악할 수 있게 되는 것이다.

○ 이러한 경우를 Tk의 예에서 살펴보도록 한다. / Tk에서 버튼을 만들기 위해서는 다음과 같은 형태의 명령을 수행한다.

button .foo -text Foo -command foo

○ 버튼이 생성된 후에는 pack 명령을 이용해 화면에 보이게 하여야 한다.

pack .foo -side left

○ 다음은 제대로 동작하지 않는다.

set args {-text Foo -command foo}

button .foo $args

=> unknown option "-text Foo -command foo"

○ 문제는 $args가 리스트 값으로서 button은 args를 구성하는 값 전체를 하나의 파라미터로 인식하게 된다는 것이다.

○ 문제를 해결하기 위해서는 args를 구성하는 원소 각각을 하나의 파라미터로 인식하게 만들어야 한다.

eval button .foo $args

=> .foo

○ 다음 예는 이러한 eval의 기능을 사용해 버튼을 만들고 보여주는 프로시져이다.

proc PackedButton {path txt cmd {pack {-side right}} args} {

      eval {button $path -text $txt -command $cmd } $args

      eval {pack $path} $pack

}

○ PackedButton에서 pack과 args는 모두 리스트 파라미터로서 명령어의 일부를 구성하게 된다.


6.3.  Uplevel 명령

○ uplevel 명령은 eval과 유사하지만 현재 프로시져와는 다른 스코프에서 명령어를 수행하게 된다.

○ 이것은 새로운 제어 구조를 정의하는 데에 유용하다.

○ uplevel 명령의 문법은 다음과 같다.

uplevel level command

○ level 파라미터의 의미는 upvar 명령의 경우와 동일하다.


7.  UNIX에서 작업하기

○ 여기서는 Tcl로 어떻게 프로그램을 수행시키며 파일 시스템에 접근하는지를 살펴 본다.

○ 여기서 소개하는 명령은 UNIX를 기초로 하여 만들어졌지만 DOS나 Macintosh 등에서도 Tcl 해석기가 구현되어 있으므로 플랫폼에 무관하게 사용할 수 있다.


7.1.  exec 명령으로 UNIX 프로그램 수행하기

○ exec 명령은 Tcl 스크립트에서 UNIX 명령을 수행한다.

○ 다음 예를 살펴 보자.

set d [exec date]

○ 이 경우 exec 명령의 수행 결과는 표준 출력으로 출력된 결과가 된다.

○ 만약 수행한 프로그램이 표준 오류 출력으로 결과를 찍거나 0이 아닌 상태 코드 값을 가지게 되는 경우는 에러를 유발시킨다.

○ exec 명령은 I/O redirection과 pipeline 구문을 완벽히 지원한다.

○ 다음은 exec 명령에서 사용할 수 있는 여러 문법을 나열하였다.

- keepnewline: 결과값의 마지막에 있는 newline 문자를 없애지 않는다.

- |: pipeline

- |&: 표준 에러 출력으로도 파이프라인을 한다.

- < fileName: fileName 파일로부터 입력을 받는다.

- <@ fileId: fileId의 id를 가지는 I/O 스트림으로부터 입력을 받는다.

- << value: 주어진 값으로부터 입력을 받는다.

- > fileName: fileName 파일에 결과를 쓴다.

- 2> fileName: fileName 파일애 표준 에러 출력 결과를 쓴다.

- >& fileName: 표준 출려과 표준 에러 출력 결과를 모두 fileName 파일에 쓴다.

- >> fileName: fileName 파일에 표준 출력 결과를 덧붙인다.

- 2>> fileName: fileName 파일에 표준 에러 출력 결과를 덧붙인다.

- >>& fileName: fileName 파일에 표준 출력과 표준 에러 출력 결과를 덧붙인다.

- >@ fileId: fileId id를 가지는 I/O 스트림에 표준 출력 결과를 쓴다.

- 2>@ fileId: fileId id를 가지는 I/O 스트림에 표준 에러 출력 결과를 쓴다.

- >&@ fileId: fileId id를 가지는 I/O 스트림에 표준 출력과 표준 에러 출력 결과를 쓴다.

- &: 백그라운드로 수행한다. 결과값은 프로세스 ID가 된다.

○ Tcl 쉘 프로그램은 기본적으로 Tcl 명령이 아닌 명령을 수행할 때 UNIX 프로그램으로 인식해 수행하도록 되어 있다.

○ 이러한 기능을 없애려면 다음과 같이 한다.

set auto_noexec anything


7.2.  파일 시스템

○ file 명령은 파일의 상태를 알 수 있는 여러 명령을 제공한다.

○ 다음은 file 명령의 여러 형태를 나열한 것이다.

- file atime name: 가장 최근에 파일에 접근한 시간을 10진수의 스트링으로 돌려 준다.

- file dirname name: name 파일이 위치하는 디렉토리 이름을 돌려 준다.

- file executable name: 실행 가능한 파일인 경우 1을 돌려 주고 그렇지 않은 경우 0을 돌려 준다.

- file exists name: 파일이 존재하는 경우 1을 돌려 주고 그렇지 않은 경우 0을 돌려 준다.

- file isdirectory name: name 파일이 디렉토리인 경우 1을 돌려 주고 그렇지 않은 경우 0을 돌려 준다.

- file isfile name: name 파일이 디렉토리, 심볼릭 링크, 장치가 아닌 순수 파일인 경우 1을 돌려주고 그렇지 않으면 0을 돌려 준다.

- file mtime name: 파일의 최근 수정 시간을 돌려 준다.

- file owned name: 현재 사용자가 파일의 소유자이면 1을 돌려 주고 그렇지 않으면 0을 돌려 준다.

- file readable name: 파일의 내용 읽기가 허가되어 있으면 1을 돌려 주고 그렇지 않으면 0을 돌려 준다.

- file readlink name: 심볼링 링크 name의 내용을 돌려 준다.

- file root name: 확장자를 제외한 부분을 돌려 준다.

- file extension name: 확장자 부분만을 돌려 준다.

- file size name: 파일의 크기를 바이트 단위로 계산해 돌려 준다.

- file stat name var: 파일의 상태를 var 배열에 넣는다. var 배열에 정의되는 원소들은 atime, ctime, dev, gid, ino, mode, mtime, nlink, size, type, uid이다.

- file tail name: 파일의 경로를 제외한 파일 이름 부분만 돌려 준다.

- file type name: 파일의 형태를 돌려 준다. 파일의 형태는 file, directorhy, characterSpecial, blockSpecial, fifo, link, socket 중의 하나이다.

- file writable name: 파일 쓰기 허가가 되어 있으면 1을 돌려 주고 그렇지 않으면 0을 돌려 준다.

○ 다음 예제는 두 파일의 수정 시간을 비교하기 위해 file mtime 명령을 사용한다.

proc newer { file1 file2 } {

      if ![file exists $file2] {

              return 1

      } else {

              # assume file1 exists

              expr [file mtime $file] > [file mtime $file2]

      }

}

○ 다음의 makedir 예제는 새로운 디렉토리를 생성할 필요가 있는지 검사하기 위해 file 명령을 사용한다.

○ makedir은 자신을 재귀적으로 호출한다.

proc makedir { pathname } {

      if {[file isdirectory $pathname]} {

              return $pathname

      } else if {[file exists $pathname]} {

              error "Non-directory $pathname already exists."

      } else {

              # Recurse to create intermediate directories

              makedir [file dirname $pathname]

              exec mkdir $pathname

              return $pathname

      }

}

○ 가장 일반적인 file 명령의 옵션은 stat와 lstat이다.

○ stat와 lstat는 세 번째 파라미터로 상태 정보를 받을 배열 변수를 가진다.

○ 다음의 fileeq 예제는 두 파일이 똑같은 file을 가리키는지를 찾는다.

proc fileeq { path1 path2 } {

      file stat $path1 stat1

      file stat $path2 stat2

      expr [$stat1(ino) == $stat2(ino) && \

              $stat1(dev) == $stat2(dev)]

}


7.3.  입출력 명령

○ 다음은 입출력을 위한 명령이다.

- open what ?access? ?permissions?: 파일이나 파이프라인에 대한 스트림 ID를 돌려 준다.

- puts ?-nonewline? ?stream? string: 스트링을 쓴다.

- gets stream ?varname?: 한 줄을 읽는다.

- read stream ?numBytes?: numBytes 만큼의 바이트를 읽는다.

- read -nonewline stream: 스트림으로부터 모든 데이터를 읽고 마지막의 newline 문자를 없앤다.

- tell stream: 현재 검색 중인 위치를 돌려 준다.

- seek stream offset ?origin?: 검색 위치를 offset으로 설정한다. origin은 start, current, end 중의 하나이다.

- eof stream: end-of-file 상태를 질의한다.

- flush stream: 스트림의 버퍼을 쓴다.

- close stream: I/O 스트림을 닫는다.


7.4.  파일 열기

○ open 명령은 입출력을 위해 파일을 연다.

○ open 명령의 리턴값은 I/O 스트림의 ID이다.

○ open의 결과를 저장해 두었다가 stdout이나 stdin, stderr를 사용했던 부분에 대신 사용하면 된다.

○ open 명령의 기본적인 문법은 다음과 같다.

open what ?access? ?permissions?

○ what 파라미터는 파일이나 파이프라인의 이름이다.

○ access 파라미터는 짧은 문자의 나열로 쓸 수도 있고 POSIX 접근 flag 형태로 쓸 수도 있다.

○ access 파라미터를 짧은 문자 나열 형태로 쓸 때 그 형식은 다음과 같다.

- r: 파일에서 내용을 읽을 수 있도록 파일을 연다. 파일은 존재하는 것이어야 한다.

- r+: 읽기와 쓰기를 위해 파일을 연다. 파일은 존재하는 것이어야 한다.

- w: 파일에 쓰기 위해 연다. 존재하는 파일인 경우는 덮어 쓰고 존재하지 않으면 생성한다.

- w+: 읽기와 쓰기를 위해 파일을 연다. 존재하는 파일인 경우는 덮어 쓰고 존재하지 않으면 생성한다.

- a: 쓰기를 위해 파일을 연다. 파일은 존재하는 것이어야 하며 쓴 데이터는 파일의 뒤에 덧붙여진다.

- a+: 읽기와 쓰기를 위해 파일을 연다. 파일은 존재하는 것이어야 하며 쓴 데이터는 뒤에 덧붙여진다.

○ access 파라미터를 POSIX 플래그 형태로 쓰는 경우 구문은 다음과 같다.

- RDONLY: 읽기를 위해 파일을 연다.

- WRONLY: 쓰기를 위해 파일을 연다.

- RDWR: 읽기와 쓰기를 위해 파일을 연다.

- APPEND: 파일에 덧붙이기 위해 연다.

- CREAT: 파일이 존재하지 않으면 새 파일을 생성한다.

- EXCL: CREAT와 같이 쓰면 파일이 이미 존재하는 것이어서는 안 된다.

- NOCTTY: 터미널 장치가 컨트롤 터미널이 되는 것을 막는다.

- NONBLOCK: 파일을 여는 중에 블록되지 않는다.

- TRUNC: 파일이 존재하면 끝을 자른다.

○ access 파라미터는 기본적으로 read 값을 가진다.

○ permission은 파일이 생성되는 경우에 기본적으로 가질 접근허가 비트를 의미하는 것으로 기본값은 0666이다.

○ permission 비트의 의미에 대해서는 UNIX의 chmod 매뉴얼을 참조하면 된다.

○ 다음 예는 POSIX 형태의 파라미터를 사용해 파일을 여는 예를 보여 준다.

set fileId [open /tmp/bar {RDWR CREAT} ]

○ 보통 파일을 열 때에는 에러 발생 유무를 확인해야 한다.

○ 다음 예제는 catch를 사용해 파일을 열 때에 발생한 오류를 확인한다.

if [catch {open /tmp/data r} fileId] {

      puts stderr "Cannot open /tmp/data: $fileId"

} else {

      # Read and process the file, then ...

      close $fileId

}

○ catch는 에러가 발생한 경우에 1을 돌려 주고 그렇지 않으면 0을 돌려 준다.

○ catch의 두 번째 인자는 결과값을 받을 변수의 이름이다.

○ 파이프라인 문자인 '|'를 첫 번째 인자로 사용해 프로세스 파이프라인을 열 수 있다.

○ 다음 예제는 UNIX의 sort 프로그램을 사용해 password 파일을 소트한 후에 split 명령을 사용해 결과 라인을 리스트 원소로 바꾼다.

set input [open "|sort /etc/passwd" r]

set contents [split [read $input] \n]

close $input

○ r+ 접근 모드를 사용하면 파일에 대한 읽기와 쓰기가 모두 가능하게 열 수 있다.

○ 파이프라인을 사용할 때에는 버퍼에 유의해야 한다.

○ puts 명령이 수행된 후라도 버퍼에는 아직 데이터가 남아 있을 수 있다.

○ flush 명령을 사용하면 버퍼에 남아있는 데이터를 모두 출력한다.


7.5.  파일 읽기와 쓰기

○ UNIX의 표준 I/O 스트림은 항상 열려져 있다.

○ 기본적으로 열려 있는 스트림의 이름은 stdin, stdout, stderr이다.

○ 다른 스트림은 open 명령을 사용해 연다.

○ puts 명령은 출력 스트림에 스트링과 newline 문자를 출력한다.

○ -nonewline 옵션을 사용하면 마지막에 newline 문자를 출력하지 않는다.

puts -nonewline "Enter value: "

set answer [gets stdin]

○ gets 명령은 한 줄의 입력을 받는다.

○ gets 명령은 두 가지 형태로 사용할 수 있다.

○ gets의 첫 번째 형태는 위의 예제에서처럼 I/O 스트림으로부터 한 줄을 읽어 그 값을 돌려 주는 것이다.

○ gets는 마지막의 newline 문자를 없애 주며 파일의 끝에 도달한 경우는 빈 스트링을 돌려주게 된다.

○ 빈 줄과 파일의 끝을 구분하기 위해서는 eof 명령을 사용해야 한다.

○ gets 명령에 두 번째 인자를 주게 되면 결과가 두 번째 인자에 쓰여진 변수에 들어가게 되며 리턴값은 스트링의 길이가 된다.

○ 파일의 끝에 다다른 경우는 -1을 돌려 준다.

○ 다음 예제는 파일의 처음부터 끝까지 읽는 루프의 예이다.

while {[gets $stream line] >= 0} {

      #Process line

}

close $stream

○ read 명령은 스트림에서 데이터를 블록 단위로 읽는다.

○ 파일을 블록 단위로 읽으면 수행이 더 효율적이 된다.

○ read 명령도 두 형태로 사용할 수 있다.

○ numBytes 파라미터를 주지 않으며 파일 전체의 내용을 돌려 준다.

○ -nonewline 옵션을 주면 마지막 newline 문자가 지워진다.

○ numBytes 파라미터를 주면 numBytes 만큼의 바이트를 읽는다.

○ numBytes 파라미터와 -nonewline 옵션은 함께 사용할 수 없다.

○ 다음 예제는 read 명령을 사용해 파일의 내용을 읽는 예제이다.

foreach line [split [read $stream] \n] {

      # Process line

}

close $stream

○ 일반적인 크기의 파일에서 gets를 사용하는 것보다 위의 예에서처럼 read 명령을 사용하는 것이 10% 정도 더 빠르다.

○ 위의 예제에서 read는 파일 전체의 내용을 읽고 split 명령이 read를 줄 단위로 쪼개게 된다.

○ seek와 tell 명령을 사용하면 I/O 스트림의 랜덤한 위치를 접근할 수 있다.

○ 각 스트림은 seek offset이라고 불리는 스트림에서의 위치를 유지하고 있다.

○ 파일에서 읽거나 쓰면 읽거나 쓴 바이트만큼이 seek offset에서 증가된다.

○ tell 명령은 현재의 seek offset 값을 돌려 준다.

○ seek 명령은 seek offset 값을 설정한다.

○ close 명령은 I/O 스트림에 연결된 시스템 자원을 모두 해제하므로 중요한 명령이다.

○ 파일을 닫지 않으면 프로세스가 끝날 때 자동으로 닫힌다.

○ 오래동안 수행되는 프로세스에서 파일을 닫지 않으면 운영체제의 자원을 낭비하게 되므로 유의하여야 한다.


7.6.  현재 디렉토리- cd와 pwd

○ 모든 UNIX 프로세스는 파일을 찾을 때 기본위치로 삼는 현재 디렉토리라는 것을 가지고 있다.

○ pwd 명령은 현재 디렉토리의 위치를 돌려 준다.

○ cd 명령은 현재 디렉토리를 변경한다.


7.7.  glob를 사용해 파일 이름 찾기

○ glob 명령은 패턴을 이용해 파일 이름을 찾는다.

○ glob 명령의 문법은 다음과 같다.

glob ?flags? pattern ?pattern? ...

○ 패턴 파라미터의 문법은 string match 명령의 파라미터와 유사하다.

- *: 0 이상 개의 문자와 대응된다.

- ?: 하나의 문자와 대응된다.

- [abc]: abc 문자 집합과

- {a,b,c}: a,b,c 중의 하나와 대응된다.

- 기타 다른 문자는 같은 문자와 대응된다.

-nocomplain 옵션을 사용하면 일치하는 파일이 없는 경우 빈 스트링을 돌려 준다.

-nocomplain 옵션을 사용하지 않으면 일치하는 파일이 없는 경우 에러를 발생시킨다.

proc FindFile {startDir namePat} {

      set pwd [pwd]

      if [catch {cd $startDir} err] {

              puts stderr $err

              return

      }

      foreach match [glob -nocomplain -- $namePat]{

              puts stdout $startDir/$match

      }

      foreach file [glob -nocomplain *] {

              if [file isdirectory $file] {

                      FindFile $startDir/$file $namePat

              }

      }

      cd $pwd

}

○ 위의 예제에 나온 FindFile 프로시져는 파일 시스템의 하위 디렉토리를 순차적으로 검색해 원하는 파일을 찾는다.


7.8.  exit와 pid 명령

○ exit 명령은 스크립트를 종료한다.

○ exit 명령은 스크립트를 수행하던 모든 UNIX 프로세스도 종료시킨다.

○ exit 명령에 정수 인자를 주면 그 인자가 프로세스의 종료 상태가 된다.

○ pid 명령은 현재 프로세스의 프로세스 ID를 돌려 준다.

○ 이 값은 수행할 때마다 변하므로 난수 발생시 seed 값으로 활용할 수 있다.


7.9.  환경 변수

○ 환경 변수는 UNIX 프로세스와 연결된 스트링 값을 가지는 변수들이다.

○ 프로세스의 환경 변수는 env 배열을 사용해 접근할 수 있다.

○ 환경 변수의 이름은 배열의 인덱스가 되면 그 값이 환경 변수의 값과 같게 된다.

○ env 배열 값을 바꾸면 그 변경된 값은 환경 변수에도 반영된다.

○ 환경 변수는 자식 프로세스에게 상속된다.

○ 다음 프로그램은 환경 변수의 값을 화면에 출력해 준다.

proc printenv { args } {

      global env

      set maxl 0

      if {[llength $args] == 0} {

              set args [lsort [array names env]]

      }

      foreach x $args {

              if {[string length $x] > $maxl} {

                      set maxl [string length $x]

              }

      }

      incr maxl 2

      foreach x $args {

              puts stdout [format "%*s = %s" $maxl $x $env($x)]

      }

}

printenv USER SHELL TERM

=>

USER = welch

SHELL = /bin/csh

TERM = tx

8.  스크립트 라이브러리

○ 라이브러리는 유용한 프로시져들을 묶어서 다른 응용 프로그램에서 사용할 수 있게 하는 데 사용된다.

○ 커다란 스크립트 응용 프로그램을 만들 때에는 간단한 메인 스크립트를 작성하고 기타 함수는 라이브러리로 만드는 것이 좋다.

○ 이처럼 큰 응용 프로그램을 라이브러리 단위로 쪼개면 각 라이브러리는 필요할 때에만 메모리에 올라오므로 수행 효율을 향상시킬 수 있다.

○ 라이브러리를 사용하는 Tcl 프로그램을 작성할 때에는 코드 변환에 주의하여야 한다.

○ Tcl에는 형식화된 모듈 시스템이 없기 때문에 다른 패키지에서 프로시져와 전역 변수의 충돌을 막기 위해 코드 변환이 필요하다.


8.1.  unknown 명령

○ Tcl 라이브러리 기능은 unknown 명령에 의해 사용할 수 있다.

○ Tcl 해석기가 해석할 수 없는 명령을 만나게 되는 경우는 그 명령을 인다로 하여 unknown 명령을 호출하게 된다.

○ unknown 명령은 Tcl로 구현되어 있으므로 해석 불가능한 명령을 해석하는 새로운 unknown 명령을 만들 수 있다.

○ 여기서는 init.tcl 파일에 기본적으로 구현되어 있는 unknown 명령의 동작을 설명한다.

○ 라이브러리의 위치는 info library 명령을 사용해 알 수 있다.

○ 라이브러리 기능을 사용하기 위해 tclsh나 wish는 처음에 시작될 때 다음의 명령을 수행하게 된다.

source [info library]/init.tcl


8.2.  tclindex 파일

○ unknown 명령은 존재하지 않는 명령을 빨리 찾기 위해 인덱스를 사용한다.

○ 스크립트 라이브러리를 생성한 후에는 라이브러리에 어떤 프로시져가 등록되어 있는지를 기록한 인덱스를 생성하여야 한다.

○ auto_mkindex 프로시져는 인덱스를 생성하고 tclIndex라는 파일에 그 내용을 기록한다.

○ 만약 본 교재에 소개된 모든 예제가 /usr/local/tcl/examples/라는 디렉토리 밑에 있다면 다음 명령을 수행해 인덱스를 생성할 수 있다.

auto_mkindex /usr/local/tcl/examples *.tcl

○ 다음 예제는 라이브러리 인덱스를 안전하게 생성하는 프로시져이다.

○ 이 프로시져는 인덱스 파일을 처음부터 다시 생성한다.

proc Library_UpdateIndex {libdir} {

      if ![file exists $libdir/tclIndex] {

              set doit 1

      } else {

              set age [file mtime $libdir/tclIndex]

              set doit 0

              # Changes to directory may mean files were deleted

              if {[file mtime $libdir] > $age} {

                      set doit 1

              } else {

                      # Check each file for modification

                      foreach file [glob $libdir/*.tcl] {

                              if {[file mtime $file] > $age} {

                                      set doit 1

                                      break    } } } }

      if {$doit} {

              auto_mkindex $libdir *.tcl     }     }


8.3.  라이브러리 사용하기: auto_path

○ 스크립트 라이브러리를 사용하기 위해서는 unknown 명령에게 어디를 찾아 볼것인지를 알려 주어야 한다.

○ unknown 명령은 찾을 디렉토리를 auto_path라는 변수에 넣어 둔다.

○ auto_noloca 명령은 unknown 명령 메커니즘을 취소시킨다.


8.4.  자동 로드

○ tclIndex 파일을 살펴 보면 auto_index라는 배열이 정의된 것을 알 수 있다.

○ 각 배열의 한 원소는 스크립트 라이브러리를 정의한다.

○ tclIndex 파일의 줄을 하나 살펴 보면 다음과 같다.

set auto_index(Bind_Interface) "source $dir/bind_ui.tcl"

○ tclIndex 파일을 읽을 때 $dir은 tclIndex 파일을 포함하는 디렉토리 이름으로 대체된다.

9.  Tk 기초

○ Tk는 윈도우 프로그래밍을 위한 툴킷이다.

○ Tk는 X 윈도우 시스템을 위해 디자인되었지만 매킨토시와 MS-Windows에도 포팅되었다.

○ Tk는 widget을 생성하고 관리할 수 있는 Tcl 명령들을 제공한다.

○ widget은 GUI에서 어떤 특정한 모양과 동작을 가지는 윈도우를 지칭한다.

○ widget과 window라는 용어는 자주 혼용되어 사용된다.

○ widget 형태에는 버튼, 스크롤바, 메뉴, 텍스트 윈도우 등이 포함된다.

○ Tk는 또한 일반적으로 그림을 그리는 데 사용되는 캔버스라는 widget을 제공한다.

○ X 윈도우 시스템은 윈도우의 계층적인 구성을 지원하는데, Tk도 이러한 특성을 따른다.

○ 응용 프로그램에서 윈도우의 계층 구조라는 것은 하나의 메인 윈도우가 있으며 그 안에 여러 개의 자식 윈도우가 포함된다는 것을 의미한다.

○ 자식 윈도우도 또한 그 안에 여러 윈도우를 포함할 수 있다.

○ UNIX 파일 시스템에서 디렉토리가 다른 디렉토리와 파일을 포함하는 데 사용되는 것과 마찬가지로 윈도우의 계층 구조에서도 윈도우는 다른 윈도우를 포함하는 데 사용될 수 있다.

○ widget은 형상 관리자(geometry manager)에 의해 그 크기와 위치가 조절된다.

○ 형상 관리자가 widget에 대해 알게 되기 전에는 widget은 화면에 나타나지 않는다.

○ Tk에서는 여러 가지 형상 관리자를 사용할 수 있다.

○ 본 교재에서는 주로 packer 형상 관리자를 사용한다.

○ 형상 관리자에서 잘 사용되는 기법은 프레임 widget을 다른 widget을 포함하는 데 사용하는 것이다.

○ 하나 이상의 widget이 생성되고 프레임 widget에 형상 관리자에 의해 배치된다.

○ Tk를 사용해 만든 프로그램은 이벤트에 의해 동작하게 된다.

○ 이벤트는 bind명령을 통해 Tcl 명령과 연결된다.

○ X 프로토콜에 의해 정의된 이벤트에는 키보드와 마우스 입력을 포함한 여러 가지가 있다.

○ Tk widget은 기본적인 이벤트 처리기를 내장하고 있으므로 모든 사항을 프로그래밍할 필요는 없다.

○ 이벤트 바인딩은 전역 바인딩, 클래스 바인딩, 인스턴스 바인딩으로 나뉜다.

○ 클래스의 예로 모든 버튼을 나타내는 Button을 들 수 있다.

○ Tk 툴킷은 Button 클래스와의 바인딩을 통해 버튼에 대한 동작을 정의하는 기본 바인딩을 제공한다.

○ 프로그래머는 새로운 바인딩 그룹을 만들고 여러 바인딩을 묶을 수 있다. bindtags 명령은 바인딩 그룹을 관리하고 우선 순위를 정한다.

○ 바인딩과 관련된 개념으로 포커스(focus)를 들 수 있다.

○ 어떤 때에도 하나의 widget은 입력 포커스를 가지고 있으며 그 widget으로 키보드 입력이 전달된다.

○ 포커스를 주는 방법에는 두 가지가 있는데, 하나는 마우스 포인터 바로 밑에 있는 widget에게 포커스를 주는 것이며 또 하나는 직접 특정 widget에게 포커스를 주는 것이다.

○ Tk는 포커스를 바꿀 수 있는 명령을 제공한다.

○ Tk 스크립트는 widget을 생성하고 형상 관리자를 사용해 widget들을 배치하며 widget에 대한 action을 정의하는 구조로 이루어진다.

○ wish를 소스 프로그램의 이름 없이 그냥 실행시키면 기본적으로 빈 메인 윈도우를 하나 생성한 후 명령 라인 프롬프트를 보여 준다.


9.1.  Hello, World

○ 먼저 간단한 Tk 응용 프로그램을 짜 보도록 한다.

○ 버튼을 누르면 표준 출력에 "Hello, World!"라는 글자를 출력해 준다.

○ Hello, World 스크립트는 버튼을 생성하고 버튼을 배치하는 두 부분으로 구성된다.

○ 그림 10-1은 이 스크립트의 실행 화면을 보여 준다.

그림 10-1). Hello World 실행화면

#!/usr/local/bin/wish

button .hello -text Hello \

      -command {puts stdout "Hello, World!"}

pack .hello -padx 20 -pady 10

○ 첫 번째 줄은 다른 UNIX 명령과 똑같은 방식으로 수행하기 위해 필요한 줄이다.

○ 이 줄은 윈도우즈 등의 시스템에서 수행하는 경우는 필요없다.

○ button 명령은 button을 생성한다.

button .hello -text Hello \

      -command {puts stdout "Hello, World!"}

=>.hello

○ 버튼의 이름은 .hello이다.

○ 버튼에 쓰여질 글자는 Hello이다.

○ 버튼과 연결된 명령은 다음 명령이다.

puts stdout "Hello, World!"

○ pack 명령은 버튼을 화면에 보여 준다.

○ Tk는 widget을 생성하고 이름을 붙일 때 객체 기반 시스템을 사용한다.

○ 각 widget 클래스는 그 클래스의 widget의 한 인스턴스를 생성하는 명령과 연결되어 있다.

○ 새로운 widget이 생성되면 새로운 그 인스턴스와 연결될 새로운 Tcl 명령이 정의된다.

○ 위의 예제에서 만든 버튼은 .hello라는 이름을 사용해 접근할 수 있다.

○ .hello 버튼이 잠시동안 깜빡거리게 하고 싶은 경우는 다음 명령을 수행한다.

.hello flash

○ 버튼과 연결된 명령을 수행하기 위해서는 다음 명령을 수행한다.

.hello invoke

=> Hello, World!


9.2.  Tk widget에 이름 붙이기

○ 버튼의 이름 앞에 붙은 점 기호는 필요하다.

○ Tk는 widget의 이름이 widget 계층 구조에서의 위치를 반영할 수 있도록 하고 있다.

○ 계층 구조의 루트는 메인 윈도우이며 그 윈도우의 이름은 ‘.’이다.

○ 이것은 UNIX의 파일 시스템에서 ‘/’가 루트 디렉토리를 나타내는 동시에 디렉토리 이름을 구분하기 위해 사용되는 것과 유사하다.

○ Tk는 UNIX 파일 시스템에서의 '/'와 비슷한 용도로 '.'을 사용한다.

○ .hello라는 widget은 메인 윈도우의 자식 윈도우를 나타내는 이름이다.

○ .hello라는 widget의 자식 윈도우를 만들고 싶다면 그 윈도우의 이름은 .hello.hi의 형식으로 하면 될 것이다.

○ Tk 경로명은 항상 소문자나 숫자로 시작해야 한다.

○ 이러한 Tk의 이름 붙이는 방법에는 한 가지 큰 단점이 있다.

○ 단점은 어떤 widget을 widget 계층 구조의 다른 위치로 옮기고 싶은 경우에는 widget의 이름을 바꾸어야 한다는 것이다.

○ 이러한 문제를 해결하기 위해서는 직접 widget의 이름을 사용하지 말고 widget의 이름을 담는 변수를 사용하는 것이 좋다.


9.3.  Tk widget의 설정

○ Hello, World! 예제에는 Tk widget의 속성을 바꾸는 방법이 소개되어 있다.

○ widget의 속성 이름은 -로 시작한다.

○ -로 시작하는 속성 이름 다음에 오는 파라미터가 그 속성의 속성값이 된다.

○ 간단한 widget은 10개 내외의 속성을 가지며 복잡한 widget의 경우는 20개 이상의 속성을 가진다.

○ 이러한 속성값에는 기본값이 주어여 있으므로 그 중 필요한 일부의 속성만을 변경하면 된다.

○ 각 widget 인스턴스는 속성을 얻어 내고 바꿀 수 있는 configure(줄여서 config라고도 쓸 수 있음)라는 연산을 지원한다.

○ config 명령의 문법은 widget을 생성할 때 주는 속성과 똑같은 파라미터를 준다.

○ 예를 들어 .hello 버튼의 배경 색을 빨간색으로 바꾸고 싶은 경우는 다음 명령을 수행하면 된다.

.hello config -background red

○ config 연산은 현재의 속성 값을 얻어내는 데에도 사용할 수 있다.

.hello config -background

=> -background background Background #ffe4c4 red

○ 리턴값은 명령행 옵션, 자원의 이름, 클래스 이름, 기본값, 그리고 현재 값이 포함되어 있다.

○ 클래스와 리소스 이름은 X 윈도우의 자원 관리 메커니즘과 관계가 있다.

○ 보통은 현재 값만이 필요하다.

○ 현재의 값만을 얻어내기 위해서는 cget 연산을 수행하면 된다.

.hello cget -background

=> red

○ widget 속성은 언제라도 바꿀 수 있다.

○ 다음 명령은 .hello widget 버튼을 Goodbye! 버튼으로 바꾼다.

.hello config -text Goodbye! -command exit


9.4.  Tk widge 속성과 X 자원

○ widget의 속성에 이름을 붙이는 방법에는 명령행 옵션, 이름, 클래스의 세 가지가 있다.

○ 명령행 옵션은 Tcl에서 사용하는 방법이다.

○ 명령행 옵션은 소문자이며 - 기호로 시작한다.

○ 이름과 클래스로 widget 속성에 이름을 붙이는 것은 X 윈도우즈 시스템의 자원 명세서와 관련이 깊다.

○ 속성에 해당하는 자원의 이름은 앞에 - 기호가 붙지 않으며 이름의 중간중간에 대문자가 들어가기도 한다.

○ 자원 클래스는 대문자로 시작하며 중간에 대문자가 들어간다.

○ X에서는 이와 같은 이름 붙이는 방법들을 사용해 미리 정의된 widget 속성에 이름을 붙여 두었으므로 이름 붙이는 법을 잘 알아두는 것이 편리하다.

○ 보통 Tk와 함께 man page가 따라오는 것이 보통이므로 이러한 man page를 참조하는 것이 좋다.

% man button


9.5.  Tk 명령 종합

○ 다음은 Tk에서 widget을 만들기 위해 사용하는 명령들이다.

- button: 버튼을 생성한다.

- checkbutton: Tcl 변수와 연결된 토글할 수 있는 버튼을 만든다.

- radiobutton: 여러 개의 radio button 중에서 하나를 택할 수 있다.

- menubutton: 메뉴를 부르는 버튼을 만든다.

- menu: 메뉴를 생성한다.

- canvas: 캔버스를 만든다.

- label: 텍스트 레이블을 만든다.

- entry: 한 줄의 입력을 받을 수 있는 widget을 생성한다.

- message: 읽을 수만 있는 여러 줄의 텍스트 메시지를 생성한다.

- listbox: 리스트 박스를 만든다.

- text: 범용의 텍스트 widget을 만든다.

- scrollbar: 다른 widget과 연결되는 스크롤 바를 만든다.

- scale: 어떤 변수의 값을 설정할 때 사용되는 scale widget을 만든다.

- frame: 형상 관리자와 함께 사용되 다른 widget을 포함하는 데 사용되는 프레임 widget을 만든다.

- toplevel: X 윈도우의 새로운 top-level 프레임을 만든다.

다음은 widget들을 다루는 여러 명령들이다.

- after: 얼마간의 시간이 지난 후에 특정 명령을 수행한다.

- bell: X 벨 장치를 통해 벨을 울린다.

- bind: Tcl 명령을 X 이벤트에 연결한다.

- bindtags: 바인딩 클래스를 생성하고 바인딩 상속을 제어한다.

- clipboard: X 클립보드를 다룬다.

- destroy: widget을 없앤다.

- fileevent: Tcl 명령을 file descriptor와 연결시킨다.

- focus: 입력 포커스를 조절한다.

- grab: 다른 widget으로부터 입력 포커스를 빼앗아 온다.

- image: 이미지를 생성하고 다룬다.

- lower: 윈도우의 스택 순서에서 윈도우를 밑으로 보낸다.

- option: Xresources 데이터베이스에 접근한다.

- pack: 화면에 widget들을 배치해 보여 준다.

- place: 화면에 위치를 주어 widget을 배치한다.

- raise: 윈도우의 스택 순서에서 윈도우를 위로 올린다.

- selection: X PRIMARY selection을 다룬다.

- send: Tcl 명령을 다른 Tk 응용에게 전송한다.

- tk: 응용 프로그램의 이름을 묻거나 설정한다.

- tkerror: 에러를 처리한다.

- tkwait: 어떤 이벤트의 발생을 기다린다.

- update: 이벤트 루프를 통해 화면을 갱신한다.

- winfo: 윈도우의 상태에 대해 질의한다.

- wm: 윈도우 메니저와 상호 정보교환을 한다.



10.  예제로 배우는 Tk

○ Tk는 쉽고 재미있게 사용자 인터페이스를 만들 수 있게 해 준다.

○ 본 절에서는 Tk로 어떤 일들을 할 수 있는지를 살펴 보는 데 주안점을 둔다.


10.1.  ExecLog

○ 첫 번째로 살펴 볼 프로그램은 UNIX 프로그램을 수행할 수 있는 간단한 사용자 인터페이스를 제공한다.

○ 사용자 인터페이스는 'Run it'과 'Quit'의 두 버튼, 명령을 입력 받는 entry widget, 그리고 수행된 프로그램의 로그를 기록하는 텍스트 widget으로 구성된다.

○ 이 스크립트는 프로그램을 파이프라인으로 수행하며 출력을 기다리기 위해 fileevent 명령을 사용한다.

○ 이러한 구조는 프로그램이 수행 중인 경우라도 사용자 인터페이스가 동작하도록 해 준다

○ 그림 10-2와 다음 예제는 ExecLog 프로그램의 실행예와 소스 코드이다.

그림 10-2). ExecLog 프로그램의 실행예

#!/usr/local/bin/wish -f

# execlog - run a UNIX program and log the output

# Set window title

wm title . ExecLog


# Create a frame for buttons and entry

frame .top -borderwidth 10

pack .top -side top -fill x


# Create the command buttons

button .top.quit -text Quit -command exit

set but [button .top.run -text "Run it" -command Run]

pack .top.quit .top.run -side right


# Create a labeled entry for the command

label .top.l -text Command: -padx 0

entry .top.cmd -width 20 -relief sunken \

      -textvariable command

pack .top.l -side left

pack .top.cmd -side left -fill x -expand true


# Set up key binding equivalents to the buttons

bind .top.cmd <Return> Run

bind .top.cmd <Control-c> Stop

focus .top.cmd


# Create a text widget to log the output

frame .t

set log [text .t.log -width 80 -height 10 \

      -borderwidth 2 -relief raised -setgrid true \

      -yscrollcommand {.t.scroll set}]

scrollbar .t.scroll -command {.t.log yview}

pack .t.scroll -side right -fill y

pack .t.log -side left -fill both -expand true

pack .t -side top -fill both -expand true


# Run the program and arrange to read its input

proc Run {} {

      global command input log but

      if [catch {open "|$command |& cat"} input] {

              $log insert end $input\n

      } else {

              fileevent $input readable Log

              $log insert end $command\n

              $but config -text Stop -command Stop

      }

}


# Read and log output from the program

proc Log {} {

      global input log

      if [eof $input] {

              Stop

      } else {

              gets $input line

              $log insert end $line\n

              $log see end

      }

}


# Stop the program and fix up the button

proc Stop {} {

      global input but

      catch {close $input}

      $but config -text "Run it" -command Run

}


○ 첫 번째 명령은 윈도우 관리자에 의해 구현되는 윈도우의 타이틀 바를 바꾼다.

wm title .ExecLog

○ wm 명령은 윈도우 관리자와 통신을 한다.

○ 윈도우 관리자는 윈도우를 열고 닫으며 크기를 바꿀 수 있게 해 주는 프로그램이다.

○ 사용자 인터페이스의 최상위 부분에 나타날 widget들을 담기 위해 프레임이 생성된다.

○ 프레임은 widget을 위한 공간을 마련하기 위해 border를 가지고 있다.

frame .top -borderwidth 10

○ 프레임은 메인 윈도우 안에 위치된다.

○ 기본적인 packing 면은 top이므로 -side top 옵션은 빼도 상관없다.

○ -fill x 옵션은 메인 윈도우의 크기만큼 프레임이 커지도록 한다.

○ 여기서는 두 개의 버튼이 생성된다.

○ 하나의 버튼은 프로그램을 수행하는 데 사용되고 또 하나의 버튼은 ExecLog 프로그램을 종료하는 데 사용된다.

○ 각 버튼의 이름은 .top.qui과 .top.run이다.

○ label과 entry도 .top 프레임의 자식으로 생성된다.

○ entry의 크기는 입력받을 수 있는 문자 개수로 정해진다.

○ relief 속성은 화면에 어떻게 보일 것인지를 정한다.

○ label과 entrysms .top 프레임의 왼쪽에 배치된다.

○ entry widget에 대해 지정된 키 바인딩은 Enter 키나 Ctrl-C 키를 눌렀을 때에도 어떤 동작을 취할 수 있게 해줌.○ bind 명령은 Tcl 명령을 어떤 widget의 X event와 연결시킨다.

○ <Return> 이벤트는 사용자가 Enter 키를 눌렀을 때 발생된다.

○ <Control-c> 이벤트는 사용자가 Ctrl-C 키를 눌렀을 때 발생된다.

○ 이벤트가 entry widget으로 가기 위해서는 입력 포커스가 widget에 가 있어야 하므로 focus 명령으로 입력 포커스를 entry widget에 보내 준다.

○ text widget이 생성되고 프레임에 스크롤바와 함께 들어가게 된다.

○ scrollbar은 Tk에서 별도의 widget이며 여기서 사용한 방법과 같이 다른 widget과 연결된다.

○ text widget의 yscrollcommand 옵션은 widget이 수정될 때 scrollbar의 화면 표시를 갱신하게 된다.

○ setgrid 속성을 켜면 메인 윈도우의 크기를 바꿀 수 있게 된다.

○ Run 프로시져와 Log 프로시져, Stop 프로시져는 프로그램을 수행하고 결과를 기록하며 수행을 중지시키는 일을 하는 프로시져이다.


10.2.  Tcl Shell

○ 다음은 Tcl Shell 프로그램을 구현해 본 것이다.

#!/usr/local/bin/wish

# Simple evaluator. It executes Tcl in its own interpreter

# and it uses up the following identifiers

# Tk widgets:

#   .eval - the frame around the text log

# Procedures:

#   _Eval - the main eval procedure

# Variables:

#   prompt - the command line prompt

#   _t - holds the ID of the text widget


# A frame, scrollbar, and text

frame .eval

set _t [text .eval.t -width 80 -height 20 \

      -yscrollcommand {.eval.s set}]

scrollbar .eval.s -command {.eval.t yview}

pack .eval.s -side left -fill y

pack .eval.t -side right -fill both -expand true

pack .eval -fill both -expand true


# Insert the prompt and initialize the limit mark

.eval.t insert insert "Tcl eval log\n"

set prompt "tcl> "

.eval.t insert insert $prompt

.eval.t mark set limit insert

.eval.t mark gravity limit left

focus .eval.t


# Key bindings that limit input and eval things

bind .eval.t <Return> { _Eval .eval.t ; break }

bind .eval.t <Any-Key> {

      if [%W compare insert < limit] {

              %W mark set insert end

      }

}

bindtags .eval.t {.eval.t Text all}


proc _Eval { t } {

      global prompt _debug

      set command [$t get limit end]

      if [info complete $command] {

              set err [catch {uplevel #0 $command} result]

              $t insert insert \n$result\n

              $t insert insert $prompt

              $t see insert

              $t mark set limit insert

              return

      }

}

○ 위의 프로그램을 실행시키면 그림 10-3과 같은 화면이 나타난다.

그림 10-3). Tcl Shell 프로그램의 실행예

11.  Pack 형상 관리자

○ 형상 관리자는 화면에 widget을 배치한다.

○ 형상 관리자에는 여러 가지 종류가 있으며 각 widget마다 다른 종류의 형상 관리자를 사용할 수 있다.

○ 여기서는 주로 pack 형상 관리자에 대해 알아 보도록 한다.

○ 형상 관리자는 부모 widget을 하나 사용하며 그 안에 여러 개의 자식 widget을 배치하게 된다.

○ 부모 widget은 보통 프레임이지만 반드시 그렇지는 않다.

○ 하나의 widget 한번에 하나의 형상 관리자에 의해서만 관리된다.

○ widget이 형상 관리자와 연결되지 않으며 화면에 나타나지 않는다.

○ packer는 강력한 형상 관리자이다.

○ 각 윈도우의 정확한 위치를 상세히 설명하는 대신, 윈도우가 어떤 방식으로 배치되어야 하는지만 알려 주면 packer가 알아서 위치를 찾아 배치하게 된다.

○ 화면에 자신이 원하는 대로 widget들을 배치하기 위해서는 packer가 사용하는 알고리즘을 잘 이해해야만 한다.

○ 본 절에서는 예제를 통해 packer 형상 관리자에 대해 살펴 본다.

○ 본 절에서 소개하는 예제에서 메인 윈도우의 배경은 항상 검은 색이다.


11.1.  모서리쪽에 붙이기

○ 다음 예제에서는 두 개의 프레임을 생성한 후 각각을 메인 윈도우의 위쪽에 붙인다.

○ 둘 중 더 위쪽에 배치될 윈도우가 .one이며 그 아래에 .two 윈도우가 배치된다.

○ 모서리 위치는 top, right, bottom, left 중 하나로 지정할 수 있다.

# Make the main window black

. config -bg black

# Create and pack two frames

frame .one -width 40 -height 40 -bg white

frame .two -width 100 -height 50 -bg grey50

pack .one .two -side top

○ 위의 예제를 실행하면 다음과 같은 화면이 만들어진다.

그림 10-4). 모서리에 붙이기

 

○ 위의 예제에서 메인 윈도우는 두 개의 자식 윈도우를 담을 만큼의 크기로 줄어들었다.

○ 이러한 기능을 없애려면 pack propagate 명령을 사용한다.

○ pack propagate 명령을 부모 프레임에 사용하면 부모 윈도우는 자식 윈도우의 크기에 맞추어 크기가 변경되지 않는다.

# Make the main window black

. config -bg black

# Create and pack two frames

frame .one -width 40 -height 40 -bg white

frame .two -width 100 -height 50 -bg grey50

pack propagate . false

pack .one .two -side top

○ 위의 예제를 실행하면 그림 10-5와 같은 화면이 만들어진다.

그림 10-5). pack propagate 명령의 사용예


11.2.  가로 세로 쌓기

○ 일반적으로 프레임 안에서는 가로나 세로로 widget을 쌓을 수 있다.

○ 만약 한 프레임 안의 자식 widget들에 대해 left와 top을 동시에 사용한다면 그 결과는 원하는 것과 다를 수 있다.

○ 이러한 경우는 서로 다르게 쌓고 싶은 widget들을 담을 자식 프레임을 만드는 것이 좋다.

○ 예를 들어 앞의 예제에서 위쪽 프레임에 버튼을 가로로 나열해 보자.

# Make the main window black

. config -bg black

# Create two frames

frame .one -bg white

frame .two -width 100 -height 50 -bg grey50

# Create a row of buttons

foreach b {alpha beta gamma} {

      button .one.$b -text $b

      pack .one.$b -side left

}

pack .one .two -side top

○ 위의 예제를 실행시키면 그림 10-6과 같은 화면이 나타난다.

그림 10-6). 버튼을 가로로 나열한 예

○ 이번에는 좀더 복잡한 배치를 위해 프레임을 중복해서 사용하도록 하자.

# Make the main window black

. config -bg black

# Create two frames

frame .one -bg white

frame .two -width 100 -height 50 -bg grey50

# Create a row of buttons

foreach b {alpha beta} {

      button .one.$b -text $b

      pack .one.$b -side left

}                

# Create a frame for two more buttons

frame .one.right

foreach b {delta epsilon} {

      button .one.right.$b -text $b

      pack .one.right.$b -side bottom

}

pack .one.right -side right

pack .one .two -side top

○ 위의 예제를 실행시키면 그림 10-7과 같은 화면이 나온다.

그림 10-7). 프레임을 중복사용한 예


11.3.  Cavity 모델

○ packing 알고리즘은 프레임 안의 남는 공간에 대해 cavity 모델을 사용한다.

○ 메인 윈도우가 생성될 때 메인 프레임은 비어 있으며 widget을 배치할 자리가 마련되어 있다.

○ widget은 남은 공간의 한 면을 전부 차지한다.

○ 이러한 규칙을 설명하기 위해 다음 예제를 살펴 보자.

# Make the main window black

. config -bg black

# pack two frames on the bottom.

frame .one -width 100 -height 50 -bg grey50

frame .two -width 40 -height 40 -bg white

pack .one .two -side bottom

# pack another frame to the right

frame .three -width 20 -height 20 -bg grey75

pack .three -side right

○ 위의 예제를 실행시키면 그림 10-8과 같은 화면이 나온다.

그림 10-8). Cavity 모델

○ 위의 예제에서 .three widget을 오른쪽에 배치하였으나 .two의 아래로는 내려가지 않았다.

○ 그것은 .two widget이 bottom 면에 배치되어 있으므로 가로쪽으로 한 면을 전부 차지하고 있기 때문이다.


11.4.  팩킹 공간과 디스플레이 공간

○ packer는 팩킹 공간과 디스플레이 공간을 구분한다.

○ 디스플레이 공간은 widget을 화면에 보여주기 위해 요청되는 구역이다.

○ 팩킹 공간은 widget의 배치를 위해 허용하는 구역이다.

○ 화면 구성상의 특징 때문에 팩킹 공간은 디스플레이 공간보다 더 클 수 있다.

○ -fill 옵션은 디스플레이 공간이 차지하는 공간을 다 채우도록 만든다.

# Make the main window black

. config -bg black

# pack two frames on the bottom.

frame .one -width 100 -height 50 -bg grey50

frame .two -width 40 -height 40 -bg white

# pack with fill enabled

pack .one .two -side bottom -fill x

frame .three -width 20 -height 20 -bg red

pack .three -side right -fill x

○ 위의 예제를 실행시키면 그림 10-9와 같은 화면이 나온다.

그림 10-9). fill 옵션의 사용예

○ -fill x 옵션이 사용되면 위와 같이 자신이 차지하는 구역 전부에 꽉 채워져 화면에 나타나게 된다.

○ 이와 같은 fill 옵션은 다음과 같이 메뉴바를 만드는 데 많이 사용된다.

frame .menubar -bg white

frame .body -width 150 -height 50 -bg grey50

# Create buttons at either end of the menubar

foreach b {alpha beta} {

      button .menubar.$b -text $b

}

pack .menubar.alpha -side left

pack .menubar.beta -side right

# Let the menu bar fill along the top

pack .menubar -side top -fill x

pack .body

○ 위의 예제를 실행시키면 그림 10-10과 같은 화면이 나타난다.

그림 10-10). 메뉴바에서의 fill 옵션 사용예

○ 좀더 많은 공간을 확보하고 싶을 때에는 x나 y 방향으로 더 많은 공간을 요구하는 -ipadx나 -ipady 옵션을 사용한다.

○ 다음 예제는 위의 예제에 내부 패딩을 추가하였다.

# Create and pack two frames

frame .menubar -bg white

frame .body -width 150 -height 50 -bg grey50

# Create buttons at either end of the menubar

foreach b {alpha beta} {

      button .menubar.$b -text $b

}

pack .menubar.alpha -side left -ipady 10

pack .menubar.beta -side right -ipadx 10

# Let the menu bar fill along the top

pack .menubar -side top -fill x -ipady 5

pack .body

○ 위의 예제를 실행시키면 그림 10-11과 같은 화면이 나타난다.

그림 10-11). 내부 패딩을 추가한 예

○ 내부 패딩의 크기를 다르게 했으므로 alpha와 beta 버튼의 크기는 다르다.

○ 버튼은 -padx와 -pady의 또다른 패딩 옵션을 제공한다.

○ -padx와 -pady 옵션은 버튼 안에 들어 가는 글자가 버튼의 경계보다 안 쪽에 들어가게 한다.

# Foo has internal padding from the packer

button .foo -text Foo -anchor e -padx 0 -pady 0

pack .foo -side right -ipadx 10 -ipady 10

# Bar has its own padding

button .bar -text Bar -anchor e -pady 10 -padx 10

pack .bar -side right -ipadx 0 -ipady 0

○ 위의 예제를 실행시키면 그림 10-12와 같은 화면이 나타난다.

그림 10-12). 버튼의 패딩옵션 사용예

○ 원래 -padx와 -pady 옵션은 외부 패딩을 정하기 위해 사용된다.

○ 이것은 widget의 바깥 부분에 들어가는 패딩이며 3차원 효과를 나타내는 부분이기도 하다.

. config -borderwidth 10

# OK is the default button

frame .ok -borderwidth 2 -relief sunken

button .ok.b -text OK

pack .ok.b -padx 5 -pady 5

# Cancel is not

button .cancel -text Cancel

pack .ok .cancel -side left -padx 5 -pady 5

○ 위의 예제를 실행시키면 그림 10-13과 같은 화면이 나타난다.

그림 10-13). 외부 패딩의 사용 예


11.5.  크기 확장과 크기 조절

○ -expand true 옵션은 widget이 자기 자리가 아니지만 비어 있는 곳까지 확장하도록 해 준다.

○ -expand 옵션이 사용되는 더 일반적인 예는 크기를 변경할 수 있는 윈도우에서이다.

○ 윈도우가 더 커지면 새로 만들어진 남는 공간을 사용하라고 widget들에게 말해 주어야 한다.

○ 윈도우의 크기는 사용자가 직접 변경할 수도 있고 프로그램상에서 wm geometry 명령을 사용해 바꿀 수도 있다.

○ 기본적으로 윈도우의 크기는 변경할 수 없게 되어 있다.

○ wm minisize나 wm maxsize 명령은 윈도우의 크기를 바꿀 수 있게 하는 부가적인 효과를 가지고 있다.

# Make the main window black

. config -bg black

# Create and pack two frames

frame .menubar -bg white

frame .body -width 150 -height 50 -bg grey50

# Create buttons at either end of the menubar

foreach b {alpha beta} {

      button .menubar.$b -text $b

}

pack .menubar.alpha -side left

pack .menubar.beta -side right

# Let the menu bar fill along the top

pack .menubar -side top -fill x

pack .body

# Resize the main window to be bigger

wm geometry . 200x100

# Allow interactive resizing

wm minsize . 100 50

○ 위의 예제를 실행시키면 그림 10-14와 같은 화면이 나온다.

그림 10-14). interactive resizing이 허용된 윈도우의 예

○ 위의 예제 실행 후에 다음 명령을 실행하면 윈도우는 그림 10-15와 같이 바뀐다.

pack .body -expand true -fill both

그림 10-15). expand 사용 예

○ 그러나 하나의 부모 widget 밑에 있는 두 개 이상의 widget이 모두 -expand 옵션을 사용하는 경우는 각 widget이 비례적으로 남은 공간을 나누어 가지게 된다.

○ 위의 pack 명령 대신 다음 명령을 실행하면 그림 10-16과 같이 화면이 바뀐다.

pack .menubar -expand true -fill x

pack .body -expand true -fill both

그림 10-16). fill 옵션의 사용예


11.6.  Anchoring

○ widget이 디스플레이 공간보다 더 많은 팩킹 공간이 있을 때에는 -anchor 옵션을 사용해 팩킹 공간 상에서의 위치를 지정할 수 있다.

○ 기본 위치는 center이다.

○ 가능한 위치는 n, ne, e, se, s, sw, w, nw이다.

# Make the main window black

. config -bg black

# Create two frames to hold open the cavity

frame .prop -bg white -height 80 -width 20

frame .base -width 120 -height 20 -bg grey50

pack .base -side bottom

# Float a label and the prop in the cavity

label .foo -text Foo

pack .prop .foo -side right -expand true

○ 위의 예제를 실행시키면 그림 10-17과 같은 화면이 나타난다.

그림 10-17). widget의 기본 위치

○ .base 프레임은 바닥에 배치된다.

○ .prob와 .foo 레이블은 오른쪽에 배치되지만 fill 옵션은 선택되지 않는다.

○ 그에 따라 .foo는 기본적으로 중앙에 배치되게 된다.

○ 다음 예는 -anchor 옵션을 사용하는 예이다.

# Make the main window black

. config -bg black

# Create two frames to hold open the cavity

frame .prop -bg white -height 80 -width 20

frame .base -width 120 -height 20 -bg grey50

pack .base -side bottom

# Float a label and the prop

# Change their position with anchors

label .foo -text Foo

pack .prop .foo -side right -expand true -anchor sw

pack .foo -side right -expand true -anchor ne

○ 위의 예제를 실행시키면 그림 10-18과 같은 화면이 나타난다.

그림 10-18). anchor 옵션의 사용예


11.7.  팩킹 순서

○ packer는 프레임에 들어가는 자식 widget들의 순서를 유지하고 있다.

○ 기본적으로 새로 추가된 widget은 가장 마지막 팩킹 순서 뒤에 추가되게 된다.

○ 팩킹 순서의 가장 큰 효과는 가장 높은 팩킹 순서를 가지는 widget이 가장 모서리쪽에 가깝게 배치된다는 것이다.

○ -before나 -after 팩킹 옵션을 사용하면 팩킹 순서를 바꿀 수 있다.

○ 이미 들어간 widget의 팩킹 순서도 바꿀 수 있다.

# Create five labels in order

foreach label {one two three four five} {

      label .$label -text $label

      pack .$label -side left -padx 5

}

# ShuffleUp moves a widget to the beginning of the order

proc ShuffleUp { parent child } {

      set first [lindex [pack slaves $parent] 0]

      pack $child -in $parent -before $first

}

# ShuffleDown moves a widget to the end of the order

proc ShuffleDown { parent child } {

      pack $child -in $parent

}

○ 위의 예제를 실행시키면 그림 10-19와 같은 화면이 나타난다.

그림 10-19). 기본 팩킹

○ 여기서 다음 두 명령을 실행시키면 화면이 그림과 같이 바뀐다.

ShuffleUp . .five

ShuffleDown . .three

그림 10-20). 팩킹 순서를 바꾼 예

○ pack slaves 명령은 팩킹 순서로 자식 widget의 리스트를 돌려 준다.

○ 위의 예제의 ShuffleUp 명령은 이 pack slaves 명령을 사용한다.

○ 지금까지의 모든 예제에서는 widget이 부모 frame에 들어갔다.

○ 일반적으로 모든 widget은 그 부모의 자손 window에는 어디에나 포함될 수 있다.

○ 예를 들어 .a.b widget은 .a, .a.c, .a.d.e.f 등의 window에 모두 포함될 수 있다.

○ -in 옵션은 다른 부모를 선택하게 하여 준다.

○ pack forget 명령은 포함된 widget을 끄집어 낸다.

12.  X 이벤트와 Tcl 명령의 연결

○ 바인딩은 Tcl 명령을 X 윈도우 시스템의 이벤트와 연결시키는 작업을 말한다.

○ 이벤트에는 다음과 같은 경우에 발생한다.

- 키가 눌려진 경우

- 키에서 손을 뗀 경우

- 마우스 버튼이 눌려진 경우

- 마우스 버튼을 놓은 경우

- 윈도우 안으로 마우스 포인터가 들어가는 경우

- 마우스 포인터가 윈도우 밖으로 빠져 나가는 경우

- 윈도우의 크기가 변경되는 경우

- 윈도우가 열리는 경우

- 윈도우가 닫히는 경우

- 입력 포커스를 얻은 경우

- widget이 없어지는 경우

○ 바인딩은 binding tag들로 정의되며 각 widget은 binding tag들의 집합과 연결된다.

○ 바인딩과 widget이 분리됨으로써 유연하고 강력한 시스템을 구성할 수 있다.


12.1.  bind 명령

○ bind 명령은 현재 바인딩에 대한 정보를 돌려 주고 새로운 바인딩을 정의하는 데 사용된다.

○ bind 명령의 문법은 다음과 같다.

bind bindingTag ?eventSequence? ?command?

○ bindingTag는 주로 widget 클래스 이름이거나 widget 인스턴스 이름이다.

○ binding tag는 뒤에서 자세히 설명한다.

○ 하나의 파라미터만 주는 경우 명령 바인딩이 설정된 이벤트들을 돌려 준다.

bind Menubutton

=> <Key-space> <ButtonRelease-1> <B1-Motion> <Motion> <Button-1> <Leave> <Enter>

○ <Button-1>은 왼쪽 마우스 버튼을 클릭하는 경우 발생하는 이벤트이다.

○ <B1-Motion>은 왼쪽 마우스 버튼을 클릭한 채로 마우스를 움직이는 경우 발생하는 이벤트이다.

○ 명령어의 바인딩은 이와 같은 이벤트의 나열에 대해 정의된다.

○ bind 명령에 eventSequence 파라미터를 주면 그 이벤트에 연결된 명령을 보여 준다.

○ 이벤트 바인딩을 위해 사용되는 Tcl 명령은 이벤트 키워드를 위한 몇 가지 추가적인 문법을 제공한다.

○ 이러한 키워드는 '%' 기호로 시작한다.

○ 이러한 키워드들은 이벤트와 관련된 데이터로 대체.

○ %W는 widget의 경로명으로 대치된다.

○ %X와 %Y 키워드는 이벤트 발생 좌표로 대치된다.

○ %는 인용 부호에 상관없이 항상 치환된다.

○ % 기호를 사용하기 위해서는 %%로 사용.

○ 이러한 이유로 바인딩 명령은 프로시져를 호출하는 방법으로 짧게 만드는 것이 좋다.

○ 새로운 바인딩은 연속된 일련의 이벤트와 명령을 줌으로써 정의된다.

bind Menubutton <B1-Motion> {tkMbMotion %W down %X %Y}

○ 바인딩 명령의 처음이 '+' 기호로 시작하면 그 명령은 바인딩 명령에 추가된다.

bind bindingTag event {+ command args}

○ 이벤트에 대한 바인딩을 삭제하기 위해서는 이벤트를 빈 스트링과 연결시킨다.

bind bindingTag event { }

○ 바인드 명령은 실행 전체에 걸쳐 영향을 미친다.


12.2.  bindtags 명령

○ 바인딩은 관련 바인딩을 묶는 binding tag와 연결.

○ 각 widget은 binding tag의 집합과 연결되어 있다.

○ bindtags 명령은 widget의 binding tag를 제어한다.

○ bindtags 명령의 문법은 다음과 같다.

bindtags widget ?tagList?

○ 다음 명령은 text widget에 대한 기본 binding tag를 돌려 준다.

bindtags .t     =>    .t Text . all

○ 모든 Tk widget은 기본적으로 다음과 같은 4개의 binding tag를 가지고 있다.

- widget의 Tk 경로명

- widget의 클래스. 클래스 이름은 그 widget을 생성하는 명령으로부터 도출해 낼 수 있다. 예를 들어 버튼 widget의 클래스 이름은 Button이고 캔버스의 클래스 이름은 Canvas이다.

- widget의 맨 꼭대기 윈도우의 Tk 경로명

- 전체 binding tag인 'all'

○ bindtags 명령을 사용하면 binding tag와 그 순서를 바꿀 수 있다.

○ tagList 파라미터는 Tcl 리스트이다.

○ 다음 명령은 $t에 대한 binding tag를 재배치하고 . binding tag를 삭제한다.

bindtags $t [list all Text $t]

○ tag 리스트의 순서는 이벤트가 집합의 둘 이상의 binding tag와 일치되는 경우에 수행할 명령의 순서를 결정한다.

○ Tk widget은 자신이 속한 클래스에 바인딩하는 것을 기본 동작으로 한다.

○ widget의 이름에 이벤트를 바인딩해서 추가적인 바인딩을 추가할 수 있다.

○ 가장 꼭대기의 윈도우에 대한 바인딩은 대화 상자에서 단축키를 처리하기 위해 사용할 수 있다.

○ all binding tag : 全 widget에 해당하는 바인딩을 의미.

○ all에 정의된 기본 바인딩은 widget 간에 입력 포커스를 변경하는 것이다.

frame .one -width 30 -height 30

frame .two -width 30 -height 30

bind Frame <Enter> {%W config -bg red}

bind Frame <Leave> {%W config -bg white}

bind .two <Any-Button> {puts "Button %b at %x %y"}

pack .one .two -side left

bind all <Control-c> {destroy %W}

bind all <Enter> {focus %W}

○ 위의 예제에서 Frame 클래스는 마우스가 들어오면 빨간색으로 변하고 나가면 하얀색으로 변하는 명령과 바인딩되었다.

○ .two widget은 마우스 버튼이 눌려진 경우 그 정보를 화면에 보여주게 하였다.

○ all에 대해 Control-c에 대한 바인딩을 정의하였으므로 모든 widget은 Ctrl-C 키를 누른 경우 사라지게 된다.


12.3.  이벤트 표기법

○ 이벤트는 다음과 같은 표기법에 의해 표현된다.

<modifier-modifir-type-detail>

○ 여기서 가장 중요한 부분은 Button이나 Motion 등의 단어가 사용되는 type 부분이다.

○ detail은 키 이름이나 버튼 종류를 지정하기 위해 사용한다. (예를 들어 Key-a, Button-1과 같이)

○ modifier는 이벤트가 발생할 때 이미 눌려져 있는 키를 의미한다.(Control-Key-a 또는 B2-Motion등과 같이)

○ < >로 둘러 싸서 하나의 이벤트임을 나타낸다

○ 다음은 모든 이벤트 type을 나타내고 있다.

- ButtonPress, Button: 마우스 버튼이 눌려졌다.

- ButtonRelease: 마우스 버튼이 떼어졌다.

- Circulate: 윈도우의 쌓이는 순서가 바뀌었다.

- Configure: 윈도우의 크기, 위치, border 또는 쌓이는 순서가 바뀌었다.

- Destroy: 윈도우가 사라졌다.

- Enter: 마우스가 윈도우 안으로 들어왔다.

- Expose: 윈도우가 아이콘 표시에서 원래 화면으로 바뀌었다.

- FocusIn: 윈도우가 입력 포커스를 받았다.

- FocusOut: 윈도우가 입력 포커스를 잃었다.

- Gravity: 부모 윈도우의 크기가 바뀌어 윈도우가 이동되었다.

- KeyPress, Key: 키가 눌려졌다.

- KeyReleas: 키가 떼어졌다.

- Motion: 마우스가 윈도우상에서 움직이고 있다.

- Leave: 마우스가 윈도우를 떠나고 있다.

- Map: 윈도우가 열려졌다.

- Property: 윈도우의 속성이 변경되거나 삭제되었다.

- Reparent: 윈도우가 reparent 되었다.

- Unmap: 윈도우가 아이콘 표시로 바뀌었다.

- Visibility: 윈도우의 보이는 모양이 바뀌었다.


12.4.  키보드 이벤트

○ KeyPress 이벤트와 KeyRelease 이벤트는 서로 다른 명령을 수행하게 할 수 있게 하기 위해 구분되어 있다.

○ KeyPress는 줄여서 Key라고 써도 되며 아예 Key도 생략할 수 있다.

○ KeyPress의 경우는 양쪽을 둘러싸는 < > 기호를 생략할 수 있다.

○ 다음은 모두 같은 의미이다.

- <KeyPress-a>

- <Key-a>

- <a>

- a

○ 이 외에도 다음과 같은 특수 키들이 정의되어 있다.

- Return

- Escape

- BackSpace

- Tab

- Up

- Down

- Left

- Right

- comma

- period

- dollar

- asciicircum

- numbersign

- exclam


12.5.  마우스 이벤트

○ 버튼 이벤트의 경우도 ButtonPress 이벤트와 ButtonRelease 이벤트가 분리되어 있다.

○ 다음은 모두 같은 의미이다.

- <ButtonPress-1>

- <Button-1>

- <1>

○ 숫자키 1이 눌린 이벤트인 경우는 <1>이라고 표기하면 안되고 <KeyPress-1>, <Key-1> 또는 그냥 1이라고 표기하여야 한다.

○ 마우스의 좌표는 %x와 %y 키워드로 얻음.

○ 이렇게 얻은 마우스좌표는 widget에서의 상대 좌표.

○ %X와 %Y는 화면상에서의 절대 좌표를 돌려 준다.


12.6.  지정자(modifier)

○ 지정자는 이벤트가 발생한 시점에 다른 키나 버튼이 눌려진 상태임을 나타낸다.

○ 전형적인 지정자로 Shift와 Control 키를 들수있다.

○ 예를 들어 Shift와 Control 키를 동시에 누르고 c 키를 눌렀을 때 발생하는 이벤트는 <Shift-Control-Key-c>로 표시할 수 있다.

○ 다음은 주로 사용되는 지정자들이다.

- Control: Control 키

- Shift: Shift 키

- Lock: caps-lock 키

- Meta: 메타 키

- Alt: Alt 키

- Button1(B1), Button2(B2), ... Button5(B5): 1(2,3...5)번째 마우스 버튼

- Double: 더블 클릭

- Triple: 세 번 연속으로 클릭

- Any: 지정자의 어떤 조합도 허용


12.7.  이벤트의 나열

○ bind 명령은 연속적으로 발생하는 둘 이상의 이벤트에 대해서도 명령을 지정할 수 있는 기능을 제공.

bind . a {puts stdout A}

bind . abc {puts stdout C}

bind .foo <Control-x><Control-s> {Save; break}


12.8.  이벤트 키워드

○ 다음은 바인딩에서 사용할 수 있는 키워드들이다.

- %%: % 기호

- %#: 이벤트의 일련 번호

- %a: 이벤트의 above 필드

- %b: 버튼 번호

- %c: count 필드

- %d: detail 필드

- %f: 포커스 필드

- %h: height 필드

- %k: keycode 필드

- %m: mode 필드

- %o: override_redirect 필드

- %p: place 필드

- %s: state 필드

- %t: time 필드

- %v: value_mask 필드

- %w: width 필드

- %x: x 상대 좌표

- %y: y 상대 좌표

- %A: 출력되는 문자

- %B: border 필드

- %E: send_event 필드

- %K: 이벤트의 keysim

- %N: 10진수로 표현된 keysim

- %R: 루트 윈도우 ID

- %S: sub 윈도우 ID

- %T: type 필드

- %W: widget의 경로명

- %X: x_root 필드

- %Y: y_root 필드