• ベストアンサー

置換演算子についての疑問

お世話になっております。horagaiです。質問160286 http://oshiete1.goo.ne.jp/kotaeru.php3?q=160286 と同じようなことをやりたかったので回答#2のやり方を 試してみました。そこでいろいろ疑問が出てきたので教えていただきたいと思います。 (1) $num=<> ; while($num =~ s/(.*\d)(\d\d\d)/$1,$2/g){;} print "num=$num\n"; とするとたしかにうまくいくのですが、これでうまくいく理由がわかりません。 置換演算子が後ろからパターンマッチをしていくのだとすればわかるのですが。 前からだとするとたとえば123456は最初に(1)(234)56で引っかかって1,23456 。 次に (1,2)(345)6 で引っかかって1,2,3456 ・・・。 などとなりそうな気がします。 (2) またwhileを使わずに $num =~ s/(.*\d)(\d\d\d)/$1,$2/g; としてみると 例えば入力が 12345678 とすると num=12345,678 と最初の3桁しか区切ってくれません。マニュアルを見ると 「gオプションは出現したパターンをすべて置換する。」 と書いてあるのにどうしてでしょう。 前からマッチするせよ後ろからマッチするにせよカンマが1つしか 入らないということはないと思うのですが。 以上、私が根本的な勘違いをしているかも知れませんので その辺のところもご指摘いただければ幸いです。 ちなみにOSはLinux.Perlのバージョンは5.004です。

  • Perl
  • 回答数3
  • ありがとう数3

質問者が選んだベストアンサー

  • ベストアンサー
noname#25358
noname#25358
回答No.1

 えとですね。  正規表現には1つの原則がありまして。  たとえば、123456789だと、可能性として、   1,23456789   12,3456789   123,456789   1234,56789   12345,6789   123456,789  これらすべて、マッチする可能性がありますよね。  そういうパターンであることはわかりますか?  で、正規表現は、「マッチする可能性の中から、もっとも文字列の長いものを採用する」という原則があります。  ですので、.* にひっかかる可能性のあるうちで、もっとも長い文字列である 12345 がひっかかってるわけです。  これは正規表現すべてで統一されていて、最長マッチといいます。  逆に、もっとも短い可能性を採用させるには、   (.*?\d)(\d\d\d)  と、* のあとに ? を記述します。  もっとも、これをやってもおっしゃったような状況にはならず、   1,2345,6789  になりますが。  (これは、一度置き換えの対象になった部分は二度と検索しないという規則があるからです)

horagai
質問者

補足

な~るほど。質問の件についてはよーくわかりました。 >これらすべて、マッチする可能性がありますよね。 >そういうパターンであることはわかりますか? もちろんわかります。だから置換演算子が前からマッチさせていくとすると うまく行く理由がわからなかったのです。 >「マッチする可能性の中から、もっとも文字列の長いものを採用する」 >という原則があります。 この原則は知りませんでした。とにかく前から1文字ずつ読んでいって、マッチする パターンが最初に出現した時点で即置換するものだとばかり思っていましたが、 一度最後まで読んでからパターン検出をするのですね。 ですがこの原則はあくまで長さが不定のパターンに対する原則ですよね。 長さが一定になるようなパターンなら、例えば(\d\d)(\d\d\d)などであれば やはりパターンが最初に出現した部分にマッチすると考えてよいのでしょうか? また正規表現の上では長さ不定でも、たまたま与えられた文字列ではそのパターンに マッチするすべての場所が同じ長さになってしまった場合はどうなるのでしょう。 その場合も最初に出現した部分にマッチするのでしょうか? >一度置き換えの対象になった部分は二度と検索しないという規則があるからです なるほど。それで(2)の疑問、すなわちgオプションを付けても一度しか置換して くれない理由もわかりました。 最初に 「マッチする可能性の中から、もっとも文字列の長いものを採用する」 という原則にしたがって (12345)(678) でマッチさせ (12345),(678)とする。 すると (12345,678)はすでに置き換えられた部分だから検索対象にならない。 よって残っているのは空文字列なので置換演算は終了する。 というわけですね。 whileを使うとうまく行くのは、一回置換するごとに新しい変数として扱われるので また最初から全部検索してくれるため。 というわけですね。 そういう解釈であっていますか?

その他の回答 (2)

noname#25358
noname#25358
回答No.3

 leaz024さんに補足していただいているようですが念のため。 >長さが一定になるようなパターンなら  はい。  実際に試してみました。必ず先頭からです。  最後に $ を付けたら最後からになるかと思ったんですが、やってみたらやっぱり先頭からでした。  ですので、正規表現の検索は必ず先頭からってことになるようです。 >そういう解釈であっていますか?  これも合ってます。はい。

horagai
質問者

お礼

いろいろとありがとうございました。 いままで掲示板ソフトなどで置換演算子を使ってみてもなかなか思い通りの 置換をしてくれず、その理由がわからなくて悩んでいたのですが 「マッチする可能性の中から、もっとも文字列の長いものを採用する」 「一度置き換えの対象になった部分は二度と検索しない」 の2つの原則を知っただけで置換演算子の振舞の謎はほとんど解けました。 おかげさまでこれからは悩むことも減りそうです。 また何かありましたら宜しくお願いします。

  • leaz024
  • ベストアンサー率75% (398/526)
回答No.2

最長マッチについては、deagleさんのアドバイスでご理解頂けていると思います。 $num="1234567890"とすると、1回目のマッチングでは「1234567,890」となりますね。 s///は「置換した回数」(この場合1)を返すので、置換が成功するとwhileの条件が真となり、また置換を行おうとします。 この繰り返しで、2回目で「1234,567,890」、3回目で「1,234,567,890」となり、ここでマッチしなくなるためs///が0を返し、whileが終了します。 下記HPが大変参考になりますので、ご一読ください。

参考URL:
http://www.din.or.jp/~ohzaki/perl.htm#NumberWithComma
horagai
質問者

お礼

ご回答ありがとうございます。 御紹介いただいたURLは知っていたのですが、「数字をコンマで区切る」 の項目には気がつきませんでした。たしかに参考になりました。 正規表現は奥が深いですね。

関連するQ&A

  • Perlのs///を使った置換で質問です

    s///のパターンマッチ演算子を使った置換で、 <br>→<br /> に置換するときははどうしたらいいのでしょうか? ↓これでは<br />の/が引っかかってできないようです・・・。 $content =~ s/<br>/<br />/g;

    • ベストアンサー
    • Perl
  • Perl正規表現s置換演算子の末尾にカンマについて

    お世話になります。 Perl正規表現プログラムで、 s置換演算子の末尾に、 カンマと丸カッコでスカラ変数を囲んで、 存在している箇所 たとえば、 s/あいうえお/($hensu = $&) =~ tr{ABC}{abc}, ($hensu) =~ s{123}{123}, ($hensu) /eg; のような正規表現であれば、 上記のtr置換演算子や、s置換演算子それぞれの末尾に、 「 , ($hensu) 」 として、カンマと丸カッコでスカラ変数を囲んで、 存在している箇所は、 どのような意味があるのでしょうか? 以上になります。 PS: ネット上や正規表現の本などを読んでも、 上記したような、 カンマと丸カッコでスカラ変数を囲んでいる パターンの解説を、 見つけられなかった為、 ご質問させていただきました。 ご存じの方がいらっしゃいましたら、 教えてください。 よろしくお願いします。

    • ベストアンサー
    • Perl
  • 正規表現の置換で、カンマで区切られた語句を""と改行で分ける方法

    正規表現による置換についてお教えください。 Unixのコマンドで置換するときに、sed コマンドを使うと思います。 今、プログラムのソースコードの中に含まれている語句を取得するために、色々いじろうと思っています。 私が最もお聞きしたいのは、正規表現の書き方であることを、まずご理解ください。 ソースコードの中に、 string headline="語句1,語句2,・・・,語句n-1,語句n"; というように、 string headline= で始まっていて、 その後に " "(ダブルクォーテーションの組)で囲まれた部分が1箇所あり、 その部分に、カンマ(,)で区切られた語句がいくつもあるような行があったとき、 その " "の部分を、 "語句1", "語句2", ・・・ "語句n-1", "語句n" のように置換したいのです。 つまり、語句一つ一つをダブルクォーテーションで包んで、1行に語句が1つになるようにしたいのです。 厳密に言えば、stringとheadlineの間などに、タブや空白がいくつあるかわからないという問題なども考える必要があるかもしれません。 とりあえず私が考えたのは、 s/\(string headline="\)\([^,]\)+,\(";\)/\1\2",改行"\3/g です。 s/置換前の文字列のパターン/置換後の文字列のパターン/g となっています。 置換前のパターンで \( \) で囲まれている部分は、置換後のほうで、\1のように参照できます。 ■■でも、私の書いたものって、語句が繰り返し出現することに対応できていないように思われます。■■ 実際に、置換に改行を含めるには、正規表現をファイルの中に書いて、sedの-fオプションで読み込ませることになると思います。 string headline=" " という形をしていないものは、たとえ、" " の中がカンマで区切られていてもそれを分けません。

  • 置換演算子を使うと空白が入ってしまう

    $test = abcdef; $test =~ s/abc//g; 上記の様に置換演算子を使って文字列の削除を行うと、 「 def」の様に、削除した文字列に半角空白が入ってしまいます。 1回や2回ならよいのですが、何回も処理を行うと、 最終的に大量の空白が挿入されてしまい、大変困っています。 なんとか削除した部分に空白を挿入しない方法はないものでしょうか? 解決法がわかる方がいらっしゃいましたら、御教授頂きたく、 よろしくお願いします。

    • ベストアンサー
    • CGI
  • sedでカンマ区切りの列の置換を行いたい

    以下の様なファイルがあります。 sedコマンドを使用して、カンマで区切られたn番目のカラム目を 置換したい場合どうすればよいでしょうか。 "1","2","3","4","5","6" "1234","12","34567","abcd","efg","hi" "a c","d f","12 34","0","AAA","" "g i","j l","45 67","0","BBB","2021" "m o","p r","3 2 1","1","ABC","" "123","456","","abc","efg","hij" ①1カラム目以外置換(1カラム目以降削除) sed 's/\(.*\),.*,.*,.*,.*,.*/\1/g' test.csv ②最後の6カラム目以外置換(6カラム目以外削除) sed 's/.*,.*,.*,.*,.*,\(.*\)/\1/g' test.csv ③3カラム目を置換(3カラム目を「""」だけにしたい) sed -e 's/\(.*\),\(.*\),.*,\(.*\),\(.*\),\(.*\)/\1,\2,"",\3,\4,\5/g' test.csv とできたのですが、 表現がないというか、置換対象文字列の条件、表現が長いというか、全カラムを指定しているので、 少し、短くできないものでしょうか。 ①パターン sed 's/\([^,]*\),.*/\1/g' test.csv ②パターン sed "s/.*,\([^,]*\)$/\1/g" test.csv の様にできました。 ③パターン 思いう浮かばず これで、できているので間違いはないかとおもいますが、 どうなのでしょうか。 なにかいい案はないでしょうか?

  • 文字列置換

    UNIXのコマンドで文字列の置換を試みています。 今回行いたい置換としては。。。 12年 1986年2月12日 14 などの文字列を NUM年 NUM年NUM月NUM日 NUM などに、連続した数字をひとまとめにNUMに置換したいと考えています。 文字列の置換方法としてsedコマンドがあると聞いてしらべてみたのですが、いまいいち理解することができませんでした。 自分で考えて試してみたコマンドが以下の通りです。 sed -e "s/\([1-9]\{1,2,3,4\}\)/NUM/g" filename が・・・まったく置換はされませんでした。 よろしければお教えください。よろしくお願いいたします。

  • Vimで連続した空白をカンマ1つに置換したい

    Vimで連続した空白をカンマ1つに置換したい Vimでの置換がうまくいきません。 やりたいことは、 abc  def aaa dd を abc,def aaa,dd のようにしたいのです。 :%s/ */,/g :%s/\s*/,/g はうまくいきませんでした。 どうぞよろしくお願いします。

  • sedコマンド置換について(マッチした数字を演算に再利用したい)

    こんにちは Linuxのsedコマンドについて質問させてください。 軽く検索をかけたりUNIXのコマンド本を調べましたが、わかりませんでした。 例えば正規表現でマッチした数字を2倍して返す処理をする場合 perlではhogeという文字列に対して  hoge=~s/(\d+)/$1*2/g; このような処理で実現できると思います。 このように今マッチしたものに対して何か処理を行って置換するということは sedコマンド、もしくはそれを使ったシェルスクリプトで処理することは可能でしょうか? sedの方が処理速度が速いので、こちらを使いたいのです。

  • viでの置換

    if($b =~ /(?=A?B{1,2})/){  print "b\n"; } この文章中で?=の=だけ!に置換したいのですが :%s/?=\&=/!/g だとE486: パターンはみつかりませんでした: ?=\&=と表示されました. ですが, :%s/=A\&=/!/g だとうまくいきました.?がエスケープされてないから(素人目線ですが)と思い :%s/\?=\&=/!/g とすると E64:\? の後になにもありません E476: 無効なコマンドです 続けるにはENTERを押すかコマンドを入力してください となりました.なぜ:%s/?=\&=/!/gではダメなのでしょうか?

  • 文字列の置換

    JAVAであるファイル内の文字列を置換するプログラム(日本語)を作りたいのですが、 ・ ・ String h_s; FileReader h_fr = new FileReader(in_filename); BufferedReader h_br = new BufferedReader(h_fr); while(true){  h_s = h_br.readLine();  if (h_s == null){   break;  }  ●文字列を置換するプログラム● } ・ ・ ここの●文字列を置換するプログラム●にあたる適当な関数って何かありますか? たとえばperlでいうと↓みたいなものなのですが・・・ s/置換前文字/置換後文字/g よろしくお願いします。

    • ベストアンサー
    • Java