Pythonで学ぶ「詳解 UNIXプログラミング」(その11) 第11章 端末入出力
11.1 割り込み文字を無効にし、ファイルの終わりの文字を変更する
import os, sys, termios if not os.isatty(0): sys.exit("standard input is not a terminal device") vdisable = os.fpathconf(sys.stdin.fileno(), "PC_VDISABLE") if vdisable < 0: sys.exit("_POSIX_VDISABLE not in effect") term = termios.tcgetattr(sys.stdin.fileno()) # fetch tty state cc = term[6] cc[termios.VINTR] = vdisable # disable INTR character cc[termios.VEOF] = 2 # EOF is Control-B termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, term)
11.2 tcgetattrの例
import os, sys, termios term = termios.tcgetattr(sys.stdin.fileno()) size = term[2] & termios.CSIZE if size == termios.CS5: print "5 bits/byte" elif size == termios.CS6: print "6 bits/byte" elif size == termios.CS7: print "7 bits/byte" elif size == termios.CS8: print "8 bits/byte" else: print "unknown bits/byte" term[2] &= ~termios.CSIZE # zero out the bits term[2] |= termios.CS8 # set 8 bits/byte termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, term)
11.3 POSIX.1のctermidの実装
_ctermid_name = '' def ctermid(): return "/dev/tty"
11.4 POSIX.1のisattyの実装
import termios, sys def isatty(fd): try: termios.tcgetattr(fd) except termios.error: return False else: return True # return True if no error
11.5 isatty関数のテスト
print "fd 0:", "tty" if isatty(0) else "not a tty" print "fd 1:", "tty" if isatty(1) else "not a tty" print "fd 2:", "tty" if isatty(2) else "not a tty"
11.6 POSIX.1のttyname関数の実装
import termios, sys, os, stat DEV = "/dev" # device directory def ttyname(fd): if not os.isatty(fd): return fdstat = os.fstat(fd) if not stat.S_ISCHR(fdstat.st_mode): return for fname in os.listdir(DEV): pathname = os.path.join(DEV, fname) devstat = os.stat(pathname) if fdstat.st_ino == devstat.st_ino and fdstat.st_dev == devstat.st_dev: # found a match return pathname
11.7 ttyname関数のテスト
print "fd 0:", ttyname(0) if isatty(0) else "not a tty" print "fd 1:", ttyname(1) if isatty(1) else "not a tty" print "fd 2:", ttyname(2) if isatty(2) else "not a tty"
このttyname
の実装はLinuxでは当てはまらないらしく、サンプルを実行すると
fd 0: /dev/stderr fd 1: /dev/stderr fd 2: /dev/stderr
となってしまう。本来は
>>> os.ttyname(0) '/dev/pts/1'
が正しい。
11.8 getpass関数の実装
import sys, os, signal, termios, copy from ctypes import * from posixsignal import * def getpass(prompt): fp = open(os.ctermid(), "r+", 0) sig = sigset_t() sigsave = sigset_t() # block SIGINT & SIGTSTP, save signal mask sigemptyset(byref(sig)) sigaddset(byref(sig), signal.SIGINT) sigaddset(byref(sig), signal.SIGSTOP) sigprocmask(SIG_BLOCK, byref(sig), byref(sigsave)) term = termios.tcgetattr(fp.fileno()) # save tty state termsave = copy.deepcopy(term) # structure copy term[3] &= ~(termios.ECHO | termios.ECHOE | termios.ECHOK | termios.ECHONL) termios.tcsetattr(fp.fileno(), termios.TCSAFLUSH, term) sys.stdout.write(prompt) passwd = sys.stdin.readline().rstrip("\n") # restore tty state termios.tcsetattr(fp.fileno(), termios.TCSAFLUSH, termsave) # restore signal mask sigprocmask(SIG_BLOCK, byref(sigsave), None) # done with /dev/tty fp.close() return passwd
このサンプルでは、10章で作成したposixsignal
を使用している。
11.9 getpass関数を呼ぶ
passwd = getpasswd("Enter password:") passwdlen = len(passwd) # now we use password del passwd trash = "\0"+passwdlen
パスワードは用が済んだら'\0'
で上書きするべき、と書かれているが、Pythonの文字列は変更不能なので、上書きはできない。ここでは、気休めに明示的なdel
でパスワードを削除し、その直後にパスワードと同じ長さの文字列オブジェクトを作成してみた。こうすることで、パスワードで使用したメモリ領域と同じ場所に'\0'
が書き込まれることが期待できる…かもしれない。上手く行く保証はないので、あくまで気休めだ。
11.10 端末モードをローまたはcbreakに設定
import copy, termios save_termios = None ttysavefd = -1 RESET, RAW, CBREAK = range(0, 3) ttystate = RESET def tty_cbreak(fd): # put terminal into a cbreak mode global save_termios, ttystate, ttysavefd save_termios = termios.tcgetattr(fd) buf = copy.deepcopy(save_termios) # structure copy buf[3] &= ~(termios.ECHO|termios.ICANON) # echo off, canonical mode off buf[6][termios.VMIN] = 1 Case B: 1 byte at a time, no timer buf[6][termios.VTIME] = 0 termios.tcsetattr(fd, termios.TCSAFLUSH, buf) ttystate = CBREAK ttysavefd = fd def tty_raw(fd): # put terminal into a raw mode global save_termios, ttystate, ttysavefd save_termios = termios.tcgetattr(fd) buf = copy.deepcopy(save_termios) # echo off, canonical mode off, extended input processing off, signal chars off buf[3] &= ~(termios.ECHO|termios.ICANON|termios.IEXTEN|termios.ISIG) # no SIGINTon BREAK, CR-to NL off, input parity check off, don't strip 8th bit on input, # output flow control off buf[0] &= ~(termios.BRKINT|termios.ICRNL|termios.INPCK|termios.ISTRIP|termios.IXON) # clear size bits, parity checking off buf[3] &= ~(termios.CSIZE|termios.PARENB) # set 8 bits/char buf[3] |= termios.CS8 # output processing off buf[1] &= ~(termios.OPOST) # Case B: 1 byte at a time, no timer buf[6][termios.VMIN] = 1 buf[6][termios.VTIME] = 0 termios.tcsetattr(fd, termios.TCSAFLUSH, buf) ttystate = RAW ttysavefd = fd def tty_reset(fd): # restore terminal's mode global ttystate if ttystate in (CBREAK, RAW): termios.tcsetattr(fd, termios.TCSAFLUSH, save_termios) ttystate = RESET def tty_exit(): # can be set up by atexit(tty_exit) if ttysavefd >= 0: tty_reset(ttysavefd) def tty_termios(): # let caller see original tty state return save_termios
11.11 ローモードとcbreakモードのテスト
import signal, sys, os def sig_catch(signo, frame): print "signal caught" tty_reset(sys.stdin.fileno()) sys.exit(0) # catch signals signal.signal(signal.SIGINT, sig_catch) signal.signal(signal.SIGQUIT, sig_catch) signal.signal(signal.SIGTERM, sig_catch) fileno = sys.stdin.fileno() tty_raw(fileno) print "Enter raw mode characters, terminate with DELETE" while True: c = os.read(fileno, 1) if ord(c) == 0177: break print hex(ord(c)) tty_reset(fileno) tty_cbreak(fileno) print "Enter cbreak mode characters, terminate with SIGINT" while True: c = os.read(fileno, 1) print hex(ord(c)) tty_reset(fileno)
11.12 ウィンドウサイズを表示する
import sys, os, signal, fcntl, termios, tty, struct def pr_winsize(fd): ret = fcntl.ioctl(fd, tty.TIOCGWINSZ, struct.pack('hhhh', 0, 0, 0, 0)) height, width = struct.unpack('hhhh', ret)[:2] print "{0} rows, {1} columns".format(height, width) def sig_winch(signo, frame): print "SIGWINCH received" pr_winsize(fileno) fileno = sys.stdin.fileno() if not os.isatty(fileno): sys.exit(1) pr_winsize(fileno) # print initial size signal.signal(signal.SIGWINCH, sig_winch) # sleep forever while True: signal.pause()