- ベストアンサー
テキストファイルの編集
以前にもperlでのプログラミングについて質問させて頂いた者です。 前回と被る所があるのですがちょっと複雑で、私の手に負えないので質問させて頂きます。 よろしくお願いします。 データファイルAからデータを取得して、テキストファイルBの内容の一部を取得したデータで書き換えるという操作を行うプログラムを作りたいです。 具体的には、 データファイルA[A.txt]の中身 100 111 200 222 300 333 400 444 テキストファイルB[B.txt] 1st step 文字列1 start="123" end="134" 2nd step 文字列2 start="234" end="245" 3rd step 文字列3 start="345" end="356" 4th step 文字列4 start="456" end="467" という二つのファイルを読み込み、B.txtの""で囲まれた部分を、A.txtで取得したデータで置き換えるような操作を行いたいのですが・・。 A.txtの一行目で取得したデータをB.txtの1stステップの始まりと終わりの値として置き換え、それを二行目は2ndと同様の操作を行うようなプログラムを作りたいです。 もともとこのような操作を行いたくて前回のような質問をし、それを参考にし今日まで考えましたがやはり難しく、また頼りたいと思います。。 どうかよろしくお願いします。
- みんなの回答 (3)
- 専門家の回答
質問者が選んだベストアンサー
再び、お答えしますねん。 if($data_b[$i] =~ s/".+" end=".+"/"$start[$ii]" end="$end[$ii]"/){$ii++}; の正規表現は、B.txt内にダブルクォートで囲まれた部分が2箇所しかないことを前提に書いたもので "→ダブルクォートで始まり、 .→何か一文字が +→1個以上続き、 "→ダブルクォートで閉じ →半角スペースが続き end"→endとダブルクォートが続き .→何か一文字が +→1個以上続き、 "→ダブルクォートで閉じる文字列。 ということで、 文字列1 start="123" end="134" を例に考えると "123" end="134" にマッチするので 123→$start[$ii] 134→$end[$ii] で置き換えてます。 ただし、他にダブルクォートで挟まれた場所が増えるとNGになるわけで… <src="文字列1" start="123" end="134"/ 1列目> だと 最初のダブルクォートからあてはまってしまい 文字列1" start="123→$start[$ii] 134 →$end[$ii] で置き換えてしまいます。 なので、条件を下記に返るとOKですね。 if($data_b[$i] =~ s/"[^"]+" end="[^"]+"/"$start[$ii]" end="$end[$ii]"/){$ii++}; 今度は、ダブルクォート内にダブルクォート以外の一文字が1個以上続く場合に条件を変えました。 これで、今まで "文字列1" start="123" がstart部としてヒットしていたのが "123"(ダブルクォートを含まない文字列)になるので期待したとおりになりますよ。 他の書き方だと、置換したいところが数字しか来ないのであれば if($data_b[$i] =~ s/"\d+" end="\d+"/"$start[$ii]" end="$end[$ii]"/){$ii++}; こんなんでもOKですね。
その他の回答 (2)
- sakusaker7
- ベストアンサー率62% (800/1280)
書いちゃったので参考までに。 use strict; use warnings; my $datafile = 'a.txt'; my $targetfile = 'b.txt'; open my $ifh, '<', $datafile or die "can't open $datafile ($!)"; my @data; while (my $line = <$ifh>) { chomp $line; push @data, [split q{ }, $line]; } close $ifh; open my $tfh, '<', $targetfile or die "can't open $targetfile ($!)"; my $tmpname = $targetfile . '.tmp'; open my $tmp, '>', $tmpname or die "can't open work file ($!)"; while (my $line = <$tfh>) { if (index($line, '"') >= 0) { my $datum_ref = shift @data; $line =~ s{(".+?)" (\s+ end=) "(.+?)"}{"$datum_ref->[0]"$2"$datum_ref->[1]"}x; } print {$tmp} $line; } close $tfh; close $tmp; rename $targetfile, $targetfile . '.bak'; rename $tmpname, $targetfile; 修正前のファイルも残したほうがいいよね、つーことで。 本当はもうちょっとこったやり方をしないといけないのだけど手抜き。 #.+ → .+? だけのつもりだったのに何をやってるんだ
お礼
サンプルプログラムありがとうございます。 参考に致します。 丁寧な回答ありがとうございました。
- sample_
- ベストアンサー率76% (20/26)
前回のコードをちょいちょい改造して回答します。 -------------------------------------------- use strict; use warnings; open (my $fh1, "<", "A.txt") or die "$!"; chomp(my @data_a = <$fh1>); close $fh1; my (@start, @end); for(my $i=0; $i<@data_a; $i++){ ($start[$i], $end[$i]) = split(/ /, $data_a[$i]); } open (my $fh2, "+<", "B.txt") or die "$!"; my @data_b = <$fh2>; for(my $i=0, my $ii=0; $i<@data_b; $i++){ if($data_b[$i] =~ s/".+" end=".+"/"$start[$ii]" end="$end[$ii]"/){$ii++}; } seek $fh2, 0, 0; truncate $fh2, 0; print $fh2 @data_b; close $fh2; -------------------------------------------- ざっくりした流れは A.txtをopenして、データを読み込みます。 この時chompで改行コードを落とします。(残しておくとB.txtに書き込んだ際に改行されちゃうので) closeして A.txtの内容をstart用とend用にsplit関数で 半角をセパレータとして分割しておきます。 読書きモードでB.txtをopenして、データを読取(今回はchompしない) 後は、B.txtを格納した配列をfor文でぐるぐる回して 置換作業を行います。 A.txtとB.txtの行数が違うので$iと$iiの別々の変数を用意して 取り出し置換を行っています。 後は、seek関数でB.txt内のカーソル位置を先頭に戻し truncate関数で0バイト(消去)してあげ 改めて書き直せば完成! こんなんでいかが(^^;)
補足
サンプルソースプログラム非常に参考になりました。 ありがとうございます。 if($data_b[$i] =~ s/".+" end=".+"/"$start[$ii]" end="$end[$ii]"/){$ii++}; の行についての追加での質問なのですが、これはend=を探し、その前後の""で囲まれた部分をA.txtのデータで置き換えるという動作を行っているのでしょうか? 私が具体例として上げたB.txtではこの動作でやりたいことが出来るのですが、end=の行に置き換えたいデータ部の他に""で囲まれた文字列があった場合、上手く行きませんでした。 start=後の""をA.txtのスタートデータで置き換え、 end=後の""をA.txtのエンドデータで置き換えるようなプログラムにすればこれは解決するのでしょうか? 具体的にやりたいことは、 <src="文字列1" start="123" end="134"/ 1列目> からプログラムを通して、 <src="文字列1" start="100" end="111"/ 1列目> というように置き換えを行いたいです。
お礼
疑問に思っていた点が解決致しました。 丁寧な回答本当にありがとうございます。 前回の質問から今回最後まで付き合っていただいて本当に感謝します。