情報科学概論
2002.6.11

Back to index page


  1. 本日の作業内容

  2. 出席確認

    出席に関する注意事項を説明しますので,その注意を良く聞いてから,電子メールにより出席の申告をしてください.「件名」は

    gairon 6-11 attend s0240**

    のように自分の学生番号を使って下さい.なお,入力ミスがあると出席として認 められないことになりますので,慎重に行ってください.文 字はすべて1バイト文字 (半角英数字) です.途中に入るスペースは一度に一個だけと してください.スペースは全部で3個です.また,**の部分は自分の学生番号の下二桁 です.

  3. 正規表現

    正規表現とは文字列の「パターン」の表現方法です.文字列の中から特定の文字 の繰り返しや位置関係のパターンを抽出し,表現する方法ですが,wwwサーバに おけるCGIプログラムなどで「フォーム」に入力した情報をデータベースの項目 ごとに分類して保存したり,自動で返信メールを出したり,と言うような処理で は必ず使用されます.慣れないうちはこれもまた難しい項目ですが,まずは使っ てみて感触をつかんでみてください.

    • Rubyにおける正規表現の表現

      Rubyのスクリプトの中では, / / で囲まれた文字列を正規表現として 扱います.abc を「含む」文字列という場合には,

      /abc/

      のように書きます.これは,文字列の中のどこかに abc という文字の 並びが存在すると該当します.そのような該当する場合を「マッチする」と言いま す.マッチすることをどのようにスクリプト内で使用するかについては,目的ご とにそれぞれ異なっていますので,一概には言えませんが,例えば,次のような 例で試してみましょう.

      
      str = ARGV.shift
      if /abc/ =~ str
        print "Match!\n"
      else
        print "Not match.\n"
      end
      

      スクリプトを match.rb という名前で保存したとすると,

      $ ruby match.rb hogehoge

      というようにコマンドライン引数に文字列を与えて動かします.引数として与え た文字列のどこかに abc という文字の並びがあれば「マッチする」の で Match! と表示しますが,そうでなければ,Not match. を返します.引数に種々の文字列を入れて試してみてください.

    • メタ文字 (繰り返し)

      「メタ」とはギリシャ語に語源を持つ接頭辞で古くはmetaphysical (形而上)の ように使われた表現であり,対象物を指すのではなくその一段上の概念を示す場 合に使用されるようなちょっと難しい表現です.ここで言うメタ文字は文字本来 の表す意味とは違う意味で使用される文字のことを言います.シェルで使うワイ ルドカードの記号などが同様のものと考えられます.

      先ほどのスクリプトの一部分を変更してみましょう.変更するのは,if で始まる行です.

      
      if /a*/ =~ str
      

      として動作させるとどうでしょうか.引数のどのような文字を入れてもマッチす る結果となります.唯一マッチしないのは,引数に何も与えない場合です.では, 先ほど使用した * の意味は何であったのでしょうか.

      正規表現では,* は直前の表現の「0回以上」の繰り返し,になります. すなわち,先ほどは a と言う文字が0回以上繰り返されていたらマッ チするわけです.* + に変更して試してみてください. 今度は,先ほどと異なる結果になるはずです.+ は直前の表現の「1回」 以上の繰り返し,となるので,必ず a という文字が含まれていないと マッチしません.

      直前の表現について少し変更したものも確かめてみましょう.先ほどま ではマッチするかどうかを試す文字を a だけとしましたが,次にはそ れを ab に変更してまた試してみてください.どういう条件ならばマッ チするのか,考えてみましょう.

      ここまで紹介した繰り返しは0回もしくは1回以上でしたが,n回と言うように自 分で指定する場合はどうすれば良いかというと,{m,n} とすると,直 前の表現のm回からn回までの繰り返しにマッチします.スクリプトの条件判断の 部分を

      
      if /a{4,4}/ =~ str
      

      などとして試してみてください.最後に,繰り返しに関する記号一覧の一部をま とめておきます.

      *直前の表現の0回以上の繰り返し
      +直前の表現の1回以上の繰り返し
      ?直前の表現の0回または1回の繰り返し
      {m,n}直前の表現のm回からn回までの繰り返し

    • 文字クラス

      もう一度例文全体を次のように変更して表示してみます.

      
      str = ARGV.shift
      if /a./ =~ str
        print "Match!\n"
      else
        print "Not match.\n"
      end
      

      ピリオド . は「改行」を除く任意の1文字を意味します.ということ は,上の例では,a の後に何でも良いので文字があればマッチします が,a の後ろに文字がなければマッチしません.次の例はどうでしょ うか.いつもの条件部分を次のように変更します.

      
      if /[abc]/ =~ str
      

      この例では,引数の文字列中に a b c のどれか一文字でも含まれてい ればマッチします.この [ ] でくくった部分はその中の文字のどれか, と言うことを意味しています.今の場合であれば,文字コードの順に並んでいる ので,[a-c] と書くこともできます.と言うことは,[a-z] とすると,アルファベットの小文字のどれか,と言うことになります.また, [A-Za-z0-9] とすると,英数字(大文字と小 文字の両方を含む)すべてと言うことになるわけです. 文字クラスにはまとめると次のようなものがあります.

      .改行を除く任意の1文字
      [ ]文字クラスの指定
      [^ ]文字クラスの指定 (含まない文字 を指定)
      -文字クラス内の文字範囲
      \w英数字 ([0-9A-Za-z]と同じ)
      \W非英数字 ([^0-9A-Za-z]と同じ)
      \s空白文字 (スペース,タブ,改行, など)
      \S非空白文字
      \d数字 ([0-9]と同じ)
      \D非数字 ([^0-9]と同じ)

    • アンカー記号

      アンカーとは「錨」のことです.ここでは,文字の位置を特定するための記号を 紹介します.例によって,スクリプトを変更しましょう.マッチの条件部分を

      
      if /^abc/ =~ str
      

      のように変更します.^ の記号は行頭を意味します.すなわち,引数 の文字列が abc で始まっていればマッチしますが,そうでなければた とえ文字列中に abc が含まれていてもマッチしません.行末,すなわ ち,文字列の最後を指定するときには /abc$/ のように指定します.

    • グルーピング

      正規表現の文法にも数式の演算のように優先順位があります.すなわち,繰り返 しが最も優先され,次がアンカーと並べ,最後が選択です.選択とは /ab|cd/ のように | を用いて表現するもので,これは ab cd かのどちらかの文字列があれば マッチします.

      四則演算のように優先順位をきめておかないとどの順にマッチを取っていくかの 判断に問題が出るので必要な順位ですが,意図的に順位を変えたり表現を見通し 良くするためにグルーピングが使用されます.先ほどの /ab|cd/ は説 明したように, ab cd かのどちらかですが, /a(b|c)d/ とすると,abd かもし くは acd かどちらかとマッチすることにな ります.

    • 文字列の置き換え

      ここまでは,マッチするかどうかについて例を通して試してきましたが,実際に 「マッチ」とは文字列のどの部分が対応しているのかを見てみるために,別の例 を見てみましょう.そのために新しいメソッドである gsub を使用し ます.用法は次のようになっています.

      
      str = "abcdefg"
      puts str.gsub(/d/, "D")
      

      与えられた文字列に対して,カッコ内の前半の正規表現にマッチする部分につい て,カッコ内の後半の文字列に置き換えます.ここでは,小文字の d を大文字に置き換える操作を行っています.

      上のような特定の文字について gsub がどのように動作するかを見る のは簡単ですが,繰り返しと一緒に使用するとなかなか難しくなります.次のよ うに変更してみてください.

      
      str = ARGV.shift
      puts str.gsub(/a*/, "A")
      

      これを gsub.rb という名前で保存したとして,

      $ ruby gsub.rb a

      としたら結果はどうなるでしょうか.問題は,* が「0回以上の」繰り 返しという部分です.文字列が始まる部分にすでに a の0回以上の繰 り返しがあるので,まず,置き換えで A が置かれます.次に,実際に a が出てくるので,a の1回以上 の繰り返しが行われていることになり,A に置き換 えられます.また,引数として与える文字列を aa としても, aaaaaaaaaaa としても 結果は変りません.すなわち,a の0回以上 の繰り返しをすべて一つの A に置き換える からです.マッチしている部分は連続している a の全部と言うことになるわけです.以上のことは引数に a を含まな い文字列を与えるとさらに良く分かると思います.

      上のような無用の混乱を避けるためには,繰り返しは「1回以上」を指定する + を用いた方が無難でしょう.次に試すの は,より具体的な文字列の指定です.gsub の部分を変更してみましょう

      
      str = ARGV.shift
      puts str.gsub(/[a-h]*/, "A")
      puts str.gsub(/[a-h]+/, "A")
      

      結果はどうでしょうか.自分の予想と同じ動作をするかどうか確認してみてくだ さい.

    • 応用例

      概念的な話ばかりが続いたので少し実用的なスクリプトの例も試してみましょう. 次に挙げるのはディレクトリ内のRubyスクリプトの数を数えるものです.

      
      n = 0
      filename = `ls`.split()
      
      for i in 0.. filename.size
        if /rb$/ =~ filename[i]
          n += 1
        end
      end
      
      puts n
      

      動作自体は単純なのでたどっていけば分かると思います.バッククオート ` ` でくくっているのは,以前に説明したターミナルのコマンド操作を実行 させるための記号です.ここでは,ls コマンドによりディレクトリ内 のファイル一覧を取得し,Rubyスクリプトに該当するものを計上していますが, XEmacsで編集した際に残ってしまうバックアップファイルは計上していません.

    • メタ文字の打ち消し

      これまで,正規表現中で使用されるメタ文字について説明してきましたが,では, そのメタ文字の本来の意味を示す文字にマッチさせたい場合にはどうすれば良い でしょうか.改行文字 \n において,n 本来の意味を打ち消 すためにバックスラッシュが使用されたように,ここでもバックスラッシュで打 ち消すことが可能です.バックスラッシュの記号自体をマッチさせたい場合には, \\ のように2つ続けて1つ目のバックスラッ シュにより打消しの意味を持たせて2つ目のバックス ラッシュを参照することになります.

      それ以外にももう少し単純な打ち消し方法もいくつかあります.例えば,行頭を 表す ^ は先頭に来なければ意味がないので,先頭以外で使用するとメ タ文字としての機能はなくなりますし,文字クラスの [a-z] の際に使 用するハイフンは先頭に来るとメタ文字の機能が失われます.

    • 最左最長優先

      一般に正規表現のマッチングは一番最初に登場した部分で行われ,また,該当す る範囲の全体に対してマッチします.次の例を見てみましょう.

      
      str = "aBcabcabcabababcabdefdefdfdfdabcabcabc"
      puts str.gsub(/(abc|aBc)+/, "REPLACED")
      puts str.gsub(/(def)+/, "REPLACED")
      

      結果は次のようになるはずです.

      
      $ ruby test.rb
      REPLACEDababREPLACEDabdefdefdfdfdREPLACED
      aBcabcabcabababcabREPLACEDdfdfdabcabcabc
      

  4. 実習作業

    上で学習した例を試してください.また,次のようなコマンドと連携して自分の 環境を確認するようなスクリプトを自作してみると良いかもしれません.

    • ホームディレクトリ内のディレクトリで今年の6月に作成したもののリス ト

      
      line = `ls -al ~`
      list = line.split("\n")
      
      for i in 0..list.size
        if /^d/ =~ list[i]
          if /Jun/ =~ list[i]
            if /\d+:\d/ =~ list[i]
      	print list[i], "\n"
            end
          end
        end
      end
      

    • 最近実行したコマンドの内Rubyスクリプトを実行した回数の比率

      
      line = `cat ~/.bash_history`
      list = line.split("\n")
      n = 0.0
      
      for i in 0..list.size
        if /^ruby\s\w+/ =~ list[i]
          n += 1
        end
      end
      s ="%"
      
      printf "Ruby operation ratio was %1.1f%s.\n", n * 100 / list.size, s
      

      ただし,前回ログアウト時までのデータから計算しています.

  5. 宿題

    授業の終りに宿題 (AクラスBクラス別々) について説明しますので,アナウンスに注意してください.


ページトップに戻る

目次ページに戻る