Tcl 8.0부터 바이너리 데이터를 조작하기 위한 binary 커맨드가 추가되었습니다. binary 커맨드를 사용하면 문자열과 바이너리 데이터 간의 상호 변환이 가능합니다.
바이너리 변환
binary format은 formatString으로 지정한 형식에 따라 value로 지정한 문자열을 바이너리 데이터로 변환하여 반환합니다.
binary format formatString ?value value ...?
아래는 formatString 예시입니다.
$ binary fomat a3a2 Good morning!
Goomo
formatString으로 "a3″과 "a2″ 두 개의 필드가 지정되어 있고, 해당 값은 "Good"과 "morning!"의 두 가지 값이 지정되어 있습니다. 필드의 첫 번째 문자는 변환 방법을 나타내는 타입입니다. 그 뒤의 숫자는 변환을 반복할 횟수를 나타냅니다. 숫자 대신 "*"를 사용하면 해당 value의 모든 문자열을 변환합니다. "a" 타입은 value 문자열의 count 자릿수만큼의 바이너리 데이터로 변환하여 반환합니다. 예제의 경우, Goo의 3글자와 mo의 2글자를 바이너리 데이터로 변환하여 반환합니다.
Goomo의 문자를 16진수로 표현하면 다음과 같습니다.
47 6f 6f 6d 6f
G o o m o
따라서 "\x47\x6f\x6f\x6d\x6f"의 바이너리 값이 반환됩니다. 위의 예는 아스키코드 범위 내의 문자이므로 변환 후에도 문자로 보이지만, 한글 등 아스키코드 범위를 벗어난 문자인 경우 반환된 바이너리 데이터는 깨져 보일 것입니다.
타입으로 지정할 수 있는 종류는 다음과 같습니다.
a
카운트 수만큼 문자열을 바이너리로 변환합니다. value가 카운트 수보다 적으면 제로(null)로 패딩 합니다. 카운트 수에"*"를 지정하면 value의 나머지를 모두 사용합니다. 카운트 수가 1이면 생략할 수 있습니다.
binary format a6a*a hello world !
=> hello\000world!
A
제로(null) 대신에 스페이스로 패딩 하는 것만 제외하면 a와 같습니다.
binary format A6A*A hello world !
=> hello world!
b
카운트 수만큼 2진수(low-high 순) 문자열을 바이너리로 변환합니다.
binary format b5b* 11100 111000011010
=> \x07\x87\x05
B
카운트 수만큼 2진수(high-low 순) 문자열을 바이너리로 변환합니다.
binary format B5B* 11100 111000011010
=> \xe0\xe1\xa0
h
카운트 수만큼 16진수(high-low 순) 문자열을 바이너리로 변환합니다.
binary format h3h* AB def
=> \xba\x00\xed\x0f
H
카운트 수만큼 16진수(low-high 순) 문자열을 바이너리로 변환합니다.
binary format H3H* ab DEF
=> \xab\x00\xde\xf0
c
카운트 수만큼 숫자 문자열을 8비트 정수값으로 변환합니다. 리스트의 요소가 카운트 수보다 많으면 초과 요소를 무시하고, 리스트의 요소가 카운트 수보다 적으면 에러가 발생합니다.
binary format c3cc* {3 -3 128 1} 260 {2 5}
=> \x03\xfd\x80\x04\x02\x05
binary format c {2 5}
=> expected integer but got "2 5"
s
카운트 수만큼 숫자 문자열을 16비트 정수값(little-endian)으로 변환합니다.
binary format s3 {3 -3 258 1}
=> \x03\x00\xfd\xff\x02\x01
S
카운트 수만큼 숫자 문자열을 16비트 정수값(big-endian)으로 변환합니다.
binary format S3 {3 -3 258 1}
=> \x00\x03\xff\xfd\x01\x02
i
카운트 수만큼 숫자 문자열을 32비트 정수값(little-endian)으로 변환합니다.
binary format i3 {3 -3 65536 1}
=> \x03\x00\x00\x00\xfd\xff\xff\xff\x00\x00\x01\x00
I
카운트 수만큼 숫자 문자열을 32비트 정수값(big-endian)으로 변환합니다.
binary format I3 {3 -3 65536 1}
=> \x00\x00\x00\x03\xff\xff\xff\xfd\x00\x01\x00\x00
f
카운트 수만큼 숫자 문자열을 단정도 부동소수점 값으로 변환합니다.
binary format f2 {1.6 3.4}
=> \xcd\xcc\xcc\x3f\x9a\x99\x59\x40
d
카운트 수만큼 숫자 문자열을 배정도 부동소수점 값으로 변환합니다.
binary format d1 {1.6}
=> \x9a\x99\x99\x99\x99\x99\xf9\x3f
x
카운트 수만큼 제로(null)로 변환합니다. 카운트 수에 "*"를 지정하면 에러가 발생합니다.
binary format a3xa3x2a3 abc def ghi
=> abc\000def\000\000ghi
X
출력 위치를 카운트 수만큼 뒤로 이동합니다. 카운트 수에"*"를 지정하면 처음 위치로 이동합니다.
binary format a3X*a3X2a3 abc def ghi
=> dghi
@
카운트 수로 지정한 절대 위치에 출력 위치를 이동합니다. 카운트 수에 0을 지정하면 처음 위치로, "*"를 지정하면 마지막 위치로 이동합니다. 최종 위치를 넘어 데이터가 저장될 경우, 초기화되지 않은 위치에 제로(null)로 패딩 됩니다.
binary format a5@2a1@*a3@10a1 abcde f ghi j
=> abfdeghi\000\000j
문자열 변환
binary scan 명령어는 지정한 formatString에 따라 바이너리 데이터를 문자열로 변환합니다.
binary scan binaryData formatString ?varName varName ...?
formatString 은 타입과 카운트 수로 구성되며, 변환 결과는 varName으로 지정한 변수에 저장이 됩니다. binary scan 명령어는 생성된 변수의 개수를 반환합니다. formatString에 지정할 수 있는 타입은 아래와 같습니다.
a
바이너리 데이터를 카운트 수만큼 문자열로 변환합니다. 카운트 수에 "*"를 지정하면 binaryData의 나머지를 모두 사용합니다. 카운트 수가 1일 때는 생략할 수 있습니다.
binary scan abcde\000fghi a6a10 var1 var2
=> 1
set var1
=> abcde\000
set var2
=> can't read "var2": no such variable
A
제로(null)를 최종적으로 제거하는 것만 제외하면 a와 같습니다.
binary scan "abc efghi \000" A* var1
=> 1
set var1
=> abc efghi
b
바이너리 데이터를 카운트 수만큼 2진수(low-high 순)의 문자열로 변환합니다.
binary scan \x07\x87\x05 b5b* var1 var2
=> 2
set var1
=> 01110
set var2
=> 1110000110100000
B
바이너리 데이터를 카운트 수만큼 2진수(high-low 순)의 문자열로 변환합니다.
binary scan \x07\x87\x05 B5B* var1 var2
=> 2
set var1
=> 01110
set var2
=> 1000011100000101
h
바이너리 데이터를 카운트 수만큼 16진수(low-high 순)의 문자열로 변환합니다.
binary scan \x07\x86\x05 h3h* var1 var2
=> 2
set var1
=> 706
set var2
=> 50
H
바이너리 데이터를 카운트 수만큼 16진수(high-low 순)의 문자열로 변환합니다.
binary scan \x07\x86\x05 H3H* var1 var2
=> 2
set var1
=> 078
set var2
=> 05
c
카운트 수만큼 8비트 정수의 바이너리 데이터를 8비트 부호 있는 정수의 문자열로 변환합니다.
binary scan \x07\x86\x05 c2c* var1 var2
=> 2
set var1
=> 7 -122
set var2
=> 5
반환된 값은 부호 있는 정수이며, 아래 식으로 부호 없는 정수로 변환할 수 있습니다.
expr ( $num + 0x100 ) % 0x100
s
카운트 수만큼 16비트 정수의 바이너리 데이터를 16비트 부호 있는 정수(little-endian) 문자열로 변환합니다.
binary scan \x05\x00\x07\x00\xf0\xff s2s* var1 var2
=> 2
set var1
=> 5 7
set var2
=> -16
반환된 값은 부호 있는 정수이며, 아래 식으로 부호 없는 정수로 변환할 수 있습니다.
expr ( $num + 0x10000 ) % 0x10000
S
카운트 수만큼 16비트 정수의 바이너리 데이터를 16비트 부호 있는 정수(big-endian) 문자열로 변환합니다.
binary scan \x00\x05\x00\x07\xff\xf0 S2S* var1 var2
=> 2
set var1
=> 5 7
set var2
=> -16
i
카운트 수만큼 32비트 정수의 바이너리 데이터를 32비트 부호 있는 정수(little-endian) 문자열로 변환합니다.
binary scan \x05\x00\x00\x00\x07\x00\x00\x00\xf0\xff\xff\xff i2i* var1 var2
=> 2
set var1
=> 5 7
set var2
=> -16
반환된 값은 부호 있는 정수이며, Tcl에서는 부호 없는 정수로 표현할 수 없습니다.
I
카운트 수만큼 32비트 정수의 바이너리 데이터를 32비트 부호 있는 정수(big-endian) 문자열로 변환합니다.
binary \x00\x00\x00\x05\x00\x00\x00\x07\xff\xff\xff\xf0 I2I* var1 var2
=> 2
set var1
=> 5 7
set var2
=> -16
f
카운트 수만큼 실수형 바이너리 데이터를 단정도 실수형 문자열로 변환합니다.
binary scan \x3f\xcc\xcc\xcd f var1
=> 1
set var1
=> 1.6000000238418579
d
카운트 수만큼 실수형 바이너리 데이터를 배정도 실수형 문자열로 변환합니다.
binary scan \x9a\x99\x99\x99\x99\x99\xf9\x3f d var1
=> 1
set var1
=> 1.6000000000000001
x
카운트 수만큼 바이트 수만큼 스캔 위치를 앞으로 이동합니다. 카운트 수에 "*"를 지정하면 마지막 위치로 이동합니다.
binary scan \x01\x02\x03\x04 x2H* var1
=> 1
set var1
=> 0304
X
카운트 수만큼 바이트 수만큼 스캔 위치를 뒤로 이동합니다. 카운트 수에 "*"를 지정하면 처음 위치로 이동합니다.
binary scan \x01\x02\x03\x04 c2XH* var1 var2
=> 2
set var1
=> 1 2
set var2
=> 020304
@
카운트 수로 지정한 절대 위치로 스캔 위치를 이동합니다. 카운트 수에 0을 지정하면 처음 위치로, "*"를 지정하면 마지막 위치로 이동합니다.
binary scan \x01\x02\x03\x04 c2@1H* var1 var2
=> 2
set var1
=> 1 2
set var2
=> 020304
주의 사항
타입이 c, s, S (64비트 시스템에서는 i, I도 포함)는 데이터의 최상위 비트가 1일 때, 부호 확장(sign extension)으로 인해 아래와 같은 문제가 발생합니다.
set signShort [binary format s1 0x8000]
binary scan $signShort s1 val; # val == 0xFFFF8000
만약 부호 없는 값을 얻고 싶다면, 아래와 같이 마스크 처리를 해야 합니다.
set val [expr {$val & 0xFFFF}]; # val == 0x8000
바이너리 데이터의 처리는 머신 네이티브 정수값의 바이트 순서에 의존합니다. 때로는 tcl_platform(byteOrder) 변수의 내용에 따라 s와 S, i와 I 타입을 선택해야 할 필요가 있습니다.