読者です 読者をやめる 読者になる 読者になる

perlで学ぶ「詳解 UNIXプログラミング」(その1) 第1章 概論

unix perl APUE

はじめに

「詳解 UNIXプログラミング」の第一章を示します。

1.1 ディレクトリ内の全てのファイルをリストする

opendirと readdirで実現できます。

#!/usr/bin/env perl
use strict;
use warnings;

# ディレクトリ内の全てのファイルをリストする

my $dirname = shift or die "$0 directory_name\n";

opendir my $dh, $dirname or die "Can't open directory:$dirname\n";
map { print "$_\n" unless $_ =~ m{^\.} } readdir $dh;
closedir $dh or die "Can't close directory:$dirname\n";

1.2 低レベルファイルIOを使って、標準入力から標準出力にファイルをコピーする

sysreadとsyswriteで実現します。

#!/usr/bin/env perl
use strict;
use warnings;

use constant BUFSIZE => 8192;

# 低レベルファイルIOを使って、標準入力から標準出力にファイルをコピーする

while (1) {
    my $len = sysread STDIN, my $input, BUFSIZE;
    die "Can't read from STDIN\n" unless defined $len;

    last if $len == 0;

    my $write_len = syswrite STDOUT, $input, $len;
    die "Can't write data to STDOUT\n" unless defined $write_len;
}

1.3 標準入出力を使用したファイルのコピー

getcと printで実現できます。POSIXにも putcはなく、printを使えと書いています。

#!/usr/bin/env perl
use strict;
use warnings;

# 標準入出力を使用したファイルのコピー
while (1) {
    my $c = getc;
    last unless defined $c;

    print STDOUT $c;
}

1.4 プロセスのプロセスIDを出力する

特殊変数 $$で表示できます。

#!/usr/bin/env perl
use strict;
use warnings;

# プロセスのプロセスIDを出力する
printf "Hello world from process ID %d\n", $$;

1.5 標準出力からコマンドを読み込み、実行する

execの部分は安全性を少し高めるためにコマンド名を別途指定しています。
このプログラム自体セキュリティを考えるとあまりおすすめできないものですがね。

#!/usr/bin/env perl
use strict;
use warnings;

# 標準出力からコマンドを読み込み、実行する
while (1) {
    print "> ";
    my $command = <STDIN>;
    last unless defined $command;
    next if $command =~ m{^\s*$};

    my $pid = fork;
    die "Can't fork\n" unless defined $pid;

    if ($pid == 0) {
        # child process
        my @commands = split /\s/, $command;
        exec {$commands[0]} @commands;
    }

    waitpid $pid, 0;
}

1.6 エラー処理

Perlでは例外処理に evalブロックを使います。
例外は特殊変数 $@に入っています。

#!/usr/bin/env perl
use strict;
use warnings;

# エラー処理

eval {
    open my $fh, "<", "not_exist_file" or die $!;
};
if ($@) {
    print "Error $@ \n";
}

直接 evalを使う場合、考慮しないといけないことが多いので
Try::Tinyモジュールを使った方が良いでしょう。
Try::Tinyでは例外は $@ではなく、$_に格納されます。

#!/usr/bin/env perl
use strict;
use warnings;

use Try::Tiny;

# エラー処理(モダン版)

try {
    open my $fh, "<", "not_exist_file" or die $!;
} catch {
    print "Error $_ \n";
};

なお Perlではエラー情報は特殊変数 $!に格納されます。
これは文字列として評価するとエラー内容が、数値として扱うとエラー番号が
返ります。エラーを表示するという場合はこの変数を使えばよいですが、
エラー処理自体は上記の evalブロック、Try::Tinyを使った方が良いでしょう。

1.7 ユーザ識別

ユーザIDは特殊変数 $<, グループIDは特殊変数 $)にそれぞれ格納されています。

#!/usr/bin/env perl
use strict;
use warnings;

# ユーザ識別
printf "uid = %d, gid = %d\n", $<, $);

1.8 シグナル

Perlのシグナル処理は %SIGにサブルーチンリファレンスを設定することで
実現できます。グローバルに書き換えないように localを使った方が良いでしょう。

Python版の方で懸念されていた、STDINからの入力待ち中にシグナルが発生した
場合、終了するという問題は Perlでは発生しませんでした。

#!/usr/bin/env perl
use strict;
use warnings;

local $SIG{INT} = sub {
    print "interrupt\n";
};

# シグナル
while (1) {
    print "> ";
    my $command = <STDIN>;
    last unless defined $command;
    next if $command =~ m{^\s*$};

    my $pid = fork;
    die "Can't fork\n" unless defined $pid;

    if ($pid == 0) {
        # child process
        my @commands = split /\s/, $command;
        exec {$commands[0]} @commands;
    }

    waitpid $pid, 0;
}

まとめ

詳解 UNIXプログラミングの第1章を示しました。
PerlPythonに比べると Cにいくらか近いのかなという印象ですね。