cmd — 줄 단위 명령 인터프리터 지원

cmd — 줄 단위 명령 인터프리터 지원

Cmd 클래스는 줄 단위로 명령을 처리하는 인터프리터를 손쉽게 만들 수 있는 프레임워크를 제공해요. 테스트 하네스나 관리 도구, 그리고 나중에 더 정교한 인터페이스로 감싸게 될 프로토타입을 만들 때 특히 유용합니다.

출처: Python 표준 라이브러리

본문

Cmd 클래스

Cmd 인스턴스(또는 그 서브클래스 인스턴스)는 줄 단위 인터프리터 프레임워크예요. Cmd 자체를 바로 인스턴스화할 이유는 거의 없고, 대신 여러분이 직접 정의하는 인터프리터 클래스의 상위 클래스로 써서 Cmd의 메서드를 상속받고 동작 메서드(action method)를 캡슐화하는 방식이 일반적입니다.

class cmd.Cmd(completekey='tab', stdin=None, stdout=None)
  • completekey는 완성 키의 readline 이름인데 기본값은 Tab이에요. completekeyNone이 아니고 readline을 사용할 수 있으면 명령 완성이 자동으로 동작합니다.
  • 기본값인 'tab'은 특별히 처리돼서 모든 readline.backend에서 Tab 키를 가리켜요. 구체적으로 readline.backendeditline이면 Cmd'tab' 대신 '^I'를 사용해요. 다른 값은 이렇게 처리되지 않으니 특정 백엔드에서만 동작할 수 있어요.
  • stdinstdout은 입출력에 사용할 파일 객체를 지정하며, 지정하지 않으면 sys.stdinsys.stdout이 기본값이 됩니다.
  • 지정한 stdin을 실제로 쓰려면 인스턴스의 use_rawinput 속성을 False로 설정해야 해요. 그렇지 않으면 stdin은 무시됩니다.

버전 3.13 변경: editline의 경우 completekey='tab''^I'로 대체되었어요.

Cmd 객체

Cmd 인스턴스는 다음과 같은 메서드를 가져요.

Cmd.cmdloop(intro=None)

프롬프트를 반복해서 띄우고, 입력을 받아 그 앞부분의 명령 접두사를 파싱한 뒤, 남은 줄을 인자로 넘기면서 동작 메서드로 보내요. intro는 첫 프롬프트 전에 내보낼 배너 문자열로, intro 클래스 속성을 덮어씁니다. readline 모듈이 로드돼 있으면 bash 스타일의 히스토리 편집이 자동 적용돼요(예: Control-P는 이전 명령, Control-N은 다음 명령, Control-F는 커서를 오른쪽으로, Control-B는 커서를 왼쪽으로 비파괴적으로 이동). 입력의 파일 끝(EOF)은 'EOF' 문자열로 돌아옵니다.

인터프리터 인스턴스는 do_foo() 메서드를 가질 경우에만 foo라는 명령 이름을 인식해요. 특수한 경우로, '?'로 시작하는 줄은 do_help()로 보내지고, '!'로 시작하는 줄은 do_shell()(정의돼 있을 때)로 보내집니다. postcmd()가 참 값을 반환하면 이 메서드는 반환해요. postcmd()stop 인자는 명령의 해당 do_*() 메서드 반환값입니다.

완성이 활성화돼 있으면 명령 완성은 자동으로 되고, 명령 인자 완성은 complete_foo(text, line, begidx, endidx)를 호출해서 처리돼요. text는 매칭하려는 문자열 접두사로, 반환되는 모든 매칭은 이걸로 시작해야 해요. line은 앞쪽 공백이 제거된 현재 입력 줄이고, begidx/endidx는 접두사 텍스트의 시작·끝 인덱스라서 인자가 놓인 위치에 따라 다른 완성을 제공할 때 쓸 수 있어요.

Cmd.do_help(arg)

Cmd의 모든 서브클래스는 미리 정의된 do_help()를 상속받아요. 'bar' 인자를 주고 호출하면 해당하는 help_bar() 메서드를 부르고, 없으면 do_bar()의 docstring(있을 때)을 출력해요. 인자 없이 호출하면 모든 도움말 주제(즉 help_*() 메서드가 있거나 docstring이 있는 모든 명령)를 나열하고, 문서화되지 않은 명령도 함께 나열해요.

Cmd.onecmd(str)

인자를 프롬프트에 입력받은 것처럼 해석해요. 보통은 오버라이드할 필요가 거의 없고, 실행 훅이 필요하면 precmd()/postcmd()를 보세요. 반환값은 인터프리터가 명령 해석을 멈춰야 하는지를 나타내는 플래그예요. str에 대한 do_*() 메서드가 있으면 그 반환값을, 아니면 default() 메서드의 반환값을 돌려줍니다.

Cmd.emptyline()

프롬프트에 빈 줄이 입력됐을 때 호출돼요. 오버라이드하지 않으면 마지막으로 입력된 비어 있지 않은 명령을 반복합니다.

Cmd.default(line)

명령 접두사를 인식하지 못한 입력 줄에서 호출돼요. 오버라이드하지 않으면 오류 메시지를 출력하고 반환합니다.

Cmd.completedefault(text, line, begidx, endidx)

명령 특정 complete_*() 메서드가 없을 때 입력 줄을 완성하기 위해 호출돼요. 기본적으로 빈 리스트를 반환합니다.

Cmd.columnize(list, displaywidth=80)

문자열 리스트를 컴팩트한 컬럼 형태로 보여주기 위해 호출돼요. 각 컬럼은 필요한 만큼만 너비를 차지하고, 읽기 좋게 컬럼 사이에 두 칸을 둡니다.

Cmd.precmd(line)

명령 줄 line이 해석되기 직전(단 프롬프트 생성·출력 이후)에 실행되는 훅 메서드예요. Cmd에서는 스텁으로, 서브클래스에서 오버라이드하기 위해 존재해요. 반환값이 onecmd()가 실행할 명령으로 쓰이며, 명령을 다시 쓰거나 line을 그대로 돌려줄 수 있어요.

Cmd.postcmd(stop, line)

명령 디스패치가 끝난 직후 실행되는 훅 메서드예요. line은 실행된 명령 줄이고, stoppostcmd() 호출 후 실행을 종료할지 나타내는 플래그(=onecmd()의 반환값)예요. 이 메서드의 반환값이 stop에 대응하는 내부 플래그의 새 값이 되며, 거짓을 반환하면 해석이 계속됩니다.

Cmd.preloop()

cmdloop()가 호출될 때 한 번 실행되는 훅 메서드예요. 스텁이며 서브클래스에서 오버라이드합니다.

Cmd.postloop()

cmdloop()가 반환하려 할 때 한 번 실행되는 훅 메서드예요. 스텁이며 서브클래스에서 오버라이드합니다.

Cmd 서브클래스 인스턴스는 이런 공개 인스턴스 변수들을 가져요.

  • prompt — 입력을 요청할 때 내보내는 프롬프트
  • identchars — 명령 접두사로 받아들이는 문자들의 문자열
  • lastcmd — 마지막으로 본 비어 있지 않은 명령 접두사
  • cmdqueue — 대기 중인 입력 줄 리스트. cmdloop()는 새 입력이 필요할 때 이 리스트를 확인해요. 비어 있지 않으면 그 요소를 프롬프트에 입력된 것처럼 순서대로 처리합니다.
  • intro — 소개/배너로 내보낼 문자열. cmdloop()에 인자를 주면 덮어씀.
  • doc_header — 도움말 출력에 문서화된 명령 섹션이 있을 때 내보내는 헤더
  • misc_header — 기타 도움말 주제 섹션(대응하는 do_*() 없는 help_*() 메서드들)이 있을 때 내보내는 헤더
  • undoc_header — 문서화되지 않은 명령 섹션(대응하는 help_*() 없는 do_*() 메서드들)이 있을 때 내보내는 헤더
  • ruler — 도움말 메시지 헤더 아래에 구분선을 그릴 때 쓰는 문자. 비어 있으면 선을 그리지 않고, 기본값은 '='이에요.
  • use_rawinput — 기본값 True인 플래그. 참이면 cmdloop()input()으로 프롬프트를 표시하고 다음 명령을 읽고, 거짓이면 sys.stdout.write()sys.stdin.readline()을 사용해요. 즉 readline을 import하면 지원되는 시스템에서 Emacs 스타일 줄 편집과 명령 히스토리 키 입력을 자동 지원하게 됩니다.

Cmd 예제

cmd 모듈은 주로 사용자가 프로그램과 인터랙티브하게 작업할 수 있는 커스텀 셸을 만드는 데 유용해요. 여기서는 turtle 모듈의 몇 가지 명령을 둘러싼 셸을 만드는 간단한 예제를 보여 드릴게요.

forward() 같은 기본 turtle 명령은 do_forward()라는 메서드 이름으로 Cmd 서브클래스에 추가돼요. 인자는 숫자로 변환된 뒤 turtle 모듈로 보내지고, docstring은 셸이 제공하는 help 유틸리티에 사용됩니다.

예제에는 precmd() 메서드로 구현된 간단한 기록·재생 기능도 있어요. precmd()는 입력을 소문자로 바꾸고 명령을 파일에 씁니다. do_playback() 메서드는 파일을 읽고 기록된 명령을 즉시 재생할 수 있도록 cmdqueue에 추가해요.

import cmd, sys
from turtle import *

class TurtleShell(cmd.Cmd):
    intro = 'Welcome to the turtle shell.   Type help or ? to list commands.\n'
    prompt = '(turtle) '
    file = None

    # ----- basic turtle commands -----
    def do_forward(self, arg):
        'Move the turtle forward by the specified distance:  FORWARD 10'
        forward(*parse(arg))
    def do_right(self, arg):
        'Turn turtle right by given number of degrees:  RIGHT 20'
        right(*parse(arg))
    def do_left(self, arg):
        'Turn turtle left by given number of degrees:  LEFT 90'
        left(*parse(arg))
    def do_goto(self, arg):
        'Move turtle to an absolute position with changing orientation.  GOTO 100 200'
        goto(*parse(arg))
    def do_home(self, arg):
        'Return turtle to the home position:  HOME'
        home()
    def do_circle(self, arg):
        'Draw circle with given radius an options extent and steps:  CIRCLE 50'
        circle(*parse(arg))
    def do_position(self, arg):
        'Print the current turtle position:  POSITION'
        print('Current position is %d %d\n' % position())
    def do_heading(self, arg):
        'Print the current turtle heading in degrees:  HEADING'
        print('Current heading is %d\n' % (heading(),))
    def do_color(self, arg):
        'Set the color:  COLOR BLUE'
        color(arg.lower())
    def do_undo(self, arg):
        'Undo (repeatedly) the last turtle action(s):  UNDO'
    def do_reset(self, arg):
        'Clear the screen and return turtle to center:  RESET'
        reset()
    def do_bye(self, arg):
        'Stop recording, close the turtle window, and exit:  BYE'
        print('Thank you for using Turtle')
        self.close()
        bye()
        return True

    # ----- record and playback -----
    def do_record(self, arg):
        'Save future commands to filename:  RECORD rose.cmd'
        self.file = open(arg, 'w')
    def do_playback(self, arg):
        'Playback commands from a file:  PLAYBACK rose.cmd'
        self.close()
        with open(arg) as f:
            self.cmdqueue.extend(f.read().splitlines())
    def precmd(self, line):
        line = line.lower()
        if self.file and 'playback' not in line:
            print(line, file=self.file)
        return line
    def close(self):
        if self.file:
            self.file.close()
            self.file = None

def parse(arg):
    'Convert a series of zero or more numbers to an argument tuple'
    return tuple(map(int, arg.split()))

if __name__ == '__main__':
    TurtleShell().cmdloop()

다음은 turtle 셸을 사용한 샘플 세션으로, help 기능, 빈 줄로 명령 반복하기, 간단한 기록·재생 기능을 보여 줍니다.

Welcome to the turtle shell.   Type help or ? to list commands.

(turtle) ?
Documented commands (type help <topic>):
========================================
bye     color    goto     home  playback  record  right
circle  forward  heading  left  position  reset   undo

(turtle) help forward
Move the turtle forward by the specified distance:  FORWARD 10
(turtle) record spiral.cmd
(turtle) position
Current position is 0 0

(turtle) heading
Current heading is 0

(turtle) reset
(turtle) circle 20
(turtle) right 30
(turtle) circle 40
(turtle) right 30
(turtle) circle 60
(turtle) right 30
(turtle) circle 80
(turtle) right 30
(turtle) circle 100
(turtle) right 30
(turtle) circle 120
(turtle) right 30
(turtle) circle 120
(turtle) heading
Current heading is 180

(turtle) forward 100
(turtle)
(turtle) right 90
(turtle) forward 100
(turtle)
(turtle) right 90
(turtle) forward 400
(turtle) right 90
(turtle) forward 500
(turtle) right 90
(turtle) forward 400
(turtle) right 90
(turtle) forward 300
(turtle) playback spiral.cmd
Current position is 0 0

Current heading is 0

Current heading is 180

(turtle) bye
Thank you for using Turtle

더 알아보기