• お問い合わせ
  • ユーザー登録
Turbolinux関連書籍
トレーニング&資格 パートナー コンサルティング ユーザー登録 Turbolinux関連書籍
turbolinux関連書籍 Turbolinux関連特集&Turbolinux執筆記事 Turbolinux関連教材ほか
Turbolinux関連書籍

C MAGAZINE Linux programming Tips
第7回 Linuxで正規表現してみよう

■はじめに

今回は、一度、はまってしまうと抜け出せない正規表現について、Cプログラムからの扱いを説明し、いくつか有用な実装例を紹介します。

ある程度の規模のプログラム(特にネットワークデーモン系)を書く場合、 コンフィギュレーションファイルがつきものです。しかしコンフィギュレーションファイルのパース(解析)をC言語で実装で苦労した人も少なくないでしょう。 通常の string.hで定義されている文字列関数では、限界があったり、ポインターでゴリゴリ書くのは、 思わぬバグを誘発したりするかもしれません。その一つの解決法として正規表現ライブラリー関数を使用する方法があります。正規表現の書き方さえ理解していれば、 少々複雑なファイルのパースも実装しやすくなることでしょう。


■簡単なパーサーを作ってみよう 〜 正規表現を使わない実装 vs 正規表現を使った実装 〜

とりあえず /etc/hosts のパース(解析)を例に、正規表現を使わない実装と使った実装がどの程度違うのか比較してみましょう。

説明するまでもないと思いますが、念のために/etc/hostsのフォーマットについて再確認の意味を含めて説明します。実際、 何らかのファイルのパーサーを実装する場合、対象ファイルのフォーマットを正しく理解することは非常に重要ですので以下のようにまとめておくのをお勧めします。

/etc/hostsのフォーマット
  • IPアドレスとホスト名を関連づけるファイルである。 "#"文字から行末までは、コメントになる。 各フィールドは、(半角)スペースまたはタブで区切られる。 有効な行は、少なくともIPアドレスとホスト名の組合せからなる。
  • IPアドレスとホスト名の後に、エリアスを記述できる。
上記のフォーマットに基づいてちょっとばかり意地悪なサンプルを作成してみました(わざと先頭にスペースをいれたりしています)。 hosts.example のようなファイル名で保存しておいてください。

hostsファイル 設定サンプル

# /etc/hosts
# IPaddress(IPv4)  FQDN(Fully Qualified Domain Name)  ALIAS

127.0.0.1      localhost.localdomain  localhost
192.168.0.1    gateway.localdomain      gateway
	192.168.0.2    hoge.localdomain       # comment

100.100.0.     badip.localdomain

では上記のサンプルファイルを標準入力から読み込んで、標準出力に(以下のような)必要な情報だけ出力するプログラムを記述してみましょう。

出力例

IP='127.0.0.1' FQDN='localhost.localdomain' ALIAS='localhost'
IP='192.168.0.1' FQDN='gateway.localdomain' ALIAS='gateway'
IP='192.168.0.2' FQDN='hoge.localdomain'
IP='100.100.0.' FQDN='badip.localdomain'


正規表現を使わない実装
まずは正規表現を使わないパーサーを実装してみましょう。実装的には色々できると思いますが、今回は、次のような手順で実装してみます。

Step0 一行読み込む
Step1 '#'または'\n'を発見したら、そこにNULL文字を入れる。
Step2 空の行の場合、Step0に
Step3 トークンセパレートを行う。
Step4 1つ目(IP)、2つ目(ホスト名)のトークンがある場合、それらの情報を出力する。
さらに3つ目(エリアス)があれば、その情報を出力する。

トークンセパレートをする場合、関数 strtok() を使うとちょっとだけ楽になるかもしれませんが、あまりお勧めしたくないので、 一文字づつ捜査する以下のような実装にしてみました。

[parse_hosts.c]

#include <stdio.h>
#include <string.h>
#include <ctype.h>

static void analyze_etchosts_noregex(void)
{
  char buf[BUFSIZ];
  char tokens[3][BUFSIZ];
  char *p;
  int  n;
  
  while( fgets(buf,sizeof(buf),stdin) != (char*)NULL ) {
    /* reset tokens buffer */
    *tokens[0] = *tokens[1] = *tokens[2] = (char)NULL;

    /* eliminate comment and newline*/;
    if( (p=strchr( buf,(int)'#'))  != NULL ) *p = (char)NULL;
    if( (p=strchr( buf,(int)'\n')) != NULL ) *p = (char)NULL;

    /* search IP address */
    p = buf;
    n = 0;
    for(;;) {
      while( !(isgraph((int)*p)!=0 || *p==(char)NULL) ) p++;
      if( *p != (char)NULL ) {
	char *t;
	t = tokens[n];
	/* copying to tokens buffer */
	while( !(isgraph((int)*p)==0||*p==(char)NULL) ) *t++ = *p++;
	*t = (char)NULL;
	n++;
	if( n==3 ) break;
      } else break;
    }

    if( strlen(tokens[0])>0 && strlen(tokens[1])>0 ) { 
      printf("IP='%s' FQDN='%s'",tokens[0],tokens[1]);
      if( strlen(tokens[2])>0 ) {
	printf(" ALIAS='%s'",tokens[2]);
      }
      puts("");
    }
  }
}

int main(int argc, char *argv[])
{
  analyze_etchosts_noregex();
  return 0;
}

[コンパイル&実行]

$ cc -Wall -o parse_hosts parse_hosts.c
$ ./parse_hosts < hosts.example


正規表現を使った実装

では正規表現(ライブラリー関数)を使って hostsファイルの パーサーを実装してみましょう。以下がその実装例になります。 幾分ソースコードが短くなったことに気づくでしょう(注: ソースコードを短くするのが目的ではありません)。それ以外はどのようなことに気づかれましたか? 意味不明な文字列がある? そう思った人は、"getting started 正規表現"を参考にしてください。とりあえずここでは、コンパイル、 実行して同じ結果が得られるか見ておいてください。

[parse_hosts_regex.c]
#include <stdio.h>
#include <string.h>
#include <regex.h>

static void analyze_etchosts_regex(void)
{
  regex_t preg;
  char buf[BUFSIZ];
  char *regex = "([.0-9]+)[ \t]+([^ \t#]+)([ \t]+([^ \t#]+)) ";
  regmatch_t   pmatch[5];

  regcomp(&preg,regex,REG_EXTENDED|REG_NEWLINE);

  while( fgets(buf,sizeof(buf),stdin) != (char*)NULL ) {
    if( regexec(&preg,buf,5,pmatch,0) != REG_NOMATCH ) {
      printf("IP='%.*s' FQDN='%.*s'",
	     pmatch[1].rm_eo-pmatch[1].rm_so,&buf[pmatch[1].rm_so],
	     pmatch[2].rm_eo-pmatch[2].rm_so,&buf[pmatch[2].rm_so]);
      if( pmatch[3].rm_eo!=-1 ) {
	printf(" ALIAS='%.*s'",
	       pmatch[4].rm_eo-pmatch[4].rm_so,&buf[pmatch[4].rm_so]);
      }
      puts("");
    }
  }  
  regfree(&preg);
}

int main(int argc, char *argv[])
{
  analyze_etchosts_regex();
  return 0;
}

[コンパイル&実行]

$ cc -Wall -o parse_hosts_regex parse_hosts_regex.c
$ ./parse_hosts_regex < hosts.example


■正規表現ライブラリー関数について

実は、Linuxに限らず、正規表現ライブラリー関数は、POSIX.2に準拠している Cライブラリを持つシステムならどれでも利用可能なはずです。

ヘッダーファイル regex.h で、正規表現関連の関数として4つの関数が宣言されています。これらは、manページ regex(3)に 詳しい説明がありますが、 ちょっと分かりにくいところもあるので、ここでは捕捉的にこれらの関数を説明します。

正規表現ライブラリー関数は、簡単に説明すると次のような流れで使用します。

関数 regcomp() : 正規表現パターン文字列をコンパイル
関数 regexec() : 対象バッファーと正規表現パターンの検索処理を行う
関数 regfree() : コンパイルしたパターンをフリー

各関数の書式は以下になります。


#include <regex.h>

int  regcomp(regex_t *preg, const char *regex, int cflags);
int  regexec(const  regex_t  *preg,  const  char   *string,
             size_t   nmatch, regmatch_t  pmatch[],  int
             eflags);
void regfree(regex_t *preg);	

関数regcomp()のregexに、正規表現パターン文字列を指定し、実行することで、 pregが指す構造体の中にプリコンパイルされたパターンが格納されます。また関数regcomp()のcflagsに、以下の値を指定することで、 正規表現の検索の動作を変更できます。論理和で接続することで複数指定可能です。

関数 regexec()の基本動作は、stringが正規表現パターンにマッチした場合、 関数の戻り値として0を返すといった簡単なものです(grepのようなものだと思ってください)。マッチした部分文字列を取り出そうとしようとした場合、 若干ややこしくなります。関数 regexec()では、正規表現パターン中でグルーピング指定している場合、 部分文字列のマッチング位置情報を得ることができます。 nmatchで指定された個数だけ、 (構造体)配列 pmatchに マッチした文字列の位置情報が格納されます。rm_soの値が-1の場合、マッチしていないことを意味します。

[構造体 regmatch_t]
typedef struct
{
   regoff_t rm_so;
   regoff_t rm_eo;
} regmatch_t;	

もう一度、parse_hosts_regex.cを見てみましょう。

[parse_hosts_regex.cの一部]
  regex_t preg;
  char *regex = "([.0-9]+)[ \t]+([^ \t#]+)([ \t]+([^ \t#]+)) ";
  regmatch_t   pmatch[5];
  ....
    if( regexec(&preg,buf,5,pmatch,0) != REG_NOMATCH ) {
      printf("IP='%.*s' FQDN='%.*s'",
	     pmatch[1].rm_eo-pmatch[1].rm_so,&buf[pmatch[1].rm_so],
	     pmatch[2].rm_eo-pmatch[2].rm_so,&buf[pmatch[2].rm_so]);
      if( pmatch[3].rm_eo!=-1 ) {
	printf(" ALIAS='%.*s'",
	       pmatch[4].rm_eo-pmatch[4].rm_so,&buf[pmatch[4].rm_so]);
      }
  .... 
  ....	

この例では関数 regexec()で、5つ部分文字列を取り出そうとしていることが分かると思います。ホストファイルの書式では、 フィールドは最大3つなのに、なぜ5つ指定する必要があるのか疑問に思うかも知れません。でもこのケースの場合は、5つが正解なのです。まず基本的に、 関数regexec()は、全体にマッチした位置情報を返します。これで 3+1で4になります。もう一つは、ホストファイルはエリアスはオプションなので、 "スペース+エリアス"を一つのグループとして定義して、ある場合のみマッチするようにしています。その中に"エリアス "だけのサブグループを定義しているので、 合計で5つ指定しなければなりません。

説明
REG_EXTENDED POSIX 拡張正規表現を使用
REG_ICASE 大文字小文字の違いを無視
REG_NEWLINE 全ての文字にマッチするオペレータに改行をマッチさせない
REG_NOSUB 部分文字列にマッチさせない

INDEX 部分文字列
0 全体
1 IPアドレス
2 ホスト名
3 スペース+エリアス
4 エリアス

ちなみに ホストファイルのサンプルに、わざとIPアドレス(IPv4)としては妥当でない文字列を入れているのに気がつきましたか? parse_hosts_regex.cのの実装では、この間違ったIPアドレスにもマッチしていましたが、IPアドレスにマッチする "([.0-9]+)"を"([0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)"に変更し、 もう一度、コンパイル&実行してみてください。間違ったIPアドレスを検出しないようになると思います。

一方、parse_hosts.c(正規表現を使っていないパーサー)に、このIPアドレスのチェックルーチンをつけるのは、 ちょっと面倒かもしれません。IPアドレスなら 関数 inet_aton()の戻り値で妥当なIPアドレスか判断できますが、 他のコンフィグファイルではそうもいかないかもしれません。

このように正規表現は、パターン文字列だけを変更することで、処理自体を大きくかえれる強力でクールなものなのです。また何らかのコンフィグファイルを作る場合場合、 そのコンフィグファイルのコメントにマッチする正規表現パターンを記述しておくのもお洒落かもしれません。


■ちょっと便利なプログラムを実装してみよう

POSIX regex関数を使った、読むにはちょうど良い長さのプログラムを2つほど用意しました。

grepを実装してみよう
普段よく使うgrepの実装の例を示します。名前は、simple-grepと名付けることにします。標準入力や複数ファイルの指定が可能だったり、 良く使われる オプション -c, -n, -l, -iもサポートしているので 本物の grepと見分けが付かないかも知れません(冗談です)。

1つほど宿題を出します。grepに実装されていて、simple-grep.cに実装されていないオプションの実装を行ってみてください。例えば、オプション -qの実装など。


inetdコンフィグ・ユーティリティを実装してみよう
インターネットスーパーサーバ inetd(netkit版inetd)の設定ファイルのパース 、ちょっとした変更を行うプログラムの実装例を紹介します。 RedHat系Linuxにあるシステムサービスの設定ツール chkconfigの名前にちなんで、chkinetcfgと名付けることにします。

まず最初に、/etc/inetd.confの書式を説明します。まず"#"文字から行末までは、コメントになります。基本的に、以下に示すような7つのフィールドから構成されます。

サービス名  ソケットタイプ プロトコル フラグ ユーザ  サーバパス サーバ引数

サーバ引数を除き、それ以外のフィールドは、空白またはタブで区切られます。サーバ引数は、コマンドライン引数で、複数のフィールドから構成されます。

chkinetcfgの特徴的なところは、#以降でも、サービスの定義として正しいければ、コメントアウトされたサービスとして扱うことができるところです。例えば、 一時的に、あるサービスが必要でなくなった場合に、コメントアウトすると思いますが、chkinetcfgは、そのような行も発見してくれます。

chkinetcfgの機能は、/etc/inetd.confで記述されているサービスを読み込み&解析する。リスト表示、 コメントアウトされているサービスのコメントを外し (またはその逆)設定ファイルを標準出力に出力する。

全てのサービスのリストを表示する。
$ ./chkinetcfg list
on  : echo       stream     tcp  
on  : echo       dgram      udp 
        ...
off : telnet     stream     tcp       # コメント化されている
        ...

特定のサービスのリストを表示する。
$ ./chkinetcfg list  telnet
off : telnet     stream     tcp 

telnetのコメントを外した設定ファイルを標準出力に出す
$ ./chkinetcfg on telnet > inetd.conf.telnet-on

echoを無効にするためにコメント化する。その設定ファイルを標準出力に出す
$ ./chkinetcfg off echo > inetd.conf.echo-off
				

最後に、一つだけ宿題を出します。chkinetcfg.cでは、ソケットタイプやプロトコルを指定できません。そのためechoのようなtcp,udp両方あるサービスに対して、 片方ずつコマンドを発行することができません。以下のように指定できるように変更してみてください。

$ ./chkinetcfg list echo/tcp
on  : echo       stream     tcp  

[simple-grep.c]
/* ------------------------------------------------------------
   This program is one of examples of grep implementation with 
   the regex() function in the GNU C library.

   written by Kazutomo Yoshii  

   This program is free software; you can redistribute it and/or modify
   it under ther terms of GNU General Public License, version 2.  See
   COPYING for more detail.
   ------------------------------------------------------------ */
#include <stdio.h>
#include <string.h>
#include <regex.h>

#define SG_COUNTNOMATCHES    (1<<0)
#define SG_IGNORECASE        (1<<1)
#define SG_MATCHEDFILENAME   (1<<2)
#define SG_LINENO            (1<<3)
#define SG_TWOORMOREFILES    (1<<4)

static int simple_grep(char *regex,char *filename,int options )
{
  FILE    *fp;
  regex_t preg;
  int     rc;
  char    buf[BUFSIZ];
  char    errbuf[BUFSIZ];
  int     lineno=1;
  int     n_matches=0;

  if(filename) {
    fp = fopen( filename,"r");
    if( !fp ) {
      perror("fopen");
      return 1;
    }
  } else {
    fp = stdin;
  }

  if( options&SG_IGNORECASE ) {
    rc = regcomp(&preg,regex,REG_EXTENDED|REG_NEWLINE|REG_ICASE);
  } else {
    rc = regcomp(&preg,regex,REG_EXTENDED|REG_NEWLINE);
  }

  if( rc ) {
    regerror(rc, &preg, errbuf, sizeof(errbuf) );
    printf("error: %s\n", errbuf);
    return 1;
  }

  while( fgets(buf,sizeof(buf),fp) != (char*)NULL ) {
    if( regexec(&preg,buf,0,NULL,0) != REG_NOMATCH ) {
      n_matches++;

      if( (options&SG_COUNTNOMATCHES)==0 &&
	  (options&SG_MATCHEDFILENAME)==0 ) {
	if(options&SG_TWOORMOREFILES ) {
	  printf("%s:", filename);
	}
	if( options&SG_LINENO) {
	  printf("%d:%s", lineno, buf);
	} else {
	  printf("%s", buf);
	}
      } else if( (options&SG_MATCHEDFILENAME) ) {
	break;
      }
    }
    lineno++;
  }

  if( (options&SG_COUNTNOMATCHES) ) {
    if(options&SG_TWOORMOREFILES ) {
      printf("%s:", filename);
    }
    printf("%d\n", n_matches);
  }

  if( (options&SG_MATCHEDFILENAME) ) {
    if( fp == stdin ) {
      puts("(standard input)");
    } else {
      printf("%s\n", filename); 
    }
  }

  regfree(&preg);

  if( fp != stdin ) {
    fclose(fp);
  }

  return 0;
}

static void print_usage(void)
{
  puts("Usage: simple-grep [OPTIONS] PATTERN [FILE...]");
  puts("");
  puts("[OPTIONS]");
  puts("");
  puts("-c : Suppress  normal  output;  instead print a count of");
  puts("     matching lines for each input file.");
  puts("");
  puts("-i : Ignore  case  distinctions  in the PATTERN");
  puts("");
  puts("-l : Suppress  normal  output; instead print the name of");
  puts("     each input file from which  output  would  normally");
  puts("     have  been  printed.  The scanning will stop on the");
  puts("     first match.");
  puts("");
  puts("-n : Prefix each line of output  with  the  line  number");
  puts("     within its input file.");
  puts("");
}

int main(int argc, char *argv[] )
{
  char *regex;
  int  options = 0;
  int  i,rc;

  if( argc < 2 ) {
    print_usage();
    return 1;
  }

  for(i=1; i  

[chkinetcfg.c]
/* ------------------------------------------------------------
   chkinetcfg is free software; you can redistribute it and/or modify
   it under ther terms of GNU General Public License, version 2.  See
   COPYING for more detail.

   Written by Kazutomo Yoshii  
   ------------------------------------------------------------ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>

#define INETDCONF   ("/etc/inetd.conf")

#define INETDENT_REGEX_STR    ("([[:punct:]]) [[:blank:]]*"\
      "([[:graph:]]+)[[:blank:]]+"\
      "(stream|dgram|rdm|seqpacket|raw)[[:blank:]]+"\
      "([[:alnum:]]+)[[:blank:]]+"\
      "(wait|nowait)([.0-9]+) [[:blank:]]+" \
      "([[:alnum:]]+)([.[:alnum:]]+) [[:blank:]]+"\
      "([/[:alnum:]]+)[[:blank:]]*"\
      "(.*)")

typedef struct _inetd_conf {
  struct _inetd_conf *next;
  char *line;     /* buffer for each line */
  char *name;     /* if name is NULL, means line doesn't contain service */
  char *socktype;
  char *proto;
  int  iscomment; /* val=1 means that service is commneted */
} InetdConf;

static void removeInetdConfList(InetdConf *ptr)
{
  while(ptr) {
    InetdConf *next;

    if( ptr->line ) free(ptr->line);
    if( ptr->name ) free(ptr->name);
    if( ptr->socktype ) free(ptr->socktype);
    if( ptr->proto ) free(ptr->proto);
    next = ptr->next;
    free(ptr);
    ptr = next;
  }
}

static InetdConf *newInetdConf(char *line,regmatch_t *pmatch)
{
  InetdConf *elem;

  elem = (InetdConf*)malloc(sizeof(InetdConf));
  if( !elem ) {
    perror("malloc");
    exit(1);
  }
  memset( elem,0,sizeof(InetdConf) ); 

  elem->line = strdup(line);
  if( pmatch ) {
    char tmp[BUFSIZ];

    sprintf(tmp,"%.*s", 
	    pmatch[2].rm_eo-pmatch[2].rm_so,&line[pmatch[2].rm_so]);
    elem->name = strdup(tmp);
    sprintf(tmp,"%.*s", 
	    pmatch[3].rm_eo-pmatch[3].rm_so,&line[pmatch[3].rm_so]);
    elem->socktype = strdup(tmp);
    
    sprintf(tmp,"%.*s", 
	    pmatch[4].rm_eo-pmatch[4].rm_so,&line[pmatch[4].rm_so]);
    elem->proto = strdup(tmp);
      
    if( line[pmatch[1].rm_so] == '#' ) {
      elem->iscomment = 1;
    }
  }

  return elem; 
}

static InetdConf *readInetdConfList(void)
{
  FILE *fp;
  char buf[BUFSIZ];
  InetdConf *conf = NULL;
  InetdConf *lastconf = NULL;
  regex_t preg;
  regmatch_t   pmatch[11];
  int     rc;
  char    errbuf[BUFSIZ];

  rc = regcomp(&preg,INETDENT_REGEX_STR,REG_EXTENDED|REG_NEWLINE);
  if( rc ) {
    regerror(rc, &preg, errbuf, sizeof(errbuf) );
    printf("error: %s\n", errbuf);
    return NULL;
  }

  fp = fopen( INETDCONF, "r" );
  if( !fp ) {
    printf("can't open %s\n", INETDCONF);
    return NULL;
  }

  while( fgets(buf, sizeof(buf),fp ) != (char*)NULL ) {
    InetdConf *newelem;
    if( regexec(&preg,buf,11,pmatch,0) != REG_NOMATCH ) {
      newelem = newInetdConf(buf,pmatch);
    } else {
      newelem = newInetdConf(buf,NULL);
    }
    if( !conf ) {
      lastconf = conf = newelem;
    } else {
      lastconf->next = newelem;
      lastconf = newelem;
    }
  }

  fclose(fp);
  regfree(&preg);

  return conf;
}

static void  switch_services(InetdConf *conf,char *on_off, char *name)
{
  int flag;
  while(conf) {
    flag = 0;
    if( conf->name ) {
      if( strcmp(conf->name, name)==0 ) {
	if( strcmp(on_off,"on")==0 && conf->iscomment==1 ) {
	  printf("%s", &(conf->line[1]));
	  flag =1; 
	} else if( strcmp(on_off,"off")==0 && conf->iscomment==0 ) {
	  printf("#%s", conf->line);
	  flag =1; 
	} 
      }
    }
    if( flag==0 ) printf("%s", conf->line);
    conf= conf->next;
  }
}

static void print_line(int iscomment,char *name,char *socktype,char *proto)
{
  printf("%s : %-10s %-10s %-10s\n",
	 (iscomment==1) "off":"on ",
	 name, socktype, proto);
}

static void  list_services(InetdConf *conf,char *name)
{
  while(conf) {
    if( conf->name ) {
      if( name ) {
	if( strcmp(conf->name, name)==0 ) {
	    print_line(conf->iscomment,conf->name,conf->socktype,conf->proto);
	}
      } else {
	print_line(conf->iscomment,conf->name,conf->socktype,conf->proto);
      }
    }
    conf= conf->next;
  }
}


static void usage(void)
{
  puts("");
  puts("Usage: chkinetcfg command [name]");
  puts("");
  puts("[command]");
  puts("\tlist : this command shows all services if service isn't specified.");
  puts("\t       if service is specified,information in only display");
  puts("\t       about name,socktype and proto.");
  puts("");
  puts("\ton   : this command turns on service which service is specified");
  puts("\t       with making un-comment of the target line."); 
  puts("");
  puts("\toff  : this command turns off service which service is specified");
  puts("\t       with making comment of the target line."); 
}

int main(int argc, char *argv[]) 
{
  InetdConf *conf;
  if( argc <2 ){
    usage();
    return 1;
  }

  conf = readInetdConfList();

  if( strcmp(argv[1], "list")==0 ) {
    if( argc == 3 )  list_services(conf,argv[2]);
    else             list_services(conf,NULL);
  } else if( strcmp(argv[1], "on")==0 || 
	     strcmp(argv[1], "off")==0 ) {
    if( argc == 3 )  switch_services(conf,argv[1],argv[2]);
    else {
      printf("Please specify the service name.\n");
      usage();
      return 1;
    }
  } else {
    printf("Unknown command : '%s'\n", argv[1]);
    usage();
    return 1;
  }

  removeInetdConfList(conf);

  return 0;
}	


お ま け

■getting started 正規表現

正規表現に使ったことのない人は、最初は、正規表現パターンは、謎めいたの文字列に感じるかもしれません。以下のように、 grep などでコツを掴んでみましょう。慣れて来ると離れられない存在になると思います。

$ egrep 'root'  /etc/passwd     <- rootがある行を表示
$ egrep '^r'    /etc/passwd     <- rで始まる行を表示
$ egrep 'bash$' /etc/passwd     <- bashで終っている行を表示
$ egrep '[0-9]{3}' /etc/passwd  <- 3桁の数字かある行を表示
$ egrep 'puts|printf' simple-grep.c  <- putsまたはprintfがある行を表示	

以下に良く使う正規表現のメタ文字の紹介をします。またmanページ regex(7)で文字クラスの説明がされています。表やmanページを参考に、 chkinetcfg.cで使われている正規表現パターンを解析してみてください。

良く使われる正規表現
メタ文字 マッチ対象
. 任意の文字1個にマッチする。
注: .(ドット)だけにマッチさせたい場合は、\.を使う。
[...] []内の任意の一文字にマッチする
ex). [abc]の場合、a,bまたはcが含まれる文字列にマッチする。
[^...] []内で指定された文字以外にマッチする
ex).[^abc]]の場合、a,bまたはcが含まれない文字列にマッチする。
0または1個の要素にマッチする
ex). string(s) は、string,stringsを含む文字列にマッチする。
* 0個以上にマッチする。
+ 1個以上にマッチする。
{min,max} min以上にmaxまでマッチする。 ex). [0-9]{3,4}は、3桁または4桁の整数にマッチする。
^ 行頭位置にマッチする。
$ 行末位置にマッチする。
| |の前後のパターンとマッチする。
ex). Unix|Linuxは、UnixまたはLinuxにマッチする。
(..) グルーピング。()内でマッチしたものは、参照可能なことを意味する。