`shlex` — 간단한 어휘 분석
shlex — 간단한 어휘 분석
shlex 클래스는 Unix 셸과 비슷한 간단한 문법을 위한 어휘 분석기(lexical analyzer)를 쉽게 작성하게 해줘요. 이는 미니 언어를 작성하거나(예: Python 애플리케이션의 런 컨트롤 파일에서), 따옴표 붙은 문자열을 파싱할 때 자주 유용해요.
shlex 모듈은 다음 함수를 정의해요.
출처: Python 표준 라이브러리
본문
shlex.split(*s*, *comments=False*, *posix=True*)
셸과 비슷한 문법으로 문자열 s를 나눠요. comments가 False(기본값)면 주어진 문자열의 주석 파싱이 비활성화돼요(shlex 인스턴스의 commenters 속성을 빈 문자열로 설정). 이 함수는 기본적으로 POSIX 모드로 동작하지만, posix 인자가 거짓이면 비POSIX 모드를 사용해요. 버전 3.12 변경: s 인자에 None을 넘기면 sys.stdin을 읽는 대신 이제 예외를 발생시킴.
shlex.join(*split_command*)
목록 split_command의 토큰들을 이어붙여 문자열을 반환해요. 이 함수는 split()의 역함수예요.
>>> from shlex import join
>>> print(join(['echo', '-n', 'Multiple words']))
echo -n 'Multiple words'
반환 값은 주입 공격 취약점으로부터 보호하기 위해 셸 이스케이프돼요(quote() 참고). 버전 3.8에서 추가.
shlex.quote(*s*)
문자열 s의 셸 이스케이프 버전을 반환해요. 반환 값은 목록을 쓸 수 없는 경우 셸 명령줄에서 하나의 토큰으로 안전하게 쓸 수 있는 문자열이에요.
경고
shlex모듈은 Unix 셸용으로만 설계됐어요.quote()함수는 POSIX를 준수하지 않는 셸이나 Windows 같은 다른 운영체제의 셸에서는 올바르다고 보장되지 않아요. 그런 셸에서 이 모듈로 인용된 명령을 실행하면 명령 주입 취약점이 열릴 가능성이 있어요.shell=False로subprocess.run()처럼 목록으로 명령 인자를 넘기는 함수를 쓰는 걸 고려하세요.
이 관용구는 안전하지 않아요:
>>> filename = 'somefile; rm -rf ~'
>>> command = 'ls -l {}'.format(filename)
>>> print(command) # executed by a shell: boom!
ls -l somefile; rm -rf ~
quote()는 보안 구멍을 막아줘요:
>>> from shlex import quote
>>> command = 'ls -l {}'.format(quote(filename))
>>> print(command)
ls -l 'somefile; rm -rf ~'
>>> remote_command = 'ssh home {}'.format(quote(command))
>>> print(remote_command)
ssh home 'ls -l '\"'\"'somefile; rm -rf ~'\"'\"''
인용은 UNIX 셸과 split()과 호환돼요:
>>> from shlex import split
>>> remote_command = split(remote_command)
>>> remote_command
['ssh', 'home', "ls -l 'somefile; rm -rf ~'"]
>>> command = split(remote_command[-1])
>>> command
['ls', '-l', 'somefile; rm -rf ~']
버전 3.3에서 추가.
shlex 모듈은 다음 클래스를 정의해요.
class shlex.shlex(*instream=None*, *infile=None*, *posix=False*, *punctuation_chars=False*)
shlex 인스턴스 또는 하위 클래스 인스턴스는 어휘 분석기 객체예요. 초기화 인자가 있으면 문자를 읽어올 위치를 지정해요. read()와 readline() 메서드를 가진 파일/스트림 같은 객체이거나 문자열이어야 해요. 인자가 없으면 sys.stdin에서 입력을 받아요. 두 번째 선택 인자는 파일 이름 문자열로, infile 속성의 초기 값을 설정해요. instream 인자가 생략되거나 sys.stdin과 같으면 이 두 번째 인자는 "stdin"이 기본값이에요. posix 인자는 동작 모드를 정의해요. posix가 참이 아닐 때(기본값) shlex 인스턴스는 호환 모드로 동작해요. POSIX 모드로 동작할 때 shlex는 POSIX 셸 파싱 규칙에 최대한 가까이 가려고 해요. punctuation_chars 인자는 실제 셸이 파싱하는 방식에 훨씬 더 가까워지는 방법을 제공해요. 여러 값을 받을 수 있어요. 기본값 False는 Python 3.5 이하에서 본 동작을 보존해요. True로 설정하면 ();<>|& 문자들의 파싱이 바뀌어요. 이 문자들(구두점 문자로 간주)의 연속은 단일 토큰으로 반환돼요. 비어 있지 않은 문자 문자열로 설정하면 그 문자들이 구두점 문자로 사용돼요. wordchars 속성에 있는 문자 중 punctuation_chars에 나타나는 것은 wordchars에서 제거돼요. punctuation_chars는 shlex 인스턴스 생성 시에만 설정할 수 있고 나중에 수정할 수 없어요. 버전 3.6 변경: punctuation_chars 매개변수 추가.
shlex 객체
shlex 인스턴스에는 다음 메서드가 있어요.
shlex.get_token()
토큰을 반환해요. push_token()으로 토큰이 스택에 쌓여 있으면 스택에서 토큰을 꺼내요. 아니면 입력 스트림에서 하나를 읽어요. 읽기가 즉시 파일 끝을 만나면 eof(비POSIX 모드에서 빈 문자열 '', POSIX 모드에서 None)를 반환해요.
shlex.push_token(*str*)
인자를 토큰 스택에 밀어 넣어요.
shlex.read_token()
원시 토큰을 읽어요. 푸시백 스택을 무시하고 소스 요청을 해석하지 않아요. (보통 유용한 진입점은 아니며, 완전성을 위해 여기 기록한 거예요.)
shlex.sourcehook(*filename*)
shlex가 소스 요청(아래 source 참고)을 감지하면 이 메서드에 다음 토큰을 인자로 주고, 파일 이름과 열린 파일과 같은 객체의 튜플을 반환할 것으로 기대해요. 보통 이 메서드는 먼저 인자의 따옴표를 벗겨내요. 결과가 절대 경로명이거나, 적용 중인 이전 소스 요청이 없거나, 이전 소스가 스트림(sys.stdin 같은)이면 결과를 그대로 둬요. 아니면 결과가 상대 경로명이면 소스 포함 스택에서 바로 앞에 있는 파일 이름의 디렉터리 부분을 앞에 붙여요(C 전처리기가 #include "file.h"를 다루는 방식과 비슷). 이 조작의 결과는 파일 이름으로 취급되어 튜플의 첫 구성 요소로 반환되고, open()을 호출해 두 번째 구성 요소를 만드는데(인스턴스 초기화의 인자 순서와 반대라는 점에 주의!). 이 훅은 디렉터리 검색 경로, 파일 확장자 추가, 기타 네임스페이스 해킹을 구현하는 데 쓸 수 있도록 노출돼 있어요. 대응하는 'close' 훅은 없지만, shlex 인스턴스는 EOF를 반환할 때 소스된 입력 스트림의 close() 메서드를 호출해요. 소스 스태킹을 더 명시적으로 제어하려면 push_source()와 pop_source() 메서드를 쓰세요.
shlex.push_source(*newstream*, *newfile=None*)
입력 스택에 입력 소스 스트림을 밀어 넣어요. 파일 이름 인자가 지정되면 나중에 오류 메시지에서 사용할 수 있어요. 이는 sourcehook() 메서드가 내부적으로 쓰는 메서드와 같아요.
shlex.pop_source()
입력 스택에서 마지막으로 밀어 넣은 입력 소스를 꺼내요. 렉서가 스택된 입력 스트림에서 EOF에 도달하면 내부적으로 쓰는 메서드와 같아요.
shlex.error_leader(*infile=None*, *lineno=None*)
이 메서드는 Unix C 컴파일러 오류 라벨 형식의 오류 메시지 머리말을 생성해요. 형식은 '"%s", line %d: '인데, %s는 현재 소스 파일 이름으로, %d는 현재 입력 줄 번호로 바뀌어요(선택 인자로 재정의할 수 있어요). 이 편의는 Emacs와 다른 Unix 도구가 이해하는 표준이고 파싱 가능한 형식으로 오류 메시지를 생성하도록 shlex 사용자를 장려하기 위한 거예요.
shlex 하위 클래스의 인스턴스에는 어휘 분석을 제어하거나 디버깅에 쓸 수 있는 여러 공개 인스턴스 변수가 있어요.
shlex.commenters
주석 시작으로 인식되는 문자 문자열이에요. 주석 시작부터 줄 끝까지의 모든 문자는 무시돼요. 기본적으로 '#'만 포함해요.
shlex.wordchars
다중 문자 토큰으로 축적될 문자 문자열이에요. 기본적으로 모든 ASCII 영숫자와 밑줄을 포함해요. POSIX 모드에서는 Latin-1 집합의 악센트 문자도 포함돼요. punctuation_chars가 비어 있지 않으면 파일 이름 사양과 명령줄 매개변수에 나타날 수 있는 ~-./*?= 문자도 이 속성에 포함되고, punctuation_chars에 나타나는 문자는 있다면 wordchars에서 제거돼요. whitespace_split이 True로 설정되면 효과가 없어요.
shlex.whitespace
공백으로 간주되어 건너뛰는 문자들이에요. 공백은 토큰을 경계 짓습니다. 기본적으로 공백, 탭, 줄바꿈, 캐리지 리턴을 포함해요.
shlex.escape
이스케이프로 간주되는 문자들이에요. POSIX 모드에서만 사용되며 기본적으로 '\\'만 포함해요.
shlex.quotes
문자열 따옴표로 간주되는 문자들이에요. 토큰은 같은 따옴표가 다시 나타날 때까지 축적돼요(따라서 셸에서처럼 서로 다른 따옴표 타입이 서로를 보호해요). 기본적으로 ASCII 단일·이중 따옴표를 포함해요.
shlex.escapedquotes
escape에 정의된 이스케이프 문자를 해석할 quotes의 문자들이에요. POSIX 모드에서만 사용되며 기본적으로 '"'만 포함해요.
shlex.whitespace_split
True면 토큰이 공백에서만 나뉘어요. 예를 들어 shlex로 명령줄을 파싱해 셸 인자와 비슷한 토큰을 얻을 때 유용해요. punctuation_chars와 함께 쓰면 토큰이 그 문자들에 더해 공백에서도 나뉘어요. 버전 3.8 변경: punctuation_chars 속성이 whitespace_split 속성과 호환되게 됨.
shlex.infile
클래스 인스턴스화 시점에 처음 설정되거나 이후 소스 요청으로 스택된 현재 입력 파일의 이름이에요. 오류 메시지를 만들 때 살펴보면 유용할 수 있어요.
shlex.instream
이 shlex 인스턴스가 문자를 읽어오는 입력 스트림이에요.
shlex.source
이 속성은 기본적으로 None이에요. 문자열을 할당하면 그 문자열은 여러 셸의 source 키워드와 비슷한 어휘 수준의 포함 요청으로 인식돼요. 즉 바로 뒤의 토큰이 파일 이름으로 열리고, 그 스트림에서 EOF까지 입력을 가져오며, 이때 그 스트림의 close() 메서드가 호출되고 입력 소스는 다시 원래 입력 스트림이 돼요. 소스 요청은 몇 단계든 쌓을 수 있어요.
shlex.debug
이 속성이 숫자이고 1 이상이면 shlex 인스턴스는 동작에 대한 자세한 진행 출력을 인쇄해요. 써야 한다면 모듈 소스 코드를 읽어 세부 사항을 배울 수 있어요.
shlex.lineno
소스 줄 번호(지금까지 본 줄바꿈 수 + 1)예요.
shlex.token
토큰 버퍼예요. 예외를 잡을 때 살펴보면 유용할 수 있어요.
shlex.eof
파일 끝을 결정하는 데 쓰는 토큰이에요. 비POSIX 모드에서는 빈 문자열('')로, POSIX 모드에서는 None으로 설정돼요.
shlex.punctuation_chars
읽기 전용 속성이에요. 구두점으로 간주되는 문자들이에요. 구두점 문자의 연속은 단일 토큰으로 반환돼요. 하지만 의미적 유효성 검사는 수행되지 않아요. 예를 들어 '>>>'가 토큰으로 반환될 수 있는데, 셸이 그렇게 인식하지 못할 수도 있어요. 버전 3.6에서 추가.
파싱 규칙 (Parsing Rules)
비POSIX 모드로 동작할 때 shlex는 다음 규칙을 따르려 해요.
- 단어 안에서는 따옴표 문자가 인식되지 않아요(
Do"Not"Separate는 단일 단어Do"Not"Separate로 파싱). - 이스케이프 문자는 인식되지 않아요.
- 따옴표로 문자를 감싸면 따옴표 안의 모든 문자의 리터럴 값을 보존해요.
- 닫는 따옴표는 단어를 나눠요(
"Do"Separate는"Do"와Separate로 파싱). whitespace_split이False면 단어 문자, 공백, 따옴표로 선언되지 않은 어떤 문자든 단일 문자 토큰으로 반환돼요.True면shlex는 단어를 공백에서만 나눠요.- EOF는 빈 문자열(
'')로 신호를 보내요. - 따옴표가 있어도 빈 문자열을 파싱하는 건 불가능해요.
POSIX 모드로 동작할 때 shlex는 다음 파싱 규칙을 따르려 해요.
- 따옴표는 벗겨지고 단어를 나누지 않아요(
"Do"Not"Separate"는 단일 단어DoNotSeparate로 파싱). - 따옴표가 아닌 이스케이프 문자(예:
'\')는 다음에 오는 문자의 리터럴 값을 보존해요. escapedquotes의 일부가 아닌 따옴표(예:"'")로 문자를 감싸면 따옴표 안의 모든 문자의 리터럴 값을 보존해요.escapedquotes의 일부인 따옴표(예:'"')로 문자를 감싸면escape에 언급된 문자를 제외하고 따옴표 안의 모든 문자의 리터럴 값을 보존해요. 이스케이프 문자는 사용 중인 따옴표나 이스케이프 문자 자체가 뒤따를 때만 특별한 의미를 유지해요. 아니면 이스케이프 문자는 일반 문자로 간주돼요.- EOF는
None값으로 신호를 보내요. - 따옴표 붙은 빈 문자열(
'')이 허용돼요.
셸과의 호환성 개선 (Improved Compatibility with Shells)
버전 3.6에서 추가.
shlex 클래스는 bash, dash, sh 같은 일반적인 Unix 셸이 수행하는 파싱과 호환성을 제공해요. 이 호환성을 이용하려면 생성자에서 punctuation_chars 인자를 지정하세요. 기본값은 False로 3.6 이전 동작을 보존해요. True로 설정하면 ();<>|& 문자들의 파싱이 바뀌어요. 이 문자들의 연속은 단일 토큰으로 반환돼요. 이는 셸을 위한 완전한 파서와는 거리가 멀지만(셸이 다양해서 표준 라이브러리 범위 밖), 명령줄 처리를 그렇지 않을 때보다 더 쉽게 수행하게 해줘요. 다음 스니펫에서 차이를 볼 수 있어요:
>>> import shlex
>>> text = "a && b; c && d || e; f >'abc'; (def \"ghi\")"
>>> s = shlex.shlex(text, posix=True)
>>> s.whitespace_split = True
>>> list(s)
['a', '&&', 'b;', 'c', '&&', 'd', '||', 'e;', 'f', '>abc;', '(def', 'ghi)']
>>> s = shlex.shlex(text, posix=True, punctuation_chars=True)
>>> s.whitespace_split = True
>>> list(s)
['a', '&&', 'b', ';', 'c', '&&', 'd', '||', 'e', ';', 'f', '>', 'abc', ';',
'(', 'def', 'ghi', ')']
물론 셸에 유효하지 않은 토큰도 반환될 것이므로, 반환된 토큰에 대한 자체 오류 검사를 구현해야 해요.
punctuation_chars 매개변수의 값으로 True를 넘기는 대신 특정 문자 문자열을 넘겨 어떤 문자가 구두점을 구성하는지 결정할 수 있어요. 예를 들어:
>>> import shlex
>>> s = shlex.shlex("a && b || c", punctuation_chars="|")
>>> list(s)
['a', '&', '&', 'b', '||', 'c']
참고
punctuation_chars가 지정되면wordchars속성에~-./*?=문자가 추가돼요. 이 문자들은 파일 이름(와일드카드 포함)과 명령줄 인자(예:--color=auto)에 나타날 수 있기 때문이에요. 따라서:
>>> import shlex
>>> s = shlex.shlex('~/a && b-c --color=auto || d *.py?',
... punctuation_chars=True)
>>> list(s)
['~/a', '&&', 'b-c', '--color=auto', '||', 'd', '*.py?']
하지만 셸에 최대한 가까이 맞추려면 punctuation_chars를 쓸 때 항상 posix와 whitespace_split을 함께 쓰는 걸 권장해요. 그러면 wordchars를 완전히 무효화할 거예요. 최상의 효과를 위해 punctuation_chars는 posix=True와 함께 설정해야 해요(shlex의 기본은 posix=False라는 점에 주의).
더 알아보기
configparser모듈: Windows.ini파일과 비슷한 설정 파일 파서.