interp — Tcl 인터프리터 생성·조작
interp — Tcl 인터프리터 생성·조작
한 애플리케이션 안에서 독립된 Tcl 인터프리터를 여러 개 만들어야 할 때가 있어요 — 특히 신뢰할 수 없는 스크립트를 안전하게 실행해야 한다면 더 그렇죠. interp 명령어가 인터프리터를 만들고 다루는 기능을 제공해요.
본문
시그니처
interp subcommand ?arg arg ...?
interp는 같은 애플리케이션 안에서 생성 인터프리터와 공존하는 새 Tcl 인터프리터를 하나 이상 만들 수 있게 해요. 만든 인터프리터를 부모(parent), 새로 만든 인터프리터를 자식(child) 이라고 불러요. 부모는 여러 자식을 만들 수 있고, 각 자식도 다시 자신을 부모로 하는 자식을 만들어 인터프리터의 계층 구조가 생겨요.
각 인터프리터는 서로 독립적이에요 — 명령어·프로시저·전역 변수를 위한 자기만의 이름 공간을 가져요. 부모 인터프리터는 앨리어스(alias) 라는 메커니즘으로 자식들과 자신 사이의 연결을 만들 수 있어요. 앨리어스는 자식 인터프리터의 명령어로, 호출되면 부모나 다른 자식 인터프리터의 명령어를 호출하게 해요. 인터프리터 사이의 다른 연결은 환경 변수(env 변수, 보통 애플리케이션의 모든 인터프리터가 공유)와 자원 한도 초과 콜백뿐이에요. 파일 이름 공간은 더 이상 인터프리터 간에 공유되지 않아요 — 파일을 공유하고 열린 파일 참조를 전송하는 명시적 명령어가 제공돼요.
interp는 안전한 인터프리터(safe interpreter) 도 지원해요. 안전한 인터프리터는 기능이 크게 제한된 자식이라, 신뢰할 수 없는 스크립트가 다른 인터프리터나 애플리케이션 환경을 손상시키지 않을 것을 보장하면서 실행하기에 안전해요. 예를 들어 모든 IO 채널 생성 명령어와 서브프로세스 생성 명령어가 안전한 인터프리터에서는 접근 불가능해져요. 위험한 기능은 안전한 인터프리터에서 제거되는 게 아니라 숨겨져(hidden) 있어서 신뢰할 수 있는 인터프리터만 접근할 수 있어요. 앨리어스 메커니즘은 자식과 부모 사이의 보호된 통신(커널 호출과 유사)에 쓸 수 있어요.
한정된 인터프리터 이름은 인터프리터 계층에서 조상의 부분 집합을 담은 적절한 Tcl 리스트에 즉시 부모의 이름을 이어 붙인 형태예요. 인터프리터 이름은 사용되는 인터프리터 기준 상대적이에요. 예를 들어 현재 인터프리터의 자식이 a, 그 자식이 a1, 다시 그 자식이 a11이라면, a 안에서 a11의 한정 이름은 리스트 a1 a11이에요. 명령어가 평가되는 인터프리터 자신은 항상 {}(빈 리스트나 문자열)로 가리킬 수 있어요. 자식에서는 앨리어스를 통하지 않고는 부모(조상)를 이름으로 가리킬 수 없고, 애플리케이션에서 처음 만든 인터프리터를 가리키는 전역 이름도 없어요 — 둘 다 안전성 때문이에요.
interp 하위 명령어
interp alias srcPath srcToken—srcToken이 나타내는 앨리어스와 연결된targetCmd와args목록 반환(앨리어스 생성 시 반환된 값).interp alias srcPath srcToken {}—srcPath의 자식에서srcToken의 앨리어스 삭제. 소스 명령어가 이름 바뀌었으면 바뀐 명령어가 삭제돼요.interp alias srcPath srcCmd targetPath targetCmd ?arg arg ...?— 자식과 자식 사이에 앨리어스 생성. 두 자식 모두 명령어를 호출하는 인터프리터 아래 계층 어디든 있을 수 있어요.srcPath와srcCmd가 앨리어스 소스를 식별하고,srcCmd는 소스 인터프리터에 새로 만들어질 명령어 이름이에요.targetPath·targetCmd가 대상 인터프리터와 명령어를,arg들이srcCmd호출 시 인자 앞에 붙는 추가 인자를 지정해요.targetCmd는 이 호출 시점에 정의되어 있지 않아도 되고, 이 명령어로 생성되지는 않아요. 소스 명령어가 호출될 때마다 대상 명령어가 대상 인터프리터에서 호출되도록 해요. 나중에 명령어가 이름 바뀌어도srcCmd를 고유 식별하는 토큰을 반환해요.interp aliases ?path?—path인터프리터에 정의된 모든 앨리어스 소스 명령어 토큰 리스트 반환.interp bgerror path ?cmdPrefix?—path의 현재 백그라운드 예외 처리기 가져오기·설정.cmdPrefix없으면 현재 처리기 반환, 있으면 설정할 처리기를 설명하는 최소 길이 1의 단어 리스트.interp cancel ?-unwind? ?--? ?path? ?result?—path인터프리터에서 평가 중인 스크립트 취소.-unwind없이는 둘러싼catch를 만나거나 더 이상 호출이 없을 때까지 평가 스택을 풀고,-unwind있으면 중간catch를 무시하고 풀어요.--로 스위치 끝 표시.result가 있으면 오류 메시지로 사용.interp create ?-safe? ?--? ?path?—path로 식별되는 자식 인터프리터와 새 명령어(자식 명령어) 생성. 자식 명령어 이름은path의 마지막 구성 요소.path를 생략하면interpx(x는 정수) 형태의 고유 이름 생성.-safe를 지정하면(또는 부모가 안전하면) 제한된 기능의 안전한 인터프리터로 생성. 반환값은 새 인터프리터 이름. 자식 이름은 부모의 자식들 사이에서 고유해야 하며, 중복되면 오류. 새 자식의 초기 재귀 한도는 부모의 현재 재귀 한도.interp debug path ?-frame ?bool??—path에서 프레임 수준 스택 정보를 캡처할지 제어.-frame을 주면 디버그 설정을 불리언으로 설정하고 현재 설정 반환.info frame의 출력에만 영향: 바이트코드 수준 명령어 호출의 정확한 프레임 정보가 이 설정이 켜졌을 때만 캡처돼요. 한번 켜면 끌 수 없어요(무시됨).interp delete ?path ...?— 경로 인자들이 주는 인터프리터와 각각의 자식, 자식 명령어를 삭제. 존재하지 않으면 오류.interp eval path arg ?arg ...?—concat처럼 모든 인자를 이어 붙인 뒤path자식에서 Tcl 스크립트로 평가. 평가 결과(오류 시-errorinfo·-errorcode같은 모든 반환 옵션 포함)를 호출 인터프리터로 반환. 스크립트는path인터프리터의 현재 컨텍스트 스택 프레임에서 실행돼요.interp exists path— 지정한 경로의 자식이 이 부모에 있으면 1, 아니면 0.path생략 시 호출 인터프리터.interp expose path hiddenName ?exposedCmdName?— 숨겨진 명령어hiddenName을 노출시켜(새 이름exposedCmdName으로)path인터프리터로 되돌림. 대상 이름의 노출 명령어가 이미 있으면 실패.interp hide path exposedCmdName ?hiddenCmdName?— 노출 명령어exposedCmdName을 숨겨hiddenCmdName으로 이름 변경(지정 안 하면 이름 유지). 대상 이름의 숨김 명령어가 있으면 실패. 둘 다 네임스페이스 한정자를 가질 수 없어요. 숨길 명령어는 현재 네임스페이스가 달라도 전역 네임스페이스에서 찾아요.interp hidden path—path의 모든 숨김 명령어 이름 리스트 반환.interp invokehidden path ?-option ...? hiddenCmdName ?arg ...?—path에서 숨김 명령어hiddenCmdName을 주어진 인자로 호출. 인자에 치환·평가를 적용하지 않아요. 옵션:-namespace nsName(지정 네임스페이스에서 호출),-global(전역 레벨에서 호출),--(hiddenCmdName이-로 시작할 때). 둘 다 주면-namespace무시. 기본은path의 현재 컨텍스트 스택 프레임에서 실행.interp issafe ?path?—path가 안전하면 1, 아니면 0.interp limit path limitType ?-option? ?value ...?—path의 자원 한도limitType설정·조작·조회. 옵션이 없으면 현재 구성 반환, 옵션만 있으면 그 값 반환, 아니면 옵션/값 쌍 지정.interp marktrusted path—path를 신뢰됨으로 표시. 숨김 명령어는 노출하지 않아요. 신뢰된 인터프리터에서만 호출 가능. 이미 신뢰됐으면 효과 없음.interp recursionlimit path ?newlimit?—path의 최대 허용 중첩 깊이 반환.newlimit를 지정하면 그보다 깊은Tcl_Eval호출 중첩에서 오류가 나도록 설정.newlimit는 1과 플랫폼의 non-long 정수 최댓값 사이 양의 정수. 이 명령어는 Tcl 호출 스택의 최대 크기만 설정하고, 애플리케이션이 쓰는 C 스택 오버플로는 그 자체로 막지 못해요.interp share srcPath channelId destPath—channelId채널을srcPath와destPath사이에서 공유되게 함. 양쪽이 같은 권한을 가지고, 둘 다 닫아야 기본 채널이 닫혀요.interp slaves ?path?—path의 모든 자식 인터프리터 이름 리스트 반환.interp children ?path?—interp slaves ?path?의 동의어.interp target path alias— 앨리어스의 대상 인터프리터 설명 리스트 반환. 대상이 호출 인터프리터면 빈 리스트. 호출 인터프리터가 아니거나 그 후손이 아니면 오류.interp transfer srcPath channelId destPath—channelId채널을destPath에서 사용 가능·srcPath에서 불가능하게 전송.
자식 명령어 (child COMMAND)
interp로 만든 각 자식 인터프리터에 대해, 부모에 인터프리터와 같은 이름의 새 Tcl 명령어가 생성돼요. 일반 형태는 child command ?arg arg ...?. 유효한 형태는 전역 interp 하위 명령어와 대체로 같아요:
child aliases—child의 모든 앨리어스 토큰 리스트.child alias srcToken— 앨리어스의targetCmd·args목록.child alias srcToken {}— 앨리어스 삭제.child alias srcCmd targetCmd ?arg ..?—child에서srcCmd가 호출될 때 부모에서targetCmd가 호출되는 앨리어스 생성.child bgerror ?cmdPrefix?— 백그라운드 예외 처리기 가져오기·설정.child eval arg ?arg ..?—child에서 스크립트 평가(전체 반환 옵션 포함).child expose hiddenName ?exposedCmdName?— 숨김 명령어 노출.child hide exposedCmdName ?hiddenCmdName?— 노출 명령어 숨김.child hidden— 모든 숨김 명령어 이름 리스트.child invokehidden ?-option ...? hiddenName ?arg ..?— 숨김 명령어 호출.child issafe— 안전한지.child limit limitType ?-option? ?value ...?— 자원 한도 설정·조회.child marktrusted— 신뢰됨 표시.child recursionlimit ?newlimit?— 재귀 한도 가져오기·설정.
안전한 인터프리터 (SAFE INTERPRETERS)
안전한 인터프리터는 기능이 제한되어, 가장 나쁜 적의 임의 스크립트를 실행해도 둘러싼 애플리케이션이나 컴퓨팅 환경을 손상시키지 않는 것이 안전해요. 특정 명령어·변수가 제거돼요. 예를 들어 디스크에 파일을 만드는 명령어와 exec 명령어(서브프로세스를 통한 손상 가능)가 제거돼요. 이런 기능에 제한된 접근은 인자를 신중히 검사하고 안전한 기능 부분집합에 대한 제한된 접근을 제공하는 부모 앨리어스를 만들어 줄 수 있어요.
-safe 스위치로 생성되며, 안전한 인터프리터가 만드는 자식도 안전해요. 안전한 인터프리터는 정확히 다음과 같은 내장 명령어 집합으로 생성돼요:
after append apply array binary break catch chan clock close concat continue dict eof error eval expr fblocked fcopy fileevent flush for foreach format gets global if incr info interp join lappend lassign lindex linsert list llength lrange lrepeat lreplace lsearch lset lsort namespace package pid proc puts read regexp regsub rename return scan seek set split string subst switch tell time trace unset update uplevel upvar variable vwait while zlib
다음 명령어는 interp create가 안전한 인터프리터를 만들 때 숨겨요:
cd encoding exec exit fconfigure file glob load open pwd socket source unload
이들은 나중에 Tcl 프로시저나 앨리어스로 다시 만들거나 interp expose로 재노출할 수 있어요. Tcl 지원 프로시저 라이브러리에서 다음 명령어는 안전한 인터프리터에 없어요:
auto_exec_ok auto_import auto_load auto_load_index auto_qualify unknown
특히 안전한 인터프리터엔 기본 unknown 명령어가 없어서 Tcl 기본 자동 로딩 기능을 쓸 수 없어요. 또 env 변수가 안전한 인터프리터에 없어 환경 변수를 공유하지 못해요 — env는 민감한 정보를 담을 수 있어 보안 위험이기 때문이에요(예: PGP 매뉴얼은 개인키 보호 암호를 PGPPASS 환경 변수에 저장하길 권장하는데, 이걸 신뢰할 수 없는 코드에 노출하면 보안 위험). 안전한 인터프리터에서 확장이 로드되면 안전하지 않은 명령어를 제거하도록 스스로 제한할 수도 있어요. 안전한 인터프리터는 자신을 포함한 어떤 인터프리터의 재귀 한도도 바꿀 수 없어요.
앨리어스 호출 (ALIAS INVOCATION)
앨리어스 메커니즘은 대상이 안전하지 않은 인터프리터라도 안전한 인터프리터에서 실행되는 신뢰할 수 없는 스크립트가 안전하게 쓸 수 있도록 신중히 설계됐어요. 안전성 보장에서 가장 중요한 것은 자식에서 부모로 전달되는 정보가 부모에서 절대 평가·치환되지 않는 것이에요.
자식에서 앨리어스 소스가 호출되면 그 명령어를 파싱할 때 보통의 Tcl 치환이 수행돼요. 소스 명령어의 명령어 프로시저는 인자를 받아 대상 targetCmd·args와 합쳐 새 인자 배열을 만들어요. srcCmd 단어들이 srcCmd arg1 arg2 ... argN이었다면 새 단어 집합은 targetCmd arg arg ... arg arg1 arg2 ... argN이에요. targetCmd로 대상 인터프리터에서 명령어 프로시저를 찾아 새 인자 집합으로 호출해요. 대상 인터프리터에 targetCmd가 없으면 오류. 추가 치환은 없어요 — 대상 명령어 프로시저는 보통의 Tcl 평가 메커니즘을 거치지 않고 직접 호출돼요. 각 단어의 치환은 정확히 한 번 일어나요. 안전한 인터프리터의 앨리어스 targetCmd를 작성할 때 그 명령어의 인자가 절대 평가·치환되지 않게 하는 게 매우 중요해요 — 그렇지 않으면 자식이 부모에서 임의 코드를 실행하는 탈출구가 되어 보안을 해칠 수 있어요.
숨김 명령어 (HIDDEN COMMANDS)
안전한 인터프리터는 안에서 실행되는 Tcl 프로그램이 쓸 수 있는 기능을 크게 제한해요. 위험한 명령어를 안전한 인터프리터에서 완전히 제거하는 대신 숨겨서, 그 인터프리터의 Tcl 스크립트에선 쓸 수 없게 해요. 하지만 그런 숨김 명령어는 안전한 인터프리터의 신뢰된 조상이 interp invokehidden으로 안전한 인터프리터 컨텍스트에서 호출할 수 있어요. 숨김 명령어와 노출 명령어는 별도 이름 공간에 있어서, 한 인터프리터에 같은 이름의 숨김 명령어와 노출 명령어를 정의할 수 있어요.
자식의 숨김 명령어는 앨리어스 호출 중 부모에서 호출되는 프로시저의 몸통에서 호출할 수 있어요. 예를 들어 자식에 source용 앨리어스를 만들고, 자식에서 호출되면 부모의 프로시저가 그 동작이 허용되는지(예: 자식이 접근 가능한 파일을 source하는지) 확인한 뒤 자식의 숨김 source 명령어를 호출해 파일 내용을 실제로 source할 수 있어요. 이때 자식에 source라는 이름의 명령어가 둘(앨리어스와 숨김 명령어) 존재해요.
부모가 앨리어스 호출 처리의 일부로 숨김 명령어를 호출할 수 있으므로, 앨리어스 호출로 들어온 인자는 절대 평가하지 않도록 각별히 주의해야 해요 — 악의적인 자식이 신뢰된 부모로 하여금 위험한 명령어를 자기 대신 실행하게 만들 수 있으니까요. 이를 돕기 위해 interp invokehidden의 인자엔 치환이나 평가를 적용하지 않아요. 안전한 인터프리터는 자신이나 후손의 숨김 명령어를 호출할 수 없어요. 숨김 명령어 집합은 신뢰된 인터프리터가 interp expose와 interp hide로 조작할 수 있어요. 현재 숨김 명령어 이름은 네임스페이스 한정자를 가질 수 없고, 숨기려면 먼저 네임스페이스의 명령어를 전역 네임스페이스로 이름 바꿔야 해요.
자원 한도 (RESOURCE LIMITS)
모든 인터프리터에는 어떤 부모가 자식에게 부과할 수 있는 두 종류의 자원 한도가 있어요. 명령어 한도(type command)는 인터프리터가 실행할 수 있는 총 Tcl 명령어 수를 제한하고(info cmdcount로 확인 가능), 시간 한도(type time)는 인터프리터 안의 실행이 완료되어야 할 시한을 둬요. 시간 한도는 생성 후 수정될 수 있으므로 절대 시간(clock seconds 같은)으로 표현돼요.
한도가 초과되면 먼저 부모들이 정의한 핸들러 콜백을 호출해요. 그 콜백들이 한도를 올리거나 제거하면 (이전에) 제한됐던 인터프리터의 실행이 계속돼요. 한도가 여전히 유효하면 그 지점에서 오류가 생성되고 인터프리터 내 일반 오류 처리(catch)가 비활성화돼 오류가 바깥으로 전파돼요.
한도 옵션:
-command— (모든 한도 공통) 비어 있지 않으면, 한도가 초과될 때 그 옵션을 읽고 쓰는 인터프리터의 전역 네임스페이스에서 실행될 Tcl 스크립트 지정. 콜백은 제한된 인터프리터가 계속 실행되길 원하면 한도를 수정할 수 있어요. 예외를 생성하면 백그라운드 예외 메커니즘으로 보고돼요. 한 인터프리터가 정의한 콜백은 다른 인터프리터의 콜백과 완전히 격리되고, 호출 순서는 정의되지 않아요.-granularity— (모든 한도 공통) 한도 검사가 가능한 일관 상태 지점 중 얼마나 자주 실제로 검사할지. 검사 빈도를 조정해 검사 오버헤드(시간 한도의 경우 상당할 수 있음)를 튜닝해요.-milliseconds—-seconds옵션이 정의한 시점 이후 몇 밀리초 후 시간 한도가 발동할지.-seconds와 함께만 지정해야 해요.-seconds— epoch 이후 몇 초 후(예:clock seconds) 시간 한도가 트리거될지. 밀리초 단위가 아니면 그 초의 시작에 발동. 빈 문자열이면 시간 한도가 설정되지 않았음을 뜻해요.-value— 명령어 한도를 트리거하기 전 실행할 수 있는 명령어 수. 빈 문자열이면 명령어 한도 미설정.
자원 한도가 설정된 인터프리터가 자식을 만들면, 그 자식은 생성 부모의 한도보다 최소한으로는 더 제한적인 한도를 받아요. 제한된 부모의 부모가 이 조건을 완화하려면 자식에서 interp 명령어를 숨기고 앨리어스·interp invokehidden으로 필요한 접근을 제공해야 해요.
백그라운드 예외 처리 (BACKGROUND EXCEPTION HANDLING)
예외가 스택을 직접 타고 올라가 보고될 수 없는 상황(예: update나 vwait 호출에서 이벤트를 처리할 때)에서는 백그라운드 예외 처리 메커니즘으로 보고돼요. 모든 인터프리터에는 백그라운드 예외 처리기가 등록돼 있고, 기본 처리기는 인터프리터 전역 네임스페이스의 bgerror 명령어를 호출하게 해요. 다른 처리기도 설치할 수 있어요.
백그라운드 예외 처리기는 비어 있지 않은 단어 리스트로, 호출 시 두 단어가 추가로 붙어요 — 첫 단어는 예외 시점의 인터프리터 결과(보통 오류 메시지), 둘째는 예외 시점의 반환 옵션 딕셔너리. 이 값들은 catch가 비백그라운드 상황에서 잡는 값과 같아요. 결과 리스트는 추가 치환 없이 인터프리터 전역 네임스페이스에서 실행돼요.
예제
현재 인터프리터의 명령어에 앨리어스 만들고 사용하기:
interp alias {} getIndex {} lsearch {alpha beta gamma delta}
set idx [getIndex delta]
안전한 인터프리터에서 lappend 호출마다 로그를 남기며 임의 명령어 실행:
set i [interp create -safe]
interp hide $i lappend
interp alias $i lappend {} loggedLappend $i
proc loggedLappend {i args} {
puts "logged invocation of lappend $args"
interp invokehidden $i lappend {*}$args
}
interp eval $i $someUntrustedScript
무한 루프가 끝나도록 인터프리터에 자원 한도 설정:
set i [interp create]
interp limit $i command -value 1000
interp eval $i {
set x 0
while {1} {
puts "Counting up... [incr x]"
}
}
더 알아보기
bgerror로 백그라운드 오류 처리load로 확장 패키지 로드- Safe-Tcl 메커니즘으로 안전한 인터프리터 관리