trace — 변수 접근, 명령 사용, 명령 실행 감시하기
trace — 변수 접근, 명령 사용, 명령 실행 감시하기
변수가 읽히거나 쓰일 때, 명령이 이름이 바뀌거나 이름이 지워질 때, 특정 명령이 실행될 때 무언가 반응하고 싶을 때가 있어요. trace는 바로 그런 지점에 끼어들어 지정한 명령을 실행하게 해주는 기능이에요.
본문
trace option ?arg arg ...?
이 명령은 특정 연산이 호출될 때마다 Tcl 명령이 실행되게 해요. 유효한 옵션(축약 가능)은 다음과 같아요.
trace add
-
trace add type name ops ?args?: 여기서type은command,execution,variable중 하나예요. -
trace add command name ops commandPrefix:name명령이ops목록이 주는 방식 중 하나로 수정될 때마다commandPrefix(추가 인자 포함)가 실행되도록 준비해요.name은 명령이 쓰는 보통의 네임스페이스 해석 규칙으로 해석돼요. 명령이 없으면 오류가 발생해요.ops는 관심 있는 연산을 나타내며 다음 항목 중 하나 이상의 목록이에요.rename: 추적되는 명령이 이름이 바뀔 때마다commandPrefix를 호출해요. 빈 문자열로 이름을 바꾸는 것은 삭제로 간주되며"rename"으로 추적되지 않아요.delete: 추적되는 명령이 삭제될 때commandPrefix를 호출해요. 명령은rename명령으로 빈 문자열에 이름을 바꿔 명시적으로 삭제할 수 있어요. 인터프리터가 삭제될 때도 명령이 삭제되지만, 실행할 인터프리터가 없으므로 trace는 호출되지 않아요.
trace가 발동하면, 추적되는 연산에 따라 여러 인자가
commandPrefix에 추가되어 실제 명령은 다음과 같아요.commandPrefix oldName newName opoldName과newName은 추적되는 명령의 현재(이전) 이름과 이름이 바뀌는 대상 이름을 줘요("delete"연산이면 빈 문자열).op는 명령에 수행되는 연산으로 위에서 정의한rename또는delete중 하나예요. trace 연산으로 명령이 삭제되는 것을 막을 수는 없어요. Tcl은 trace가 끝나면 항상 명령을 제거해요. 재귀적 이름 바꾸기나 삭제는 같은 유형의 추가 trace 평가를 일으키지 않아요. 따라서 명령을 자체 삭제하는 delete trace나 명령을 자체 이름 바꾸는 rename trace는 추가 trace 평가를 일으키지 않아요.oldName과newName은 모두 그들이 나타나는 네임스페이스로 완전히 정규화돼요. -
trace add execution name ops commandPrefix:name명령이 실행될 때마다,ops목록이 표시한 지점에서 trace가 발생하도록commandPrefix(추가 인자 포함)가 실행되게 준비해요.name은 명령이 쓰는 보통의 네임스페이스 해석 규칙으로 해석돼요. 명령이 없으면 오류가 발생해요.ops는 다음 항목 중 하나 이상의 목록이에요.enter: 실제 실행이 일어나기 직전에name명령이 실행될 때마다commandPrefix를 호출해요.leave: 실제 실행이 일어난 직후에name명령이 실행될 때마다commandPrefix를 호출해요.enterstep: 프로시저name의 실행 시작부터 그 프로시저가 끝날 때까지 실행되는 모든 Tcl 명령에 대해commandPrefix를 호출해요. 보고되는 Tcl 명령의 실제 실행 직전에 호출돼요. 예를 들어"proc foo {} { puts \"hello\" }"가 있으면"puts \"hello\""가 실행되기 바로 직전에 enterstep trace가 호출돼요. 프로시저를 가리키지 않는 명령 이름에 enterstep trace를 설정해도 오류가 나지 않고 그냥 무시돼요.leavestep: 프로시저name의 실행 시작부터 끝날 때까지 실행되는 모든 Tcl 명령에 대해commandPrefix를 호출해요. 보고되는 Tcl 명령의 실제 실행 직후에 호출돼요. 프로시저를 가리키지 않는 명령 이름에 leavestep trace를 설정해도 오류가 나지 않고 그냥 무시돼요.
trace가 발동하면, 추적되는 연산에 따라 여러 인자가
commandPrefix에 추가돼요.enter와enterstep연산의 경우:commandPrefix command-string opcommand-string은 현재 실행 중인 완전한 명령(enter 연산이면 추적된 명령, enterstep 연산이면 임의의 명령)을 완전히 확장된 인자 형태로 모두 담아요.op는 위에서 정의한enter또는enterstep중 하나예요. trace 연산으로 해당 명령을 삭제해 실행을 막을 수 있어요. 물론 명령이 나중에 실행되면"invalid command"오류가 발생해요.leave와leavestep연산의 경우:commandPrefix command-string code result opcommand-string은 현재 실행 중인 완전한 명령이에요.code는 그 실행의 결과 코드를,result는 결과 문자열을 줘요.op는leave또는leavestep중 하나예요. 많은 enterstep/leavestep trace를 만들면 직관적이지 않은 결과가 나올 수 있다는 점을 주의하세요. 한 trace의 호출된 명령이 다른 trace의 추가 명령 호출로 이어질 수 있기 때문이에요.commandPrefix는 추적된 연산을 호출한 코드와 같은 컨텍스트에서 실행돼요. 그래서 프로시저에서 호출된commandPrefix는 프로시저의 코드와 같은 로컬 변수에 접근할 수 있어요. 이 컨텍스트는 trace가 만들어진 컨텍스트와 다를 수 있어요.commandPrefix가 프로시저를 호출하면(보통 그렇게 함) 그 프로시저는 추적 연산을 호출한 코드의 로컬 변수에 접근하려면upvar나uplevel명령을 써야 해요.실행 trace 중
commandPrefix가 실행되는 동안name의 trace는 일시적으로 비활성화돼요. 이는commandPrefix가 본문에서name을 실행해도 다른 trace가 다시 호출되지 않게 해요.commandPrefix실행 중 오류가 발생하면name명령 전체가 같은 오류를 반환해요.name에 여러 trace가 설정되면,enter와enterstep연산의 경우 trace된 명령이 원래 만들어진 순서의 역순으로 호출되고,leave와leavestep연산의 경우 원래 만들어진 순서대로 호출돼요. 실행 trace의 동작은 다른 네임스페이스로 가져온 명령 이름에 대해서는 현재 정의되지 않아요. -
trace add variable name ops commandPrefix: 변수name이ops목록이 주는 방식 중 하나로 접근될 때마다commandPrefix가 실행되게 준비해요.name은 일반 변수, 배열의 요소, 또는 배열 전체를 가리킬 수 있어요(즉name은 괄호로 묶인 인덱스 없이 배열 이름만일 수 있음).name이 배열 전체를 가리키면 배열의 어떤 요소가 조작될 때마다commandPrefix가 호출돼요. 변수가 없으면 만들어지지만 값은 주어지지 않아요. 그래서 네임스페이스 쿼리에는 보이지만info exists쿼리에는 보이지 않아요.ops는 다음 항목 중 하나 이상의 목록이에요.array: 변수가array명령을 통해 접근되거나 수정될 때마다commandPrefix를 호출해요. 단name이array명령 호출 시점에 스칼라 변수면 그렇지 않아요.name이 스칼라 변수면array명령으로의 접근은 trace를 발동시키지 않아요.read: 변수가 읽힐 때마다commandPrefix를 호출해요.write: 변수가 쓰일 때마다commandPrefix를 호출해요.unset: 변수가 unset될 때마다commandPrefix를 호출해요. 변수는unset명령으로 명시적으로, 또는 프로시저가 반환할 때(지역 변수 모두 unset됨) 암시적으로 unset될 수 있어요. 인터프리터가 삭제될 때도 변수가 unset되지만, 실행할 인터프리터가 없으므로 trace는 호출되지 않아요.
trace가 발동하면 세 인자가
commandPrefix에 추가되어 실제 명령은 다음과 같아요.commandPrefix name1 name2 opname1과name2는 접근되는 변수의 이름을 줘요. 스칼라면name1이 변수 이름이고name2는 빈 문자열. 배열 요소면name1이 배열 이름,name2가 배열 인덱스. 전체 배열이 삭제되고 trace가 단일 요소가 아닌 배열 전체에 등록되어 있으면name1이 배열 이름이고name2는 빈 문자열.name1과name2는trace variable명령에 쓰인 이름과 반드시 같지는 않아요.upvar명령이 프로시저가 다른 이름으로 변수를 참조하게 하기 때문이에요.op는 변수에 수행되는 연산으로 위에서 정의한read,write,unset중 하나예요.commandPrefix는 추적된 연산을 호출한 코드와 같은 컨텍스트에서 실행돼요. 프로시저의 일부로 변수에 접근했다면commandPrefix는 프로시저의 코드와 같은 로컬 변수에 접근할 수 있어요. 이 컨텍스트는 trace가 만들어진 컨텍스트와 다를 수 있어요.commandPrefix가 프로시저를 호출하면 추적된 변수에 접근하려면upvar나uplevel을 써야 해요.name1이 변수에 trace를 설정할 때 쓴 이름과 반드시 같지는 않다는 점도 주의하세요.upvar명령으로 정의된 변수를 통해 접근하면 차이가 생길 수 있어요.read와writetrace의 경우commandPrefix가 변수를 수정해 추적 연산의 결과에 영향을 줄 수 있어요. read나 write trace 중commandPrefix가 변수 값을 수정하면 새 값이 추적 연산의 결과로 반환돼요.commandPrefix의 반환 값은 무시되는데, 단 어떤 종류의 오류라도 반환하면 추적된 연산도 trace 명령이 반환한 것과 같은 오류 메시지로 오류를 반환해요(이 메커니즘으로 읽기 전용 변수를 구현할 수 있어요). write trace의 경우commandPrefix가 변수 값이 바뀐 뒤에 호출돼요. 변수에 새 값을 써서 write 연산에 지정된 원래 값을 덮어쓸 수 있어요. 읽기 전용 변수를 구현하려면commandPrefix가 변수의 이전 값을 복원해야 해요.read나 write trace 중
commandPrefix가 실행되는 동안 변수의 trace는 일시적으로 비활성화돼요. 즉commandPrefix가 수행하는 읽기·쓰기는commandPrefix(나 다른 trace)를 다시 호출하지 않고 직접 일어나요. 다만commandPrefix가 변수를 unset하면 unset trace는 호출돼요.unset trace가 호출되면 변수는 이미 삭제되어 있어서 trace 없이 정의되지 않은 것처럼 보여요. 프로시저 반환 때문에 unset이 발생하면 trace는 반환되는 프로시저의 변수 컨텍스트에서 호출돼요. 반환하는 프로시저의 스택 프레임은 더 이상 존재하지 않아요. unset trace 동안 trace는 비활성화되지 않아서, unset trace 명령이 새 trace를 만들고 변수에 접근하면 그 trace가 호출돼요. unset trace의 오류는 무시돼요.
변수에 여러 trace가 있으면 최근 것부터 만들어진 순서대로 호출돼요. 한 trace가 오류를 반환하면 그 변수에 대해 추가 trace는 호출되지 않아요. 배열 요소에 trace가 있고 배열 전체에도 trace가 있으면 배열 전체의 trace가 요소의 trace보다 먼저 호출돼요.
만들어진 trace는 아래의
trace remove variable명령으로 제거되거나, 변수가 unset되거나, 인터프리터가 삭제될 때까지 유효해요. 배열 요소를 unset하면 그 요소의 trace는 제거되지만 배열 전체의 trace는 제거되지 않아요. 이 명령은 빈 문자열을 반환해요.
trace remove
trace remove type name opList commandPrefix: 여기서type은command,execution,variable중 하나예요.trace remove command name opList commandPrefix: 명령name에 연산opList와 명령commandPrefix로 설정된 trace가 있으면 그 trace를 제거해commandPrefix가 다시는 호출되지 않게 해요. 빈 문자열을 반환해요.name이 없으면 오류를 발생해요.trace remove execution name opList commandPrefix: 실행 tracename에 연산opList와 명령commandPrefix로 설정된 trace가 있으면 제거해요. 빈 문자열을 반환해요.name이 없으면 오류를 발생해요.trace remove variable name opList commandPrefix: 변수name에 연산opList와 명령commandPrefix로 설정된 trace가 있으면 제거해요. 빈 문자열을 반환해요.
trace info
trace info type name: 여기서type은command,execution,variable중 하나예요.trace info command name: 명령name에 현재 설정된 각 trace에 대해 하나의 요소를 담은 리스트를 반환해요. 각 요소는 그 trace와 연관된opList와commandPrefix를 담은 두 요소짜리 리스트예요.name에 설정된 trace가 없으면 결과는 빈 문자열이에요.name이 없으면 오류를 발생해요.trace info execution name: 실행 trace와 관련해 명령name에 현재 설정된 각 trace에 대해 하나의 요소를 담은 리스트를 반환해요. 설정된 trace가 없으면 빈 문자열,name이 없으면 오류를 발생해요.trace info variable name: 변수name에 현재 설정된 각 trace에 대해 하나의 요소를 담은 리스트를 반환해요.name이 없거나 설정된 trace가 없으면 빈 문자열이에요.
하위 호환 서브컴마드
하위 호환을 위해 세 개의 추가 서브컴마드가 있어요.
trace variable name ops command:trace add variable name ops command와 동일해요.trace vdelete name ops command:trace remove variable name ops command와 동일해요.trace vinfo name:trace info variable name과 동일해요.
이 서브컴마드들은 더 이상 쓰이지 않으며(비권장) 향후 Tcl 버전에서 제거될 가능성이 있어요. 그들은 array, read, write, unset을 각각 a, r, w, u로 대체하고, ops 인자가 리스트가 아니라 rwua 같은 단순한 문자열 연결인 옛 문법을 사용해요.
예제
전역 변수 foo와 bar가(그 시점에 upvar 명령으로 만든 다른 로컬 이름을 가져도) 갱신될 때마다 메시지를 출력해요.
proc tracer {varname args} {
upvar #0 $varname var
puts "$varname was updated to be \"$var\""
}
trace add variable foo write "tracer foo"
trace add variable bar write "tracer bar"
전역 변수 foobar가 항상 전역 변수 foo와 bar의 곱을 담도록 해요.
proc doMult args {
global foo bar foobar
set foobar [expr {$foo * $bar}]
}
trace add variable foo write doMult
trace add variable bar write doMult
Tcl 프로시저 처리 중 실행되는 명령을 추적해 출력해요.
proc x {} { y }
proc y {} { z }
proc z {} { puts hello }
proc report args {puts [info level 0]}
trace add execution x enterstep report
x
→ report y enterstep
report z enterstep
report {puts hello} enterstep
hello