(원문: http://blog.naver.com/shimchan2/70121657250)
. TCL 기본
○ Tcl은 간단한 구조와 문법을 가지고 있어 배우고 사용하기 쉬운 스크립트 언어이다. ○ 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의 라이브러리 기능은 나중에 자세히 설명.
○ Tcl 명령의 기본적인 형태는 다음과 같다. command arg1 arg2 arg3 ... ○ 'command'는 내부 명령이나 Tcl 프로시져의 이름이다. ○ 명령어와 인자를 구분하기 위해 스페이스나 텝 문자를 사용한다. ○ 한 명령줄의 마지막을 나타내기 위해 newline이나 ';'을 사용한다. ○ 위에서 설명한 기본적인 문법은 하나의 인자에 여러 단어를 허용하는 grouping, 그리고 변수나 중첩 명령 호출과 함께 사용되는 치환을 이용해 확장시킬 수 있다. ○ 인자는 모두 문자열이다.
puts stdout {Hello, World!} ○ 이 예제에서 puts가 command이고 인자는 stdout가 {Hello, World!}의 두 개다. ○ puts 명령어는 I/O 스트림에 스트링을 출력하고 newline 문자를 출력한다. ○ stdout은 출력될 I/O 장치의 이름이다. ○ Hello, World!는 {}에 의해 Grouping되었다.
○ 변수에 값을 대입하기 위해서는 set 명령을 사용한다. ○ set 명령은 두 개의 인자를 받는다. ○ 첫 번째 인자는 변수의 이름이며 두 번째 인자는 값. ○ 변수 이름은 길이에 제한이 없으며 대소문자를 구별. ○ 변수 사용 이전에 미리 변수를 정의할 필요는 없다. ○ 변수로부터 값을 얻어내기 위해서는 변수 이름 앞에 $를 붙인다. set var 5 => 5 set b $var => 5 ○ 변수 이름 앞에 $를 붙이는 것을 '변수 치환'이라고 한다. ○ 명령어 해석기는 $var를 5로 대체(substitute)하게 된다.
○ 중첩된 명령은 ‘[ ]’로 둘러싸게 되는데 이를 '명령 치환'이라고 한다. ○ ‘[ ]’ 부분은 ‘[ ]’ 안에 포함된 명령의 실행 결과로 대체된다. ○ 일반 shell에서의 ‘`’와 비슷한 의미이다. set len [string length inclab] => 6 ○ 위의 예에서 string 명령은 스트링에 대한 여러 작업을 하는 명령이다. ○ 여기서는 inclab이라는 스트링의 길이를 얻어낸다. ○ 해석기는 [string length inclab]을 'string length inclab' 명령의 실행 결과인 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 ○ 위의 예에서처럼 e-val문은 명령 치환과 함께 사용되는 경우가 많다. ○ e-val 명령은 기본 내장 수학 함수들을 지원한다. 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를 내림한 정수값
○ 지금까지 변수 치환과 명령 치환의 두 가지 치환에 대해 알아 보았다. ○ 마지막 형태의 치환은 역슬래쉬 치환이다. ○ 해석기에게 특별한 의미를 가지는 문자를 표현하기 위해 사용된다. ○ 예를 들어 $는 변수 치환을 위해 사용되므로 명령어 상에 $를 사용하면 해석기는 이를 변수 치환으로 해석하여 할 것이다. ○ 이러한 문제를 해결하기 위해 역슬래쉬 치환을 사용한다. 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) - \: - XX: 8진수 - xhh: 16진수 - c: c 문자
○ 여러 단어를 하나로 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: %st%5.3f" $name $value]
○ 프로시져를 정의하기 위해서는 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) }
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} 와 같은 의미가 되어 버린다.
○ Tcl에서 주석을 포함하는 줄은 #으로 시작한다. ○ 한 줄의 뒷부분에 주석을 달기 위해서는 ;으로 한 줄을 마치고 #을 달 수 있다. set lab inclab ;# comment ○ 주석문은 에 의해 다음줄에 계속될 수 있다. ○ 주석문 안의 ;은 아무런 의미가 없다. #this is the start of a Tcl comment and some more of it; still in the comment
○ Tcl 쉘은 명령행에서 준 인자를 argv 변수를 통해 스크립트에게 전달한다. ○ argc 변수에는 인자의 개수가 들어간다. ○ 스크립트의 이름은 argv0 변수에 들어간다. set progname $argv0 set first [lindex $argv 0] set seconf [lindex $argv 1]
○ 스트링은 Tcl에서 사용하는 기본 자료 구조이다. ○ Tcl은 스트링을 다루는 많은 명령들을 가지고 있다. ○ 스트링에서 특정 패턴을 찾아내는 패턴 매칭을 쉽게 할 수 있다. ○ Tcl은 Glob 매칭과 Regular ex-pression 매칭의 두 가지 패턴 매칭 방법을 제공한다.
○ 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를 돌려 준다.
○ 여러 스트링을 합친다. set foo z append foo a b c => zabc
○ 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중 더 짧게 표현할 수 있는 형태로 표현
set lang 2 format "%${lang}$s" one un uno => un
○ scan 명령은 C의 sscanf 함수와 유사하다. ○ scan 명령의 문법은 다음과 같다. ○ scan string format var1 var2 ... ○ scanf는 [ ]를 사용해 입력 문자의 필터링이 가능하다. scan abcABC {%[a-z]} result => 1 set result => abc
○ string match 명령은 glob 형태의 패턴 매칭을 수행한다. ○ * 기호는 어떤 문자열과 대응된다. ○ ? 기호는 하나의 문자와 대응된다. ○ [ ]으로 문자를 묶으면 [ ] 안에 포함된 임의의 한 문자와 대응된다. ○ [ ]은 명령 치환을 위해 사용되는 기호이기도 하므로 치환을 방지하기 위해서는 { }를 이용해 grouping을 해 줘야 한다. string match a* alpha => 1 string match ?? XY => 1 string match {[ab]*} cello => 0
○ Regular ex-pression을 사용하면 더 강력한 패턴 매칭이 가능하다. ○ Regular ex-pression은 패턴을 정의할 때 사용하는 구문이다. ○ Regular ex-pression에서 사용되는 문법은 다음과 같다. - .: 어떤 임의의 한 문자와 대응된다. - *: 이전 패턴 항목을 0 또는 1회 이상 반복 - +: 이전 패턴 항목을 1회 이상 반복 - ?: 이전 패턴 항목을 0 또는 1회 반복 - ( ): 패턴을 묶는다. - |: 두 가지 패턴 중 임의의 하나와 대응 - [ ]: [ ] 안에 포함된 문자 중 임의의 하나아 대응. [ ] 안에 나오는 첫 번째 문자가 ^인 경우는 [ ] 안의 문자를 제외한 모든 다른 문자와 대응된다. - ^: 문자열의 처음과 대응 - $: 문자열의 마지막과 대응 Regular ex-pression의 예를 들어 보자. - ..: 임의의 두 글자 - [Hh]ello: Hello 또는 hello - [^a-zA-Z]: 알파벳을 제외한 모든 문자 - ba*: b, ba, baa, baaa, baaaa, ... - (ab)+: ab, abab, ababab, ... - .*: 모든 스트링과 대응 - hello|Hello: Hello 또는 hello
○ regexp 명령은 regular ex-pression을 이용한 패턴 매칭을 하는 명령이다. ○ 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)으로부터 다른 스트링(배열 원소의 값)에의 대응이라고 생각할 수 있다. ○ 작은 자료의 경우에는 리스트가 적당하며 큰 자료의 경우에는 배열이 적당하다.
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 }
○ 다른 언어에서의 리스트 자료 구조와 달리 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을 리스트 원소로 쪼갠다.
○ 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 구조를 한 겹 벗겨 낸 후 합치게 된다.
○ 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}
○ 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
○ 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 } }
○ 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}
○ 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
○ 배열은 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
○ 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
○ Tcl에서의 실행 순서 제어도 역시 명령어를 통해 이루어진다. ○ 루프를 위한 명령으로는 while, foreach, for가 있다. ○ 조건문으로는 if와 switch가 있다. ○ 에러 처리 명령으로는 catch가 있다. ○ 순서를 잘 제어하기 위한 다른 명령으로 break, continue, return, error가 있다. ○ 순서제어 명령 뒤에는 보통 나중에 수행할 명령들을 모은 몸체 부분이 따르게 된다. ○ 몸체 부분은 치환을 방지하기 위해 { }로 묶어야 한다. ○ 순서제어 명령은 마지막에 수행한 명령의 리턴값을 돌려주게 된다. ○ { }의 또하나의 용도는 Newline 문자를 포함한 스트링을 묶을 수 있다는 것이다. ○ if, for, while 등의 명령은 참/거짓을 돌려주는 boolean 수식을 포함한다. ○ if, for, while 명령은 내부적으로 expr 명령을 자동으로 수행하기 때문에 expr을 명시적으로 적어주지 않아도 된다.
○ 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 명령을 쓰는 것이 좋다.
○ switch 명령은 수식의 값에 따라 어떤 특정 몸체를 수행하게 하는 데 사용된다. ○ 조건은 간단한 비교문에서 패턴 매칭에 이르기까지 다양하게 사용할 수 있다. ○ switch문의 문법은 다음과 같다. ○ switch flags value pat1 body1 pat2 body2 ... ○ switch flags value { pat1 body1 pat2 body2 ... } ○ flags에는 다음과 같은 4가지 값 중의 하나가 들어갈 수 있다. -exact: 정확하게 일치하는 패턴만을 찾는다. -glob: glob 형태의 패턴 매칭을 수행한다. -regexp: regular ex-pression 패턴 매칭을 수행한다. --: 플래그를 사용하지 않거나 플래그 부분의 마지막을 의미 ○ 패턴과 몸체 부분을 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 해석기는 첫 번째 주석 부분을 패턴으로 인식하게 된다.
○ 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 } }
○ 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}
○ 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
○ break 명령은 현재 수행 중인 루프에서 빠져 나간다. ○ continue는 다음 루프를 즉시 수행한다. ○ Tcl에는 goto 명령이 없다.
○ 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 }
○ 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라는 변수에 저장된다.
○ return 명령은 프로시져로부터 종료하기 위해 사용된다. ○ return 명령은 프로시져의 수행 중간에 프로시져 수행을 마치기 위해 사용된다.
5. 프로시져와 스코프 ○ 프로시져는 자주 사용되는 일련의 명령들을 묶어 쉽게 사용할 수 있게 해 준다. ○ 각 프로시져는 변수에 대한 새로운 스코프를 가지게 된다. ○ 변수의 스코프란 그 변수가 정의되는 명령의 영역을 의미한다. ○ 본 절에서는 Tcl의 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
○ rename 명령은 명령어의 이름을 변경한다. ○ rename은 기존 프로시져에 기능을 추가하기 위해 사용할 수 있다. rename foo foo.orig ○ 이렇게 하면 foo 프로시져를 새로 정의할 때 foo.orig 프로시져를 호출할 수 있게 된다. ○ rename의 두 번째 인자로 빈 스트링을 줌으로써 명령어를 사용할 수 없게 만들 수도 있다. rename exec { }
○ 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 변수와는 다르다.
○ 프로시져 외부에서 정의된 변수를 프로시져 내부에서 사용하기 위해서는 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
○ 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를 사용하고 있다.
○ 프로시져에게 변수의 값이 아닌 이름을 넘겨 주고 싶은 경우는 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. e-val ○ 어떤 변수나 명령을 해석하기 위해서는 치환을 필요로 한다. ○ 좀 더 고급 기능의 치환은 e-val이나 subst, 그리고 after, uplevel, send 명령에 의해 수행된다.
○ e-val 명령은 Tcl 해석기에게 또다른 호출을 하게 만든다. / 명령어를 동적으로 조합하기 위해서는 e-val 명령을 사용해야 한다. ○ 예를 들어 다음과 같은 명령을 조합해 두고 나중에 수행시키고 싶은 경우를 생각해 보자. puts stdout "Hello, World!" ○ 이러한 경우에는 다음과 같이 하면 된다. set cmd {puts stdout "Hello, World!"} => puts stdout "Hello, World!" # sometime later... e-val $cmd => Hello, World! ○ 외부에 출력할 스트링이 string이라는 변수에 저장되어 있다고 가정해 보자. ○ e-val 명령의 수행 시점에서 string 변수에 값이 없다고 하자. set string "Hello, World!" set cmd {puts stdout $string} unset string e-val $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 e-val $cmd => Hello, World! ○ 다음은 잘못된 예이다. set cmd "puts stdout $string" => puts stdout Hello, World! e-val $cmd => bad argument "World!": should be "nonewline" ○ concat는 리스트 구조를 유지하지 않으므로 문제가 된다. set cmd [concat puts stdout $string]
○ e-val 명령은 하나 이상의 인자를 받는 경우에 자동으로 concat 명령을 수행하게 된다. e-val list1 list2 list3 ... ○ concat 명령의 효과는 모든 리스트들을 하나의 리스트로 합치는 것이다. ○ 새로운 단계의 리스트 구조가 추가되지는 않는다. ○ 이러한 기능은 명령어가 여러 조각으로 분리되어 있는 경우에 유용하다. ○ 이러한 형태의 e-val 명령은 프로시져의 args와 같이 사용하는 것이 일반덕이다. ○ 옵션 인자를 다른 명령에게 전달하기 위해 args 파라미터를 사용한다. ○ 다른 명령어를 호출할 때 e-val을 사용하면 $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를 구성하는 원소 각각을 하나의 파라미터로 인식하게 만들어야 한다. e-val button .foo $args => .foo ○ 다음 예는 이러한 e-val의 기능을 사용해 버튼을 만들고 보여주는 프로시져이다. proc PackedButton {path txt cmd {pack {-side right}} args} { e-val {button $path -text $txt -command $cmd } $args e-val {pack $path} $pack } ○ PackedButton에서 pack과 args는 모두 리스트 파라미터로서 명령어의 일부를 구성하게 된다.
○ uplevel 명령은 e-val과 유사하지만 현재 프로시져와는 다른 스코프에서 명령어를 수행하게 된다. ○ 이것은 새로운 제어 구조를 정의하는 데에 유용하다. ○ uplevel 명령의 문법은 다음과 같다. uplevel level command ○ level 파라미터의 의미는 upvar 명령의 경우와 동일하다.
○ 여기서는 Tcl로 어떻게 프로그램을 수행시키며 파일 시스템에 접근하는지를 살펴 본다. ○ 여기서 소개하는 명령은 UNIX를 기초로 하여 만들어졌지만 DOS나 Macintosh 등에서도 Tcl 해석기가 구현되어 있으므로 플랫폼에 무관하게 사용할 수 있다.
○ 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
○ 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 } elseif {[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)] }
○ 다음은 입출력을 위한 명령이다. - 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 스트림을 닫는다.
○ 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 명령을 사용하면 버퍼에 남아있는 데이터를 모두 출력한다.
○ 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 스트림에 연결된 시스템 자원을 모두 해제하므로 중요한 명령이다. ○ 파일을 닫지 않으면 프로세스가 끝날 때 자동으로 닫힌다. ○ 오래동안 수행되는 프로세스에서 파일을 닫지 않으면 운영체제의 자원을 낭비하게 되므로 유의하여야 한다.
○ 모든 UNIX 프로세스는 파일을 찾을 때 기본위치로 삼는 현재 디렉토리라는 것을 가지고 있다. ○ pwd 명령은 현재 디렉토리의 위치를 돌려 준다. ○ cd 명령은 현재 디렉토리를 변경한다.
○ 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 프로시져는 파일 시스템의 하위 디렉토리를 순차적으로 검색해 원하는 파일을 찾는다.
○ exit 명령은 스크립트를 종료한다. ○ exit 명령은 스크립트를 수행하던 모든 UNIX 프로세스도 종료시킨다. ○ exit 명령에 정수 인자를 주면 그 인자가 프로세스의 종료 상태가 된다. ○ pid 명령은 현재 프로세스의 프로세스 ID를 돌려 준다. ○ 이 값은 수행할 때마다 변하므로 난수 발생시 seed 값으로 활용할 수 있다.
○ 환경 변수는 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에는 형식화된 모듈 시스템이 없기 때문에 다른 패키지에서 프로시져와 전역 변수의 충돌을 막기 위해 코드 변환이 필요하다.
○ Tcl 라이브러리 기능은 unknown 명령에 의해 사용할 수 있다. ○ Tcl 해석기가 해석할 수 없는 명령을 만나게 되는 경우는 그 명령을 인다로 하여 unknown 명령을 호출하게 된다. ○ unknown 명령은 Tcl로 구현되어 있으므로 해석 불가능한 명령을 해석하는 새로운 unknown 명령을 만들 수 있다. ○ 여기서는 init.tcl 파일에 기본적으로 구현되어 있는 unknown 명령의 동작을 설명한다. ○ 라이브러리의 위치는 info library 명령을 사용해 알 수 있다. ○ 라이브러리 기능을 사용하기 위해 tclsh나 wish는 처음에 시작될 때 다음의 명령을 수행하게 된다. source [info library]/init.tcl
○ 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 } }
○ 스크립트 라이브러리를 사용하기 위해서는 unknown 명령에게 어디를 찾아 볼것인지를 알려 주어야 한다. ○ unknown 명령은 찾을 디렉토리를 auto_path라는 변수에 넣어 둔다. ○ auto_noloca 명령은 unknown 명령 메커니즘을 취소시킨다.
○ tclIndex 파일을 살펴 보면 auto_index라는 배열이 정의된 것을 알 수 있다. ○ 각 배열의 한 원소는 스크립트 라이브러리를 정의한다. ○ tclIndex 파일의 줄을 하나 살펴 보면 다음과 같다. set auto_index(Bind_Interface) "source $dir/bind_ui.tcl" ○ tclIndex 파일을 읽을 때 $dir은 tclIndex 파일을 포함하는 디렉토리 이름으로 대체된다. |
'개발 > 프로그래밍(일반)' 카테고리의 다른 글
Sending And Receiving UDP Messages with node.js (0) | 2014.07.27 |
---|---|
vi 명령어 (0) | 2014.07.10 |
[스크랩] [Oracle Pro*C 실무 프로젝트 활용서] Part 1. Proc*C 개요 (0) | 2014.04.22 |
이클립스 패키지 비교 (0) | 2013.10.29 |
프로그램 코딩 시 네이밍 규칙과 들여쓰기 (0) | 2013.10.18 |