- ベストアンサー
ログファイルの指定行に書込み
open(IN,"$log") || &error("Open Error"); @data = <IN>; close(IN); while (100 <= @data) { pop(@data); } open(OUT,">$log") || &error("Write Error"); print OUT "$in{'id'}<>$in{'comment'}\n"; print OUT @data; close(OUT); ログにはID、時間、コメントが登録されています。 送信データの中に、ログに登録済みのIDがある場合には、そのIDのある行のみを書き換えたいのですが、方法がどうしてもわかりません。 $logに記録されるIDの順番は以下のようにランダムです。IDに登録される文字列は1からの数値のみです。 52<>コメント 120<>コメント 35<>コメント 8<>コメント 2<>コメント 19<>コメント 85<>コメント よろしくお願いいたします。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
多分これが最後かな? while (100 <= @data) { pop(@data); } unshift(@data,"$in{'id'}<>$in{'comment'}\n"); の処理は、 my $remain_count = 100; my $remain_count = $remain_count - 1; splice(@data, $remain_count - scalar @data) if (@data > $remain_count); unshift(@data,"$in{'id'}<>$in{'comment'}\n"); と同じです。 上も下も、まず配列の数を指定数-1まで、減らし、次の行でデータを追加して、指定数に調整されています。処理の結果はどちらも同じです。 どちらか好きな方を使えばいいと思います。 後、データの数など、後で変える可能性のある数字は変数にして、なるべくソースの初めの方にまとめて書いた方がいいです。後で編集が楽ですし。(つまり、上のソースを使う場合、100を$remain_countに変え、$remain_count=100;をソースの始めの方に記述する。) 配布されているCGIは設定がすべて初めの方にきていますよね?あれと同じです。
その他の回答 (6)
- W_H
- ベストアンサー率47% (21/44)
一応#4の質問について #2の最後にやり方は書いてますが、elseの方には、上書きしない場合の処理(つまり何もしない)が入るので、ループを潜り抜けた『後』に処理が必要になります。 楽な手として、ループの前に[$check=0;]というチェック用の変数を作っておき、#値を代入、のすぐ次の行に、値を代入したという変数、[$check=1;]を書きます。else{}の方に書いたら駄目です。 これで、ループ終了時に1となっていれば、データが上書きされたため新規データを追加する必要がないので、ループを抜けた後 if($check !=1){#上書きしていない unshift(@data,"$in{'id'}<>$in{'comment'}\n"); }else{} このような処理をくわえれば、配列にデータを追加できます。 これは先頭に追加していますが、末尾に追加したい場合は、unshiftの代わりにpushを使えばできます。 ちなみに、@linesですが、必要ないです。 普通に実行したら、何も上書きされません。(@linesには@dataのデータがそのままpushされている。)一応@dataの中身を上書きしているので、@lineが絡む処理2行は不必要で、ファイル出力のprintのところを、@dataに書き換えれば、正常に動くと思います。 何でわざわざ@dataの中身を上書きしたかというと、fx2さんの考え方は、ノートの左に書かれた英文を右に写しつつ、ある英文はちょっと変えて書くという作業になります。 ぼくの場合、それは面倒なので、左のページの英文を見て、ある英文だけ消しゴムで消し、書き換えるという作業にしました。 実際やらなくても分かりますが、後者のほうが楽なので、ぼくのサンプルソースは後者を選択しています。 一応ソースはそろったので、後はがんばってください。
お礼
わかりやすくご説明してくださり有難うございます。以下のように書きましたところ、おかげさまで成功しました。あと、popのところが間違っていないでしょうか。 open(IN,"$log") || &error("Open Error: $log"); my @data = <IN>; foreach(@data){#配列を回す my($fid,$comment)=split(/<>/,$_);#データ分割 if($fid==$in{'id'}){#ID比較 $_="$in{'id'}<>$in{'comment'}\n";#値を代入 $check=1; } } if($check !=1){#上書きしていない while (100 <= @data) { pop(@data); } unshift(@data,"$in{'id'}<>$in{'comment'}\n"); } close(IN); open(OUT,">$log") || &error("Write Error: $log"); print OUT @data; close(OUT);
補足
popのところを以下に変更してみました。よろしいでしょうか。 my $remain_count = 5; my $remain_count = $remain_count - 1; splice(@data, $remain_count - scalar @data) if (@data > $remain_count);
- sakusaker7
- ベストアンサー率62% (800/1280)
どのようなスタイルで記述するかということについては、自分なりに 「こうすればいいんじゃないの?」というのはありますが、まあそこはそれ 押し付ける気はないです。 #ファイルオープンが成功したかどうかのチェックとかは忘れないで欲しいとは思いますが ただ、新しい使い方にも明確なメリットというのがありますので、 Perl4時代の書き方が単純でいいというのも一度考えてみてはどうかと思います。 たとえば、ファイルをオープンするときに IN/OUT のような大文字の シンボルを使わないで変数を使うのを取り上げてみると、サブルーチンと このファイルハンドルをやり取りするのに、大文字のシンボルを使ったときと 使いやすさがぜんぜん違います。 #まあ古いバージョンだとできないという問題はありますが ということを挙げていくときりがないのとオフトピなのでやめといて > 、このスクリプトだと重複する可能性があるということでしょうか? すみません。 ここの仕様でインデントがつかないこともあって読み違えてました。 重複はしないですね。 #4のお礼にある質問はわたしが答えない方がいいかな?(笑
お礼
いろいろとご親切にご指導くださり感謝いたします。今まで人のソースをみながら自己流で何となく覚えてきたような感じなのですが、やはり本格的に勉強しなくてはと改めて思った次第です。ご紹介いただいた本も早速注文させて頂きました。ありがとうございます。
補足
すみません、ANo.6への「この回答への補足」で間違えました。my $remain_count = $remain_count - 1;は削除しまして、unshiftの下の行にspliceを追加しました。
- W_H
- ベストアンサー率47% (21/44)
sakusaker7さんへ。 一応ぼくのここでの書き方は、構造が見て分かりやすい(基礎的な関数を使う)、というのを重視しています。多分過去の回答とかを見たら、ものすごく突っ込みたくなると思います。なので、文法も一応チェックに引っかからないレベルとなっています。(ループ外ではmyなどを使わないなどしているため。) 後、参考書とforeach使用時の値代入方法を教えていただき、ありがとうございます。後々役立てさせてもらいます。 foreachに関しては、配列の時によく使っていたのですが、今回の使い方を知らなかったので、ここしばらくまともに使ってませんでした。個人的に好きな(楽な)関数だったので、本当にありがとうございます。 お礼だけだと無意味となるので、早速foreachを使ったサンプルソースを…… foreach(@data){#配列を回す my($fid,$comment)=split(/<>/,$_);#データ分割 if($fid==$in{'id'}){#ID比較 $_="$in{'id'}<>$in{'comment'}\n";#値を代入 }else{} } 前回同様@dataと$in{~}は省略しています。一応宣言していない$nを使うforを使っていないのでuse strictにも(配列とハッシュの宣言さえすれば)引っかからないようになってます。 あくまでこれは基本的考え方なので、他の改造はがんばってください。
お礼
ご回答有難うございます。また、ご丁寧に説明も入れてくださり大変助かりました。 @lines=(); open(IN,"$log") || &error("Open Error: $log"); @data = <IN>; foreach(@data){#配列を回す my($fid,$comment)=split(/<>/,$_);#データ分割 if($fid==$in{'id'}){#ID比較 $_="$in{'id'}<>$in{'comment'}\n";#値を代入 }else{ } push(@lines,$_); } close(IN); open(OUT,">$log") || &error("Write Error: $log"); print OUT @lines; close(OUT); このような感じでよろしいのでしょうか。 $in{id}に等しいidを持った行がなかった場合に、新規で書き出すには、 }else{ この部分にどのようにかけばいいのか、どうしてもわかりませんでした。 } お手数をおかけいたしますが、よろしくお願いいたします。
- sakusaker7
- ベストアンサー率62% (800/1280)
#1です。 #1で示したスクリプトは実行して確かめてますので、出力結果が期待したものと違う というのならわかりますが、実行もできないエラーというのはなんなんでしょう。 ひょっとして使っているPerlのバージョンがとっても古かったりしますか? とりあえず理解できる範囲で書くというのでもいいと思うので、それを念頭に考えてみると > if ($noid !~ /1/) { $noid は 0 か 1かのどちらかの数値ですので、わざわざ正規表現という 「重い」処理を持ち出さずに $noid != 1 というように数値としてみてやる方が良いでしょう。 !~ /1/ という正規表現と比較すると意味が違いますし。 #ぶっちゃけ 200 と比較しても成功してしまいます また、ログの切り詰めのために ・ファイルを読み出しのためにオープン ・書き戻しのためにオープン ・再度読み出しのためにオープン ・再度書き出しのためにオープン というのはいかにも無駄です。 それと、配列の要素数をある数以下にするのにpopを何回も呼ぶのは無駄です。 #1の回答に書いたように、spliceを使えば一回の関数呼び出しで必要な要素数に 丸めることが可能です。 一つ確認なんですが#1のお礼にあるスクリプトだと、 あるidを持った行が2行以上存在する可能性がありますけどそれは良いのでしょうか? idをチェックして内容を書き換えるくらいだから、書き換えた場合は $in{id}と$in{comment}の内容をファイルに書き出すことはないと思ったんですが。 ついでに。 > こんな感じで、配列をforとか配列をカウントできるようにします。 Cスタイルの for ではなく、foreach (forと書いても一緒なんですが)スタイルを使えば、 foreach (@data) { ... } と書けば、配列の要素数を調べる必要もありません。 Perlのバージョンが古い(5.004とかその辺?)もので、なおかつ配列が巨大な ものだとメモリをバカ食いしたりしますが、その挙動は修正されています。 添え字を使うこと自体に意味があるのでなければ、foreach。です。 念のため書き添えておきますが、 foreach (@data) { $_ *= 2; } のようにデフォルト変数(もしくは自分で宣言した変数)に対して 書込み操作をすれば、配列の内容の方もちゃんと書き換わります。 > $#配列名で配列の数(配列の数え方なので、配列の数-1) 微妙に違います。 特殊変数 $[ をいじることはご法度なので、これでもまあ間違いではないのですが 配列の要素数を求めるには $#配列変数名 ではなく、スカラーコンテキストで 配列を評価すればOKです。 use strict; しましょうとかほかにも書きたいことはありますが とりあえず自粛しておきます。 時間的な余裕があったらこの辺の本を読んでみるのをお勧めします。 Perlプログラミング救命病棟: 本: ピーター・J・スコット,トップスタジオ,伊藤 直也 http://amazon.jp/dp/4798109401 CGIなら、CGI.pmとかCGI_Lite.pmを使った方が楽できると思うのですが わかりやすい日本語で書かれた入門記事はあるんですかねえ。
お礼
わかりやすくご説明してくださり有難うございます。以前に、超初心者用の入門書で勉強を始めたことがあったのですが、意味がわからずに挫折したままになっていました。Perlの改造は、意味はあまりわかっていないのですが、皆さんのソースをみながら何となく見よう見まねで少しずつ覚えている程度の知識しかありません。 調べてみましたところ、perlは5.8.8が利用されているようです。 多分、私のスクリプトの書き方がメチャクチャなため、use strict;を使うとエラーが生じたのかもしれません。 今は時間ぎりぎりで生活しているような感じですが、余裕がでてきましたら、ご紹介いただいた参考本で勉強を始めてみようと思います。
補足
>> 一つ確認なんですが#1のお礼にあるスクリプトだと、 >> あるidを持った行が2行以上存在する可能性がありますけどそれは良いのでしょうか? もうしわけありません、未熟なために自分が書いている内容もあまり理解できていません。登録を行う際に、idが重複されないように作りたかったのですが、このスクリプトだと重複する可能性があるということでしょうか?
- W_H
- ベストアンサー率47% (21/44)
いやぁ、データの区切りを同じ(<>)にしてる人いるんですね。なんだか親近感を覚えてます。(笑 それはさておき、簡単にこういう場合の基礎的なの載せときます。ファイルへの読み書きは省略して、@dataにファイルデータが。$in{~}に例の新しいデータが入っているとします。 for($n=0;$n<=$#data;++$n){#配列の数だけループ my($fid,$coment)=split(/<>/,$data[$n]);#データ分割 if($fid==$in{'id'}){#ID比較 $data[$n]="$in{'id'}<>$in{'comment'}\n";#値を代入 }else{} } こんな感じで、配列をforとか配列をカウントできるようにします。whileでもできるけど、わざわざカウントしないといけないので面倒です。 $#配列名で配列の数(配列の数え方なので、配列の数-1)が取得できるので、それを使って、上手くループを回します。 で、そのたびにデータを分割(split)し、ID値を比較し、同じ場合は、その変更する配列のデータ($data[$n])を、新しいデータに書き換え、それ以外のときは、何もしないというプログラムを書けば、自然と@dataの中身は更新されます。 後応用は、それが実行されなかったら新しく追加するなどの時に、ifの中に、実行されたことを表す変数と値を代入し、例えば変数の中身が1の場合は、配列に新規データを追加する、などの処置が入ってくると思います。 文法チェックをしてないので、間違っていたらごめんなさい。
- sakusaker7
- ベストアンサー率62% (800/1280)
最大で100行ログを残すけれども、そのとき $in{id}に等しいidを持った行があった場合に それは更新して書き出したいということですか? であるならこんなところでどうでしょうか use strict; my $logfile = 'sample.log'; my $remain_count = 100; #for debug my %in = (id => 35, comment => "new comment"); open my $ifh, '<', $logfile or &error("Open for read Error $!"); my @data = <$ifh>; close $ifh; splice(@data, $remain_count - scalar @data) if (@data > $remain_count); @data = map {$in{id} == (split /<>/, $_)[0] ? "$in{id}<>$in{comment}\n" : $_} @data; open my $ofh,'>', $logfile or &error("Open for write Error $!"); print $ofh @data; close $ofh;
お礼
ご回答有難うございます。設置してみましたところ、以下のエラーが表示されてしまいました。素人なみの私の知識では、ソースの意味も、エラー内容もよくわかりませんでした。申し訳ありません。 Script Error The script did not produce proper HTTP headers. Please see the error log to see the detail of the errors. Depending on the server configuration, you can also run thisscript under CGIWrap debugging. Usually, either rename or linkthe script temporarily to a file which ends with .cgidextension, or add a AddHandler cgi-script-debug .cgiline to your .htaccess file. ------------------------------- 昨日からいろいろなCGIやら、サイトを参考に自分なりに工夫して組み立ててみましたところ、以下スクリプトで一応、思い通りの動作をしました。ですが素人が適当にやってたまたま出来た方法ですので、書き方がメチャクチャな気がして、バグもありそうで、心配です。 以下内容のスクリプトでも大丈夫でしょうか?もしこのように修正した方がよいという箇所がございましたら、お教え頂ければ幸いです。よろしくお願いいたします。 @lines=(); open(IN,"$log") || &error("Open Error: $log"); while (<IN>) { ($id,$comment) = split(/<>/); if ($in{'id'} == $id) { $noid=1; $_ = "$in{'id'}<>$in{'comment'}\n"; } push(@lines,$_); } close(IN); open(OUT,">$log") || &error("Write Error: $log"); print OUT @lines; close(OUT); if ($noid !~ /1/) { open(IN,"$log") || &error("Open Error: $log"); @data = <IN>; close(IN); while (100 <= @data) { pop(@data); } open(OUT,">$log") || &error("Write Error: $log"); print OUT "$in{'id'}<>$in{'comment'}\n"; print OUT @data; close(OUT); }
お礼
最後までお付き合いくださり有難うございます。いろいろと大変勉強になりました。