開発のfujita.yです。
IO::PtyとPosixライブラリを使用して
perlでttyrecモドキを作ってみました。
IO::Pty
perlで仮想端末を使う場合IO::Ptyが使用出来ます。http://search.cpan.org/~rgiersig/IO-Tty-1.02/Pty.pm
OSの依存を吸収してくれるので、C言語のように
/dev/ptmxや/dev/ptsXXを開く必要はなく、
my $pty = IO::Pty->new()
とするだけでマスターとスレーブが簡単に開けてとっても便利です。
それぞれのハンドルは、
マスター側=> IO::Pty->new()の戻り値
スレーブ側=> 戻り値からslave関数
で取得できます。
print $pty "test";
print <$pty>;
my $slave = $pty->slave();
print $slave "test";
print <$slave>
仮想端末の詳しい説明はman ptyを参照してください。
IOの設定
<HANDLE>を使用したIOは標準Cライブラリのstdioが使われioバッファが有効になります。
ttyrecモドキを作る場合にはIOバッファは必要ないので
出力時:
事前にSTDOUT->autoflush()
入力時:
sysread関数を使用
することによりioバッファを使用しなくします。
端末の設定
マスターに対して端末の設定を行います。カノニカルモードの場合端末が行単位でしか入力を受け付けないので無効にします。
また、入力文字を画面に表示する必要もないので無効化します。
POSIXで定義されているのでPOSIX::Termiosを使って更新します。
perlではtermios構造体を触る必要はなく直接setlflagメソッドを使用して更新します。
端末設定の詳しい説明はman termiosを参照してください。
実装
#!/usr/bin/perl
use strict;
use warnings;
use IO::Pty;
use IO::File;
use POSIX qw(:termios_h setsid);
my $term = POSIX::Termios->new();
my $stdin = fileno(STDIN);
$term->getattr($stdin);
my $deflflag = $term->getlflag();
$term->setlflag($deflflag & ~(ICANON | ECHO));
$term->setattr($stdin, TCSANOW);
STDOUT->autoflush();
my $ok = 1;
$SIG{CHLD} = sub { $ok = 0 };
my $pty = IO::Pty->new();
if ( fork() ) {
my $run = 1;
$pty->close_slave();
while( $ok ) {
sysread STDIN, my $buf, 1;
print $pty $buf;
}
$term->setlflag($deflflag);
$term->setattr($stdin, TCSANOW);
} else {
if ( fork() ) {
$pty->close_slave();
my $fout = IO::File->new('> rec.dat');
while ($ok) {
sysread $pty, my $buf, 1;
print STDOUT $buf;
print $fout $buf;
}
} else {
setsid();
my $slave = $pty->slave();
close $pty;
open(STDIN, '<&', $slave);
open(STDOUT, '>&', $slave);
open(STDERR, '>&', $slave);
exec("/bin/sh");_
}
}
各プロセスの役割は以下のとおりです。
実行
ttyrec.plを実行するとshellが起動して作業を行った後、exitで抜けると
カレントディレクトリにrec.datの名前で出力のコピーが生成されます。