Perlで学ぶ「詳解 UNIXプログラミング」(その11) 第11章 端末入出力
はじめに
「詳解 UNIXプログラミング」の第十一章を示します.
11.1 割り込み文字を無効にし、ファイルの終わりの文字を変更する
PerlではC言語っぽいインタフェースでなく、OOishなインタフェースと
なります。
#!/usr/bin/env perl use strict; use warnings; # 割り込み文字を無効にし、ファイルの終わりの文字を変更する use POSIX (); unless (POSIX::isatty(0)) { die "standard input is not a terminal device\n"; } my $vdisable = POSIX::fpathconf(POSIX::STDIN_FILENO, POSIX::_PC_VDISABLE); unless (defined $vdisable) { die "_POSIX_VDISABLE not in effect\n"; } my $termios = POSIX::Termios->new; unless ($termios->getattr(POSIX::STDIN_FILENO)) { die "Error: POSIX::getattr :$!\n"; } $termios->setcc(POSIX::VINTR, $vdisable); # disable INTR character $termios->setcc(POSIX::VEOF, 2); # EOF is Control-B $termios->setattr(POSIX::STDIN_FILENO, POSIX::TCSAFLUSH);
11.2 tcgetattrの例
#!/usr/bin/env perl use strict; use warnings; use POSIX (); # tcgetattrの例 my $termios = POSIX::Termios->new; unless ($termios->getattr(POSIX::STDIN_FILENO)) { die "Error: POSIX::getattr :$!\n"; } my $size = $termios->getcflag & POSIX::CSIZE; if ($size == POSIX::CS5) { print "5 bits/byte\n"; } elsif ($size == POSIX::CS6) { print "6 bits/byte\n"; } elsif ($size == POSIX::CS7) { print "7 bits/byte\n"; } elsif ($size == POSIX::CS8) { print "8 bits/byte\n"; } else { print "unknown bits/byte\n"; } my $csize = $termios->getcflag; $csize &= ~(POSIX::CSIZE); $csize |= POSIX::CS8; $termios->setcflag($csize); $termios->setattr(POSIX::STDIN_FILENO, POSIX::TCSANOW);
11.3 POSIX.1のctermidの実装
#!/usr/bin/env perl use strict; use warnings; # POSIX.1のctermidの実装 sub ctermid { return "/dev/tty"; }
11.4 POSIX.1のisattyの実装
#!/usr/bin/env perl use strict; use warnings; use POSIX (); # POSIX.1のisattyの実装 sub isatty { my $fd = shift; my $termios = POSIX::Termios->new; my $retval = $termios->getattr($fd); return 0 unless defined $retval; return 1; }
11.5 isatty関数のテスト
#!/usr/bin/env perl use strict; use warnings; do '11_4.pl'; # isatty関数のテスト for my $fd (0..2) { print "fd $fd: "; if (isatty($fd)) { print "tty\n"; } else { print "not a tty\n"; } }
11.6 POSIX.1のttyname関数の実装
#!/usr/bin/env perl use strict; use warnings; use POSIX (); use File::Spec (); # POSIX.1のttyname関数の実装 my $DEV = "/dev"; sub ttyname { my $fd = shift; unless (POSIX::isatty($fd)) { return; } my @fdstats = POSIX::fstat($fd); unless (POSIX::S_ISCHR($fdstats[2])) { return; } opendir my $dh, $DEV or die "Can't open directory $DEV: $!\n"; for my $dir (grep !m{\.\.?}, readdir $dh) { my $pathname = File::Spec->catfile($DEV, $dir); my @devstats = stat $pathname; if ($fdstats[1] == $devstats[1] && $fdstats[0] == $devstats[0]) { closedir $dh; return $pathname; } } closedir $dh; return; }
11.7 ttyname関数のテスト
#!/usr/bin/env perl use strict; use warnings; do '11_4.pl'; # import isatty() do '11_6.pl'; # import ttyname() # ttyname関数のテスト for my $fd (0..2) { print "fd $fd: "; if (isatty($fd)) { printf "%s\n", ttyname($fd); } else { print "not a tty\n"; } }
11.8 getpass関数の実装
#!/usr/bin/env perl use strict; use warnings; use POSIX (); # getpass関数の実装 sub getpass { my $prompt = shift; my $path = POSIX::ctermid(); my $fd = POSIX::open($path, POSIX::O_WRONLY); unless (defined $fd) { die "Error: open $!\n"; } my $sig = POSIX::SigSet->new; my $sigsave = POSIX::SigSet->new; # block SIGINT & SIGTSTP, save signal mask $sig->emptyset; $sig->addset(POSIX::SIGINT); $sig->addset(POSIX::SIGSTOP); unless (POSIX::sigprocmask(POSIX::SIG_BLOCK, $sig, $sigsave)) { die "Error: sigprocmask $!\n"; } my $termios = POSIX::Termios->new; $termios->getattr($fd); my $saveflag; my $lflag = $saveflag = $termios->getlflag; $lflag &= ~(POSIX::ECHO | POSIX::ECHOE | POSIX::ECHOK | POSIX::ECHONL); $termios->setlflag($lflag); $termios->setattr($fd, POSIX::TCSAFLUSH); print "$prompt"; chomp(my $passwd = <STDIN>); # restore tty state $termios->setlflag($saveflag); $termios->setattr($fd, POSIX::TCSAFLUSH); # restore signal mask unless (POSIX::sigprocmask(POSIX::SIG_BLOCK, $sigsave, undef)) { die "Error: sigprocmask $!\n"; } # done with /dev/tty unless (POSIX::close($fd)) { die "Error: close $!\n"; } return $passwd; } 1;
Term::ReadKeyを使った方がベターでしょう。
#!/usr/bin/env perl use strict; use warnings; use Term::ReadKey (); print "Input password >> "; Term::ReadKey::ReadMode('noecho'); my $password = Term::ReadKey::ReadLine(0); print "\npassword is $password\n";
11.9 getpass関数を呼ぶ
#!/usr/bin/env perl use strict; use warnings; do '11_8.pl'; # getpass関数を呼ぶ my $passwd = getpass("Enter password:"); # now we use password print "\npassword is $passwd\n"; $passwd = "\0";
11.10 端末モードをローまたはcbreakに設定
#!/usr/bin/env perl use strict; use warnings; use Term::ReadKey (); # 端末モードをローまたはcbreakに設定 sub tty_cbreak { my $fh = shift; Term::ReadKey::ReadMode('cbreak', $fh) } sub tty_raw { my $fh = shift; Term::ReadKey::ReadMode('raw', $fh) } sub tty_reset { my $fh = shift; Term::ReadKey::ReadMode('restore', $fh); }
11.11 ローモードとcbreakモードのテスト
#!/usr/bin/env perl use strict; use warnings; do '11_10.pl'; # ローモードとcbreakモードのテスト sub sig_catch { print "signal caught\n"; tty_reset(*STDIN); exit 0; } local $SIG{INT} = \&sig_catch; local $SIG{QUIT} = \&sig_catch; local $SIG{TERM} = \&sig_catch; tty_raw(*STDIN); print "Enter raw mode characters, terminate with DELETE\n"; while (1) { my $c = getc(); if (ord $c == 0177) { last; } print hex(ord($c)); } tty_reset(*STDIN); tty_cbreak(*STDIN); print "Enter cbreak mode characters, terminate with SIGINT\n"; while (1) { my $c = getc(); print hex(ord($c)); } tty_reset(*STDIN);
11.12 ウィンドウサイズを表示する
#!/usr/bin/env perl use strict; use warnings; use POSIX (); use Term::ReadKey qw(GetTerminalSize); # ウィンドウサイズを表示する sub pr_winsize { my $fh = shift; my ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize($fh); printf "%d rows, %d columns\n", $hchar, $wchar; } unless (POSIX::isatty(*STDIN)) { die "STDIN is not a tty\n"; } pr_winsize(*STDIN); local $SIG{WINCH} = sub { print "SIGWINCH received\n"; pr_winsize(*STDIN); }; print "My pid = $$\n"; while (1) { POSIX::pause(); }