I ♥ WordPress

[時間割:Perl]『ミニマルPerl』その16
2009/01/30 02:12 posted by kunkichi

ちょっと間が空いちゃったけど、木曜日は「Perl」の時間。引き続きfind編です。

ミニマルPerl Unix/LinuxユーザのためのPerl習得法
Tim Maher
オライリージャパン
売り上げランキング: 96883
  • パイプの代わりに、バッククォートを使ったコマンド置換で、ファイル名を引数に取るようなところでもPerlは役に立つ。# grep 'John' `ls -d * | perl -wnl -e '-T and print;'カレントにあるディレクトリ・ファイルの一覧(ディレクトリの中身は含まない)からテキストファイルであるものを出力し、それらのうち、’John’を含む行を抽出している。
  • これをファイル形式で書くとこんな感じ#!/usr/bin/perl -s -wnl
     
    BEGIN {
      @ARGV and $re or
        warin "Usage: grep_text -re='REGEX' filen1 [file2, file3, ・・・]
        and exit 255;
     
    -T $ARGV or close ARGV and next;
     
    /${re}/i and  print "$ARGV: $_";
    引数 -reで検索したい表現を指定。ファイルがテキストファイルでなければcloseして次のファイル、テキストファイルであれば、引数で指定された表現が含まれているかをチェックして、含まれていればファイル名とその行を表示する。
    こういうcloseの使い方を覚えないとね。
  • 最初のコマンドのケースをfindのように再帰的にするならば# grep 'John' `find . -type f -print | perl -wnl -e '-T and print;'
  • 引数がファイル名でない場合でも、バリデーションの目的でPerlを使うのも有用。引数のためのプリプロセッサという感じ。
  • find で抽出したファイルに対して何かコマンドを実行したい場合は-execを使ったり、# find . -type f -exec rm {} ¥;とか# find . -type f | xargs rm とかでできるが、それぞれ問題がある。
    • -execの場合はコマンドのプロセスがfindの抽出結果分だけ起動する。
    • xargsの場合は1回のパイプで渡せる上限の数で渡すので、コマンドのプロセスは節約できるが、たとえば以下のケースだと、# find . -type f | xargs sort | tail -1もしfindの数が1回のパイプの上限を超えていた場合は、分割された「まとまり」単位でsortが実行されるため、全体をsortした結果の最新(最後の1行)にはならなかったりする。
  • Perlだとこれを回避できる。以下サンプルスクリプト。#!/usr/bin/perl -wnl
    BEGIN{ $latest_modified=0; }
    $mtime = (stat $_)[9];
    if( $mtime > $latest_modified ){
      $last_modified = $mtime;   ※ここで最新のファイルのタイムスタンプが保持される。
      $last_modified_file = $_;   ※ここで最新のファイルのファイル名が保持される。
    }
    END{
      print $name;
    }
    まあプログラミングらしく、最新のタイムスタンプを変数に記録しておいて、それと比較して最も最近のものの情報(ファイル名、タイムスタンプ)を常に更新するような仕組みにすればよいということだね。
  • あとxargsだと空白文字を含む引数は別々の引数として処理されしまう。Perlだと空白を区切りとして分割させないこともできる。# find . -type f | perl -wnl -e 'print -s,  " $_";

grepやawkの置き換え、だけでなく、今回のfindのように、組み合わせて有効に使えるというのもPerlのよいところだね

これでfindも終わりました。

次回はこれまでのコマンドよりの部分から、もう少しプログラミングに近いところのお話。

[時間割:C言語]『はじめてのC言語完全入門』その16
2009/01/24 00:40 posted by kunkichi

ここんところ疲れが出たみたいで、本読みつつ知らないうちに落ちてるという毎日で少しアップをサボり気味、、、また気分入れ替えて出直しです。ということで金曜日は『C言語』。今回は文字列の応用編。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • stdlib.h ・・・ 数値への変換#include <stdlib.h>
    • 文字列をintに変換int i = atoi("12345");
      i += 5;
      printf("%d\n",i);
      結果:12350
    • 文字列をlongに変換long l = atol("1000000000");
      l += 1000000000;
      printf("%ld\n",l);
      結果:2000000000
    • 文字列をflotに変換double f = atof("3.141");
      f += 0.245;
      printf("%f\n",f);
      結果:3.386000atofはfloat型でなくdouble型に変換するので注意
    • 数値と文字が両方含まれる場合
      • 文字は変換できないのでその手前までを変換しようとする。int i = atoi("12345abcde");
        printf("%i\n",i);
        結果:12345最初から文字があって数字が続く場合int i = atoi("12345abcde");
        printf("%i\n",i);
        結果:0
      • 先頭の空白文字は無視されるint i = atoi("     12345");
        printf("%i\n",i);
        結果:12345
  • strings.h ・・・ 文字列操作#include <strings.h>
    • 文字列のコピー
      • そのままコピーchar str1[] = "abcdefghijklmnopqrstuvwxyz";
        char str2[256];
        strcpy(str2,str1);
        printf("%s\n",str2);
        結果:abcdefghijklmnopqrstuvwxyzstrcpyの引数はコピー先、コピー元の順なので注意。
      • コピーする文字数を指定してコピーchar str1[] = "abcdefghijklmnopqrstuvwxyz";
        char str2[256];
        strncpy(str2,str1,10);
        str2[10] = '\0';
        printf("%s\n",str2);<code>結果:<code>abcdefghij
        strncpyの場合、コピー元文字数とコピーする文字数に注意。
        「コピー元文字数<コピーする文字数」の場合は上記のように最後にNULL文字を付与してやる必要がある。
        逆に「コピー元文字数>コピーする文字数」の場合は残りの文字をNULL文字が埋めるので気にしなくてよい。
    • 文字の連結char str1[] = "abcdefghijklmnopqrstuvwxyz";
      char str2[] = "1234567890";
      strcat(str2,str1);
      printf("%s\n",str2);
      結果:1234567890abcdefghijklmnopqrstuvwxyzstrcatは第1引数の後ろに第2引数をくっつける。
      連結先は連結した文字列を含める十分なサイズを持ってないと行けないとあるけど、上のように書くと特に意識しなくても連結されるんだけどなぁ、、、

おっと、なんかまとめるのに時間がかかったなぁ。あんまりページ進んでないやw。
まあ少しでも毎日続けれるように、無理せず今日はここまで。

[時間割:C言語]『はじめてのC言語完全入門』その15
2009/01/18 21:13 posted by kunkichi

火曜日は仕事だったのでしょうがないとして、金曜日は帰宅早々寝ちゃったので、その分を取り返しておきます。ということで『C言語』です。引き続き文字列とポインタです。関数の引数として文字列を渡す、その関数を呼び出す、というのをやります。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • 引数の書式は2パターン。配列で渡すか、ポインタで渡すか。
    • 配列の場合void kansu(char str[])
      {
        printf("渡された引数は%sです。¥n", str);
      }
      配列の場合はサイズ指定できないのは通常の配列と同じ。また、この時strは配列名であり定数である点に注意。
    • ポインタの場合void kansu(char *p)
      {
        printf("渡された引数は%sです。¥n", p);
      }
      この時strはポインタであり変数である点が配列で渡す場合と異なる。
  • 配列形式、ポインタ形式のそれぞれで渡された配列の引数は意味としては異なるけど、関数内では文法上同じ扱いになる。従って、
    • 配列で渡されたものを関数内でポインタとして使っても良い。
    • ポインタで渡されたものを関数内で配列として使っても良い。

    以前にやった配列とポインタのところと全く同じ。

  • 呼び出す時は、文字列の先頭アドレスを渡してあげる。つまり、
    • 文字配列で渡す場合は配列名(配列名は配列の先頭アドレスを示す)kansu(str);
    • ポインタで渡す場合はポインタ(ポインタはそもそも先頭アドレス、サイズはポインタの型が決める)kansu(p);
    • さらにもう一つ、文字列を直接指定してあげることもできる。kansu("abcdef");というのは式の中で文字列リテラルが渡されると先頭アドレスとして評価されるから。
  • ということでサンプル。書籍にもある、エラーメッセージとリターンコードを渡して、exitしちゃうプログラム。配列指定の場合とポインタ指定それぞれの場合の関数を用意してます。mainの中で呼び出す関数を変更すればそれぞれを呼び出します。まあ結果は同じなんだけど。#include <stdio.h>
    #include <stdlib.h> /* include exit() */
     
    void fatal_p(char *errmsg, int errcode);
    void fatal_a(char *errmsg, int errcode);
     
    int main()
    {
      fatal_a("異常終了!!!",255);
     
      fprintf(stdout,"%s\n", "正常終了");
      return 0;
    }
     
    void fatal_p(char *errmsg_p, int errcode)
    {
      fprintf(stderr, "%s\n", errmsg_p);
      exit(errcode);
    }
     
    void fatal_a(char errmsg_a[], int errcode)
    {
      fprintf(stderr, "%s\n", errmsg_a);
      exit(errcode);
    }
  • さっきの例では、サンプルの関数はvoidだったので何も返さないのだけど、関数で文字列を返す場合は、関数の型を文字へのポインタとして定義する必要がある。そして呼び出す際は、返された文字列を一時的に入れておく文字列用バッファを用意しておいてそこに代入する。char *kansu(〜){
      ・・・
    }
     
    int main(){
      char str[256];
      char = kansu(〜);
    }
    とあるのだけど、んー、うまく動かない、、、最後に書いてある、文字列を渡したら最後の1文字を除いた文字列を返すサンプルスクリプトをちょっといじってみた。本の中ではループで順に1文字づつ消しているのだけど、単に1文字だけ返すように書き換えてみたのだけど、バッファに返そうとするところでコンパイラがエラーを吐いちゃう。#include <stdio.h>
    #include <string.h>
     
    char *remove_last_char(char str[])
    {
      int last = strlen(str);
     
      if( last > 0 )
        str[last-1] = '\0';
     
      return str;
    }
     
    int main()
    {
      char s[] = "1234567890";
     
      char ret[256];
      ret = remove_last_char(s);
     
      printf("%s\n",p);
    }
    ビルドするとこんな感じ。$ gcc -o str_ary2 str_ary2.c
    str_ary2.c: In function ‘main’:
    str_ary2.c:19: error: incompatible types in assignment
    本のサンプルと同様に関数の結果をそのままprintfに渡してやるとちゃんと動く。#include <stdio.h>
    #include <string.h>
     
    char *remove_last_char(char str[])
    {
      int last = strlen(str);
     
      if( last > 0 )
        str[last-1] = '\0';
     
      return str;
    }
     
    int main()
    {
      char s[] = "1234567890";
     
      printf("%s\n",remove_last_char(s));
    }
    なんでかなぁ〜、、、文字へのポインタにする意味がまったくわからない。

うーん、やっぱりポインタ、配列、文字配列とまだまだ整理できてないので、理解できてない。以前にやった、ポインタと配列のところ読み返してみたんだけどね〜、それでもだめか。一度整理しておく必要があるかな。ただまあそれは課題として置いておいて、どんどん進んで一通り終わらせることを優先とします。

次回は文字列の応用編。

[時間割:Perl]『ミニマルPerl』その15
2009/01/16 01:00 posted by kunkichi

木曜日は「Perl」です。今回からfindコマンドです。これまでやってきた grep、sed、awk については、Perlで置き換えれるように見てきたのだけど、find の場合は「Perl との組み合わせ」でいろいろやっていきます。

ミニマルPerl Unix/LinuxユーザのためのPerl習得法
Tim Maher
オライリージャパン
売り上げランキング: 96883
  • ファイルのテスト機能。シェルやtestコマンドで使うようなやつです。
    例:sample.txt が通常のファイルだったら。 -f sample.txt and print;例:sample.binが通常のファイルじゃなかったら。! -f sample.bin and print;ファイル名を省略して$_にすることもできます。
    例:$_ が 通常のファイルだったら。-f and print $_;例:$_が通常のファイルじゃなかったら。! -f and print $_;
  • これ以外にもテスト機能の指定はできます。findじゃできないような、テキストとバイナリのテスト、実行ユーザの権限に準じたアクセス権でのテスト、などなど。
  • find と Perl を組み合わせでは、find で一連のパスを作っておいて、フィルタをかけるのにPerlを使います。$ find . -type -name '*.dat' |
    > find /var/log -mtime -3
    これにPerlでフィルタをかけます。> | perl -wnl -e '/-f and -T/ and print;'上記の例では、findでカレントディレクトリ配下の”*.dat”というファイル名で、かつ、最終更新日が3日以内のファイル名をリストアップして、最後にPerlで、通常ファイル、かつ、テキストファイルのものだけを抽出しています。
  • 名前のマッチングとかはPerlの強力な正規表現をつかったほうがらくちんです。$ find . -type -f | perl -wnl -e '/.+¥.dat$/ and print;'ちなみにfindの場合は以下。$ find . -type f -name "*.dat"ファイル名のマッチングが一つだけしかないなら、それほどでもないけど、以下のように複数のファイル名のマッチとかの場合は、括弧で囲んだ上、エスケープして、複数のファイル名の条件は、-oでつなぐ、そして、ファイル名のマッチにはワイルドカードレベルのものしか使えない、となり結構面倒。$ find . -type f \( -name '*.dat' -o -name '1¥.*' -o '*34*' \)
  • フルパスでファイルのリストがあって、この中から、特定の「ファイル名だけ」でマッチをしたい場合、以下のように-aオプションでパスを分解して使うと良い。$ find . -type f | perl -wnla'/' -e '$F[-1] =~ /abc$|*cde*|^123/ and print;'
  • 逆に特定の「ディレクトリ配下」という指定もできる。$ find . -type f | perl -wnl -e '-T and m:data/\w+\.dat:i and print;'-Tでテキストファイル限定、mを使って正規表現中にパスを表すスラッシュをいちいちエスケープしないように、iで大文字小文字無視。こういうのはfindだけではちょっと難しいのでかなり便利。

とりあえず今日はここまで。

[時間割:Perl]『ミニマルPerl』その14
2009/01/12 23:53 posted by kunkichi

祝日ですががんばります。月曜日は「Perl」。AWKをPerlでやってみようの最後です。演算子とか組み込み関数とか。

ミニマルPerl Unix/LinuxユーザのためのPerl習得法
Tim Maher
オライリージャパン
売り上げランキング: 96883
  • AWKもプログラミング言語なので演算子が存在する。
  • 関係演算子(==とか!=とか)でAWKは文字列と数値を区別しないのに対し、Perlは数値と文字列で演算子の記述が異なる(数値だと==なのが、文字列だとeq とか。)
  • 算術演算子はどちらも同じ。
  • AWKもプログラミング言語なので標準で組み込まれている関数が存在する。
  • 似たような関数はあるけど、引数が違うとか、意味が違うとか、記述が違うとか、結構異なる点もあることに注意。
  • 関数を使ってワンライナーを作ってみる。$ perl -wnl -e 'print substr $_, 0, 40; sample.txtこれだけでもAWKとは流儀が異なる点がある。
    • substrの第2引数は開始位置のオフセットをあ表すけど、AWKの場合、最初の1文字目は1になるのに対して、Perlは0になる。
    • Perlだと、関数の引数を囲む()は省略できる。
  • exprをAWKで実装した例が紹介されている。#!/bin/sh
    awk "BEGIN{ print $*; exit }"
    • $*で引数を展開するのはシェルの役割と思ってよいのかな?
    • AWKでは、入力ループは暗黙なので、単一処理はその外側で文を書く。そのためにBEGINを使っている
    • ループに入らずに処理を終えるためにexit。

    などなど、いろいろ面倒(っぽいw)。Perlで書くとらくちん。#!/bin/sh
    perl -wl -e 'print $*;'

    • $*をシェルが展開するのは同じ。
    • -wlだとループしない。
    • 処理が終われば勝手に終了するのでexitする必要はない。
  • メインの話とは関係ないけど、Lingua::EN::Inflectで単語を自動的に複数形にできる。これ、細かいところで便利かも。Damian先生作!#!/usr/bin/perl -w
    # stop.pl
     
    use Lingua::EN::Inflect 'PL_N';
    use strict;
    use warnings;
     
    for( my $i=1; $i<10; $i++ ){
      printf("%d %s\n", $i, PL_N 'apple', $i );
    }
    $ ./stop.pl
    1 apple
    2 apples
    3 apples
    4 apples
    5 apples
    6 apples
    7 apples
    8 apples
    9 apples
  • 範囲演算子を使用する場合、指定された範囲の最後の要素では’E0′を返す。これを使うと、ブロック間にスペースを入れるように出力するとかできる。
    前回例であげたHTMLファイルから<div>〜<div>で囲まれた範囲を表示するワンライナーでやってみる。$ perl -wnl -e '$status=/^<div>$/ ... /^<\/div>$/ and print;if($status =~ /E0$/){ print "";}' sample.html
    <div>
    ここが最初のブロック
    </div>
     
    <div>
    ここが2番目のブロック
    </div>
     

これでAWKのところも終わりました。次はfindコマンドの機能をPerlでやっていきます。

[時間割:C言語]『はじめてのC言語完全入門』その14
2009/01/10 03:37 posted by kunkichi

金曜日は「C言語」。文字列とポインタです。難しそーだなー。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • 文字へのポインタ宣言。char *p;
  • ただしこの時文字列のためのメモリ確保は行われていないため、以下の方法でメモリを確保する。
    • 動的メモリの確保。これは後日。
    • 配列を確保して先頭アドレスをポインタに代入。char *p;
      char str[]= "Hello, World";
      p = str;
      これでpが文字列を表す変数として使えるので、printfで出力してみる。printf("%s¥n",p);結果:Hello,Worldさらにポインタをインクリメントして、文字列の操作もできる。p++;
      printf("%s¥n",p);
      結果:ello,World
    • 文字列リテラルchar *p = "Hello, World";シンプルでわかりやすい。ただし、確保した文字列の変更はできない。printf("%s\n",p);
      *p = 'h';
      printf("%s\n",p);
      結果:Hello,World
      Bus error
      逆に、配列を確保してポインタに代入した場合は変更できる。char *p;
      char s[] = "Hello,World";
      p = s;
       
      printf("%s\n",p);
      *p = 'h';
      *(p+6) = 'w';
      printf("%s\n",p);
      return 0;
      結果:Hello,World
      hello,world

うーん、なんかピンとこないなー。配列、ポインタ、文字列とややこしいのがミックスした感じですっきりしない。
もう一度、前のところ見直してみようかな。

ちょっと短いけど今日はここまで。

[時間割:Perl]『ミニマルPerl』その13
2009/01/09 01:44 posted by kunkichi

木曜日は「Perl」です。引き続きAWK編。今回は「パターン範囲」で特定のレコード「群」を抜き出すのをやってみたいと思います。

ミニマルPerl Unix/LinuxユーザのためのPerl習得法
Tim Maher
オライリージャパン
売り上げランキング: 96883
  • AWKでは二つのパターンマッチをカンマで区切って指定すると、最初のパターンにマッチした行から二つ目のパターンにマッチした行までを抜き出すことができる。
    例えばapacheのログから特定の時間帯(例では2008/11/25の1:49:55から1:51:30まで)を抜き出す場合$ awk '/25\/Nov\/2008:01:49:55/ , /25\/Nov\/2008:01:51:30/' access_log.1
    127.0.0.1 - - [25/Nov/2008:01:49:55 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:49:56 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:49:57 +0900] "GET / HTTP/1.0" 200 〜
    ・・・
    127.0.0.1 - - [25/Nov/2008:01:51:28 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:51:29 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:51:30 +0900] "GET / HTTP/1.0" 200 〜
    Perlの場合は、カンマではなく、範囲演算子 .. で区切る。$ perl -wnl -e '/25\/Nov\/2008:01:49:55/ .. /25\/Nov\/2008:01:51:30/ and print;' access_log.1
  • 範囲演算子には … もある。この場合は2回目のパターンマッチは1回目のパターンマッチが「終わった後」である必要がある。といってもわかりにくいので、例を書いてみる。
    以下のようなテキストデータがあるとする。# cat sample.txt
    Make a Bath on Tuesday
    Take a Bath on Wednesday
    チュラチュラチュラチュラチュラチュララ〜ってやつですねw
    でこれを “Make 〜” で始まって “〜day” で終わるように範囲指定したいとする。
    • ドット2個の場合$ perl -wnl -e '/^Make/ .. /day$/ and print;' sample.txt
      Make a Bath on Tuesday
    • ドット3個の場合$ perl -wnl -e '/^Make/ ... /day$/ and print;' sample.txt
      Make a Bath on Tuesday
      Take a Bath on Wednesday

    つまりドット2個の場合は1回目のパターンと2回目のパターンが同じ行にある場合はそこで終わるけど、ドット3個の場合は1番目のパターンにマッチした「後」の行にマッチするというわけ。ログの調査とかだとドット3個の方が活用するシチュエーションが多い気がする。

  • これにさらにandでパターンマッチを追加する。例えば最初にログの日付でマッチする例をちょっと変えてみて、1:49から1:51のログを抜き出すとする。# perl -wnl -e '/25\/Nov\/2008:01:49/ ... /25\/Nov\/2008:01:51/ and print;' access_log.1
    127.0.0.1 - - [25/Nov/2008:01:49:00 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:49:01 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:49:02 +0900] "GET / HTTP/1.0" 200 〜
    ・・・
    127.0.0.1 - - [25/Nov/2008:01:50:58 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:50:59 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:51:00 +0900] "GET / HTTP/1.0" 200 〜
    最後の行を見るとわかると思うけど、このログはほぼ毎秒1回出力されているのに、最後の1:51は0秒のところしか出力されていない。本当はこのあとも1:51:59までこのログが出ている可能性がある。
    ということでちょっと変えてみる。少し先、1:52まで出力してみる。# perl -wnl -e '/25\/Nov\/2008:01:49/ ... /25\/Nov\/2008:01:52/ and print;' access_log.1
    ・・・
    127.0.0.1 - - [25/Nov/2008:01:51:58 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:51:59 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:52:00 +0900] "GET / HTTP/1.0" 200 〜
    うん、やっぱり続いている。
    ということでこれにもう一つパターンマッチを加えてみる。1:52を含まないパターンマッチを追加する。# perl -wnl -e '/25\/Nov\/2008:01:49/ ... /25\/Nov\/2008:01:52/ and !/25\/Nov\/2008:01:52/ and print;' access_log.1
    ・・・
    127.0.0.1 - - [25/Nov/2008:01:51:57 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:51:58 +0900] "GET / HTTP/1.0" 200 〜
    127.0.0.1 - - [25/Nov/2008:01:51:59 +0900] "GET / HTTP/1.0" 200 〜
    これでOK。こういう工夫は多少必要だけどわかってしまえばそれほどでもない。
  • 複数の範囲をマッチさせることもできる。たとえば以下のHTMLから<div>〜</div>で囲まれた範囲を抜き出す。$ cat sample.html
    <html>
    <head><title>複数範囲のマッチ</title></head>
    <body>
     
    <div>
    ここが最初のブロック
    </div>
     
    ここは表示されないはず
     
    <div>
    ここが2番目のブロック
    </div>
     
    </body>
    </html>
    $ perl -wnl -e '/^<div>$/ ... /^<\/div>$/ and print;' sample.html
    <div>
    ここが最初のブロック
    </div>
    <div>
    ここが2番目のブロック
    </div>

複数行を範囲で抽出するシチュエーションで本当に多いので、これはかなり使えるかも。

[時間割:C言語]『はじめてのC言語完全入門』その13
2009/01/06 22:56 posted by kunkichi

火曜日は「C言語」。前回、ファイルからの入出をやったので、今度はファイルへの出力です。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • 書き込み時は、オープンモードを”w”にする。FILE *ofp;
    ofp = fopen("output.txt","w")
  • ファイルへの出力にはfputcを使う。fput(c, ofp);
  • 書き込みの場合はエラー処理が重要。fputcは、EOFに達した場合も書き込みエラーの場合も両方-1を返すので、終了とエラーの区別ができない。よって、エラーが発生していたら真を返すferror関数を使って、「書き込みエラー」を取得する。fputc(c,ofp);
    if(ferror(ofp)){
      printf("書き込みに失敗しました。¥n");
      fclose(ofp);
      return XXX;
    }
    エラーが発生した場合に処理を終了する場合は、ファイルのクローズを忘れないように。
  • 前回のサンプルスクリプトに出力処理を追加。sample.txtを読み込んで、その内容をそのままresult.txtに出力する、いわばcpコマンドのような感じで。#include <stdio.h>
     
    #define INFILE  "./sample.txt"
    #define OUTFILE "./result.txt"
     
    int main()
    {
      FILE *ifp;
      FILE *ofp;
      
      if( !(ifp = fopen( INFILE, "r" )) ){
        printf("cannot open input file.\n");
        return 255;
      }
      
      if( !(ofp = fopen( OUTFILE, "w" )) ){
        printf("cannot open output file.\n");
        return 255;
      }
      
      int c;
      while( (c = fgetc(ifp)) != EOF ){
        fputc(c,ofp);
      }
      
      if(ferror(ofp)){
        printf("書き込みに失敗しました。\n");
        fclose(ofp);
        fclose(ifp);
        remove(OUTFILE);
        return 1;
      }
     
      if( fclose(ofp) ){
        printf("cannot close output file.\n");
        return 255;
      }
      
      if( fclose(ifp) ){
        printf("cannot close input file.\n");
        return 255;
      }
      
      return 0;
    }
  • getcharを使うと、標準入力から1文字読み込むことができる。fgetcと同じように返り値はintで受ける。引数はなし。
  • ferrorってイメージとしてはエラーフラグが立つという感じかな。whileのループの中でなく、ループが終了した後にチェックしているのはそういう意味かと。
  • 上記はferrorで書き込みエラーを拾ったら、出力ファイルポインタをクローズすると共にファイルを削除している。削除にはremove(ファイル名)を使用する。Perlのunlinkみたいなもんかな。
  • fputcは1文字単位なので、ちょっと使いにくい。だけど、fprintfを使えば、printfのように、文字列を特定の書式に当てはめる形で出力することができる。fprintf(ofp,"今日は%d月%d日です。¥n", month, day);引数にファイルポインタを追加する以外はprintfと全く同じ使い方。
  • fprintfを使ったファイル出力のサンプル$ cat fprintf.c
    #include <stdio.h>
     
    #define OUTFILE "result.txt"
     
    int main()
    {
      FILE *ofp;
      if(!(ofp = fopen(OUTFILE,"w"))){
        printf("cannot open outfile.\n");
        return 255;
      }
      
      int num=0;
      printf("数字を入力してください。 ");
      scanf("%d",&num);
     
      fprintf(ofp,"入力された数字は、%d です。\n",num);
      fclose(ofp);
     
      return 0;
    }
    ビルドして実行してみる。$ ./fprintf
    数字を入力してください。 356
    $ cat result.txt
    入力された数字は、356 です。
  • 標準出力、標準エラー出力にも出力できる。それぞれ組み込みのファイルポインタである、stdout、stderr を fprintfの引数にすれば良い。エラーメッセージ等はstderrに出すと良いね。

ということでファイルの出力もできるようになりました。
次は文字列とポインタです。

[時間割:Perl]『ミニマルPerl』その12
2009/01/06 02:44 posted by kunkichi

三日坊主にならないようにがんばりますw
月曜日は「Perl」です。引き続き、AWKの処理をPerlで実現していきます。今日はAWKの得意なパターンとアクションを組み合わせた処理です。

  • AWKの「パターン&アクション」の例$ awk -F":" '/^root/{ print NR ":" $1 }' /etc/passwd
    1:root
    AWKのいいところは、
    • アクションを指定していないパターンの場合は、レコードをそのまま自動的に出力する。$ awk -F":" '/^root/' /etc/passwd
      root:x:0:0:root:/root:/bin/bash
    • パターンを指定していない場合はアクションを全てのレコードに対してそのまま実行する。$ awk -F":" '{ print NR ":" $1 }' /etc/passwd
      1:root
      2:bin
      3:daemon
      4:adm
      5:lp
      ・・・
  • 上記のそれぞれをPerlで実装する。
    • パターンとアクション$ perl -wnlaF":" -e '/^root/ and print "$.:$F[0]";' /etc/passwd
      1:root
    • パターンのみ$ perl -wnlaF":" -e '/^root/ and print;' /etc/passwd
      root:x:0:0:root:/root:/bin/bash
      この場合は明示的にprintをつけないといけない。
    • アクションのみ$ perl -wnlaF":" -e 'print "$.:$F[0]";' /etc/passwd
      1:root
      2:bin
      3:daemon
      4:adm
      5:lp
      ・・・
  • AWKのBEGINとENDもそのままPerlで使える。apacheログのファイルサイズの合計と平均を出すサンプルスクリプト。サイズは10番目のフィールド($F[9])に出力されているものとする。# perl -wnla -e '
    > BEGIN{
    >   $total=0;
    >   $cnt=0;
    >   $average=0;
    > }
    > $total=$total+$F[9];
    > $cnt++;
    > END{
    >   $average=$total/$cnt;
    >   print "TOTAL:$total AVERAGE:$average";
    > }' access_log.1
    TOTAL:1100250219 AVERAGE:1559
    BEGINのブロックで変数を初期化しておいて(初期化していない場合は初期化されていないという警告が出る場合があるのと、最終的にデータがなかった場合にENDで出力する際に警告が出て、かつ、NULLになってしまうため。)、ENDでまとめて出力。
  • フィールド処理に対してマッチングするとより細かい条件を設定できる。/etc/passwdの中からUIDが一桁のものを抽出してUIDとユーザ名を出力する。# perl -wnlaF':' -e '($username, undef, $uid) = @F;$uid =~ /^\d$/ and print "$uid:$username";' /etc/passwd
    0:root
    1:bin
    2:daemon
    3:adm
    4:lp
    5:sync
    6:shutdown
    7:halt
    8:mail
    9:news
  • AWKのNF(最後のフィールド)は、Perlだと$F[-1]。
  • 段落モードの場合(-00)は、行単位でフィールド処理を行うのではなく、段落内の全ての文字列に対してフィールド処理が行われる。わかりにくいので以下の例で説明。
    例)sarコマンドでCPU使用率を定期的に取得した場合の出力結果は以下。# sar -P ALL -u 1 10
    1: Linux 2.6.X-X.X.X (www.example.com)   2009年01月06日
    2:
    3: 02時08分43秒       CPU     %user     %nice   %system   %iowait    %steal     %idle
    4: 02時08分44秒       all      0.00      0.00      0.00      0.00      0.00    100.00
    5: 02時08分44秒         0      0.00      0.00      0.00      0.00      0.00    100.00
    6: 02時08分44秒         1      0.00      0.00      0.00      0.00      0.00    100.00
    7:
    8: 02時08分44秒       CPU     %user     %nice   %system   %iowait    %steal     %idle
    9: 02時08分45秒       all      0.00      0.00      0.00      0.00      0.00    100.00
    10: 02時08分45秒         0      0.00      0.00      0.00      0.00      0.00    100.00
    11: 02時08分45秒         1      0.00      0.00      0.00      0.00      0.00    100.00
    12:
    ・・・
    47:
    48: 02時08分52秒       CPU     %user     %nice   %system   %iowait    %steal     %idle
    49: 02時08分53秒       all      0.00      0.00      0.00      0.00      0.00    100.00
    50: 02時08分53秒         0      0.00      0.00      0.00      0.00      0.00    100.00
    51: 02時08分53秒         1      0.00      0.00      0.00      0.00      0.00    100.00
    52:
    53: 平均値:        CPU     %user     %nice   %system   %iowait    %steal     %idle
    54: 平均値:        all      0.00      0.00      0.00      0.10      0.00     99.90
    55: 平均値:          0      0.00      0.00      0.00      0.30      0.00     99.70
    56: 平均値:          1      0.00      0.00      0.00      0.00      0.00    100.00
    ※わかりやすいように行番号を入れてますが実際には出力されていません。

    このうち、個々のCPU使用率のidle値の推移を取得する。
    $ perl -00 -wnla -e '/^¥d/ and print "$F[23] $F[-1]";'
    100.00 100.00
     
    100.00 100.00
     
    100.00 100.00
     
    100.00 100.00
     
    97.06 100.00
     
    100.00 100.00
     
    100.00 100.00
     
    100.00 100.00
     
    100.00 100.00
     
    100.00 100.00
     
    ポイントは以下。

    • 欲しいのは推移データだけなので、1行目のunameの出力と最後のサマリーの段落を出力しないように、段落の最初が数字(sarの個々の出力データは日付が最初に出力されている)の箇所にマッチした段落のみを抽出
    • さらに、個々の段落全体の文字列を最初から見ていくと、CPU0のidle値は24番目、CPU1のidle値は32番目なので、それぞれを@Fのインデックス指定で取得。インデックスは0オリジンなので、CPU0のidle値は$F[23]、CPU1のidle値は$F[31]、ただしCPU1のidle値はその段落全体で最後のフィールド最後になるので$F[-1]、として出力。

    といった感じで、表データの任意の値だけ取得というのもかんたんにできる。

まあ、こういうデータ抽出をするかどうかは別にして、自分なりの例を作ってやってみると理解しやすい気がしました。次回に続きます。

[時間割:C言語]『はじめてのC言語完全入門』その12
2009/01/03 04:22 posted by kunkichi

前回からずいぶん時間が経ってしまっているなぁ、、でも新年の誓いも経てたことだし、心機一転がんばります!
金曜日は「C言語」。ファイル入出力の章からです。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • stdioライブラリのfopen関数を使用する前提とします。
  • fopenはFILE型のポインタを返すので、事前にFILE型のポインタを用意しておくこと。FILE *fp;
    fp = fopen( "sample.txt", "r" );
  • 引数はPerl等と同じく、ファイルとモードを指定する。ただし、モードの指定は、”r”, “w”, “a”, “+”の文字列を使用する。意味は推して計るべし。
  • fopenは失敗した場合に0を返すのでこれを使ってエラーチェックする。
  • ファイルからのデータ読み込みには、ファイルから1文字入力するfgetc関数を使う。fgetcが返すのは、成功した場合は読み込んだ文字を、失敗した場合は-1となる。
  • fgetcの返り値は普通に考えればcharだけどintで受ける。これは
    • charは1バイトのintとして扱える。ただし、その範囲は符号の有無によって変わる。(符号付きの場合は-128〜127、符号なしの場合は0〜255)
    • 全ての文字集合を1バイトで表す場合、成功時は0〜255を返し、-1は失敗を表す。すなわち、-1を表現できる必要がある
    • ところがcharの符号有無は実装に依存する。
    • charがunsignedの場合は、-1を返した場合に10進数では255と区別ができなくなる。
    • charがsignedの場合は、-1を返すことはできるが返せる文字コードの上限が127までとなり、全ての文字集合を表現できない。

    従って、charでは役不足なのでintを使うということ。

  • fgetcはファイルの終わりに達すると-1を返すので、これを使って、fgetcを-1が返るまでループで回せば全データを取得できる。while( (c = fgetc(fp)) != -1 ){
        printf("%c",c);
      }
    また-1の代わりに定数EOFを使ってもよい。
  • ファイルのクローズにはfcloseを使う。引数はファイルポインタのみ。
  • fopen同様、成功時は0、失敗時は1を返す。読み込みだけの場合はfclose時のチェックは不要だけど、書き込みの場合は、チェックした方が良い。

ここまでの内容をサンプルで書くとこんな感じに成りました。単にファイルを開いて出力するだけです。
#include <stdio.h>
 
#define FILENAME "./sample.txt"
 
int main()
{
  FILE *fp;
  
  if( !(fp = fopen( FILENAME, "r" )) ){
    printf("cannot open input file.\n");
    return 255;
  }
  
  int c;
  while( (c = fgetc(fp)) != EOF ){
    printf("%c",c);
  }
 
  if( fclose(fp) ){
    printf("cannot close input file.\n");
    return 255;
  }
  
  return 0;
}

出力時はprintfを使っているけど、putcharとか、他にもあるようです。

とりあえず今日はファイルからの入力まで。次はファイルへの出力をやります。

このページの先頭へ