「初めての Perl」勉強5日目

また正規表現です。正規表現だけで3章も使ってる。大変だ><

第八章 正規表現の詳細

練習問題1

ワード fred または wilma があり、次に何個かの空白文字があり、その後ろにワード flintstone があるような行だけにマッチするパターンを作成しましょう。

#!/usr/local/bin/perl -w

while(<>) {
    chomp;

    if (/\b(fred|wilma)\s+flintstone\b/) {
        print "Matched: |$`<$&>$'|\n";
    }
    else {
        print "No match.\n";
    }
}
練習問題2

この問題では、先に解答を示しましょう。これらのパターンはどんな問題を解こうとしているのでしょうか? 実世界からもってきたこれらのパターンは何にマッチするでしょうか? どんな目的に使われるでしょうか?

/"([^"])"/
/^0?[0-3]?[0-7]{1,2}$/
/^\b[\w.]{1.12}\b$/

これは分かりづらい。とりあえず考えてみる。

  • /"([^"])"/
    • ダブルクォーテーションで囲まれた中にダブルクォーテーション以外の文字がある場合?
  • /^0?[0-3]?[0-7]{1,2}$/
    • 数字のみで構成される。
    • 最小は0のみ。
    • 最大値は0で始まり0〜3のいずれか1文字が続き、末尾に0〜7のいずれか同じ数字が2文字続く。
    • と言うことは0377が値として最大値となるのかな
    • 何に使うのかよく分からんw
  • /^\b[\w.]{1.12}\b$/
    • 何にマッチするのか全然分からん
    • 解答見た。
    • 英数字、下線、ドットのみから構成される文字列。ただし先頭と末尾はドット以外の条件付き。
    • ファイル名を確認するのに役立つみたい。
練習問題3

スカラー変数名だけを含むような文字列にマッチするパターンを作成しましょう。例えば、 $fred, $barney,$_ にマッチします。

#!/usr/local/bin/perl -w

while(<>) {
    chomp;

    if (/^\$\D(\w)+$/) {
        print "Matched: |$`<$&>$'|\n";
    }
    else {
        print "No match.\n";
    }
}

これでいいはず。

練習問題4

同じワードが連続して2回以上出現する行にマッチするパターンを作成しましょう。ワードの間に置かれる空白文字は違っていても構いません。例えば、 Paris in the the spring には、同じ単語が重複しているのでマッチします。あなたの書いたパターンは、 I think that that that is the problem に現れる3つのワード全てにマッチしますか? また、 This is a test にマッチしますか? This shouldn't match, according to the theory of regular expressions にはマッチしますか?

「ワードが連続して2回以上」と言っているので、This is a test にはマッチさせない。また This shouldn't match, according to the theory of regular expressions にもマッチさせない。(is はワードであるが This ではこれで一単語なので違うものと認識させる必要がある)
と言うわけで書いてみたがうまくいかない。なので解答を見た。

#!/usr/local/bin/perl -w

while(<>) {
    chomp;

    if (/\b(\w+)(\s+\1)+\b/) {
        print "Matched: |$`<$&>$'|\n";
    }
    else {
        print "No match.\n";
    }
}

こうなるみたい。正直かなり難しい。orz

第九章 正規表現の利用法

さらに難しくなっていく。。。

練習問題1

$what に入っているものが3回連続して現れるものにマッチするようなパターンを書いてください。もし $what の値が fred だったら、そのパターンは fredfredfred にマッチしなければなりません。もし $what ふぁ fred|barney だったら、そのパターンは fredfredbarney や barneyfredfred や barneybarneybarney などにマッチしなければなりません。

#!/usr/local/bin/perl -w

my $what = 'fred|barney';

while(<>) {
    chomp;

    if (/($what)\1+/) {
        print "Matched: |$`<$&>$'|\n";
    }
    else {
        print "No match.\n";
    }
}

これでいいのかな?

練習問題2

perlfunc.pod ファイルの中を探して、=item で始まり、その次に何個かの空白文字が続き、さらに Perl 識別子名が続くような行を見つけ出すプログラムを書いてください。プログラムが識別子名を見つけるたびに、それを表示するようにしてください。

ダイヤモンド演算子を忘れたので復習してきた(これ鬼門。ダイヤモンド演算子が今まで一番理解できなかった)
とりあえず書いてみたが、これでいいのかな?

#!/usr/local/bin/perl -w

@ARGV = '/usr/local/lib/perl5/5.8.8/pods/perlfunc.pod';

while(<>) {
    chomp;

    if (/^=item\s([a-zA-z_]\w)+\s/) {
        print "$&\n";
    }
}

と思ったら全然違った。全然じゃないか。とりあえず違う。

#!/usr/local/bin/perl -w

@ARGV = '/usr/local/lib/perl5/5.8.8/pods/perlfunc.pod';

while(<>) {
    chomp;

    if (/^=item\s+([a-z_]\w*)/i) {
        print "$1\n";
    }
}

説明すると =item は必ず先頭にあり、その後ろに少なくとも1つはスペースがある。で、先頭の文字は数字以外。んでもって大文字小文字の区別はしないってなるとこうなるわけか。しかも出力は記録されたものだけだから $1 なのか。これは分かりづらい。

練習問題3

問題2のプログラムを改造して、=item 行に3回以上現れた識別子名だけを、現れた回数とともに表示するようにしてください。

分からんかった。解答見る。

#!/usr/local/bin/perl -w

@ARGV = '/usr/local/lib/perl5/5.8.8/pods/perlfunc.pod';

my %seen;

while(<>) {
    chomp;

    if (/^=item\s+([a-z_]\w*)/i) {
        $seen{$1};
    }
}

foreach (sort keys %seen) {
    if ($seen{$_} > 2) {
        print "$_ was seen $seen{$_} times.\n";
    }
}

ハッシュを使う発想はなかったわ。これはでも難しいなぁ。センスねぇなぁオレ。。。

第十章 さまざまな制御構造

だいたい分かるが、覚えることが大杉。

練習問題1

1から100までの間から選んだ秘密の数を、ユーザに当ててもらうプログラムを書いてください。このプログラムは、入力した数が当たるまで、何回でも繰り返してユーザに入力を求めます。乱数を得るには int(1 + rand 100) と言う魔法の式を使ってください。ユーザの入力した数が当たらなかったら、"Too high"または"Too low"と表示するようにします。もしユーザが quit または exit と入力したり、あるいは空行を入力したら、プログラムを終了させてください。もちろん、数が当たった時にも、プログラムを終了させてください!

答えとなる数はこっちで設定するのだろうか。とりあえず魔法の式の部分を調べてみた。
int 関数は式を評価して整数を返すみたい。と言うことはフツーに考えれば1から100の間のどれかの数字がこれで取れるってことになるのか。
と言うわけで少し書いてみたが、ループにはまって終わったorz

#!/usr/local/bin/perl -w

my $list = int(1 + rand 100);

while (<>) {
    if ($_ gt $list) {
        print "Too high\n";
        redo;
    }
    elsif ($_ lt $list) {
        print "Too low\n";
        redo;
    }
    elsif ($_ eq "exit" || $_ eq "quit") {
        last;
    }
    elsif ($_ eq "") {
        last;
    }
    elsif ($_ eq $list) {
        last;
    }
}

これでダメだった。解答見る。

#!/usr/local/bin/perl -w

my $secret = int(1 + rand 100);

while (1) {
    print "Please enter a guess from 1 to 100;";
    chomp(my $guess = <STDIN>);
    if ($guess =~ /quit|exit|^s*$/i) {
        print "Sorry you gave up. The number was $secret.\n";
        last;
    }
    elsif ($guess < $secret) {
        print "Too small. Try again!\n";
    }
    elsif ($guess == $secret) {
        print "That was it!\n";
        last;
    }
    else {
        print "Too large. Try again!\n";
    }
}

while (1) が何を示しているのか理解できない。誰か教えてエロい人。。。

第十一章 ファイルハンドルとファイルテスト

ここも覚えることがたくさん。ビット演算子は飛ばした。

練習問題1

ユーザから入力ファイル名、出力ファイル名、サーチパターン、置き換え文字列を入力してもらうようなプログラムを書いてください。(これらをコマンドライン引数から取得するのではなく、ユーザから対話的に入力してもらうようにします。)このプログラムは、入力ファイルを読み込んで、見つかったサーチパターンを全て置き換え文字列に置き換えて、出力ファイルへと書き出します。(入力ファイルとは別の)既存のファイルを上書きすることが出来るでしょうか? サーチパターンに正規表現のメタキャラクタが使えますか?置き換え文字の中でメモリ変数や逆スラッシュエスケープを使えますか?

意味が分かりませんw
と言うわけで解答です。

#!/usr/local/bin/perl -w

sub get_line {
    print $_[0];
    chomp(my $line = <STDIN>);
    $line;
}

my $source = &get_line("Which source file?");
open IN, $source
    or die "Can't open '$source' for input: $!";

my $dest = &get_line("What destination file?");
die "Won't overwirite existing file"
    if -e $dest;
open OUT, ">$dest"
    or die "Can't open '$dest' for output: $!";

my $pattern = &get_line("What search pattern: ");
my $replace = &get_line("What replacement string: ");

while (<IN>) {
    s/$pattern/$replace/g;
    print OUT $_;
}
練習問題2

コマンドラインからファイル名のリストを受け取って、その1つ1つについて、読み出し可能か、書き出し可能か、実行可能か、存在しないかを表示するプログラムを書いてください。0に chmod されたファイルに対しては何を表示するでしょうか?

途中まで書いたけど分からんかった。
以下解答。

#!/usr/local/bin/perl -w

foreach my $file (@ARGV) {
    my $attribs = &attributes($file);
    print "'$file' $attribs.\n";
}

sub attributes {
    my $file = shift @_;
    return "does not exist" unless -e $file;

    my @attrib;
    push @attrib, "readable" if -r $file;
    push @attrib, "writable" if -w $file;
    push @attrib, "executable" if -x $file;
    return "exists" unless @attrib;
    'is ' . join " and ", @attrib;
}
練習問題3

コマンドラインからファイル名のリストを受け取って、そのうち最も古いファイルの名前とその古さを日数単位で表示するプログラムを書いてください。ファイル名のリストが空だったら何が起こるでしょうか?

これは書けるだろーと思ったらダメだったorz
ヤバい、全滅じゃん。。。

#!/usr/local/bin/perl -w

die "No file names supplied!\n" unless @ARGV;
my $oldset_name = shift @ARGV;
my $oldset_age = -M $oldset_name;

foreach (@ARGV) {
    my $age = -M;
    ($oldest_name, $oldest_age) = ($_, $age)
        if $age > $oldest_age;
}

printf "The oldest file was %s, and it was %.1f days old.\n",
    $oldest_name, $oldest_age;