CGI-BBS > CGI > Perl > 新しい順に表示したいのですが・・・


カレッヂ
カレッヂ


質問者 futa  投稿日 2/8(木) 20:48:33
このPerl質問ページの一番最初に質問をしたものです。
あれから、いわゆるBBSみたいなのを作れるようになったのですが
新しい順に記事を表示する方法が浮かばなくて悩んでいます。

普通?、BBSを作るとき、
1.HTML内のフォームからデータを送る。
2.送られたデータをデータファイルに書き込む。
3.データファイルを一行ずつ読み込んでHTMLで出力する。
と言った流れだと思うのですが、
そのデータを、投稿された日時の新しい順に上から表示したいのですが
良い方法が浮かびません。
データファイルとテンポラリーファイルと2つのファイルを作ればできるのですが、
できるだけ、データを保管するファイルは1つだけにしたいのです。
何か良い方法はないでしょうか?

はっきり言って、Perlの質問というよりは
アルゴリズムの問題なのかもしれませんが・・・
どなたか教えていただけませんか?
回答者 しあわせのツボ  [削除]  投稿日 2/8(木) 23:31:04
レスキューさんが使われているorいた方法をいくつか。
・ファイルの末尾に追加していき、読み込んでからひっくり返して表示。
・ファイルを配列に読み込み、先頭に追加して書き戻す。
・テンポラリファイルに新規発言とログを書き込み、リネームする。
・1発言1ファイルとし、ファイル名リストを配列に入れて末尾から読みだしていく。
・DBMを使い何らかの形で降順ソートさせる。
回答者 まさ  [削除]  投稿日 2/9(金) 01:12:13
そうですね。アルゴリズムですね。
ちなみにテンポラリーファイルなんて必要ないです。

データを書き込む時にどんな方法で書き込んでいるのでしょうか?
pushですか?

$value = "………\n";
unshift(@new,$value);

書き込み処理をこうしておけば、自動的に新しいもの順になります。
回答者  [削除]  投稿日 2/9(金) 10:14:46
書き込み処理でファイルを開くときに
以下のような記述にするのはどうでしょうか。

if (!open(OUT,"+<$file")) { &error('システムエラー','ファイルを書きこみオープンできません.'); }
seek(OUT, 0, 0);
質問者 futa  [削除]  投稿日 2/9(金) 18:27:36
お返事ありがとうございます。
少し長くなります。

>しあわせのツボさんへ
>・ファイルの末尾に追加していき、読み込んでからひっくり返して表示。
「ひっくり返す方法」がわかりません。
>・ファイルを配列に読み込み、先頭に追加して書き戻す。
「先頭に追加する方法」がわかりません。
>・テンポラリファイルに新規発言とログを書き込み、リネームする。
これは私も考えた方法ですが、テンポラリーファイルは作りたくないのです。
美しさ?の問題から。
ファイルはできるだけ少なくしたいのです。
>・1発言1ファイルとし、ファイル名リストを配列に入れて末尾から読みだしていく。
上と同じ理由で・・・
>・DBMを使い何らかの形で降順ソートさせる。
DBMとはデータベース???のことですか?
SQLとか・・・
そこまでは時間がないですし、それはまた今度ということで。

>まささんへ
unshiftを調べると
配列の最初に追加するみたいなことが書いてあったんですけど
私の場合、データファイルは一行ごとに
「日にち 名前 アドレス コメント」(#)
のようにしているので違うような気がするのですが。
ひょっとして、一度データファイルを普通に?書き込んでいき
(#)を配列の1つの要素して、全データを配列に入れて、
追加書き込みモードでなくデータファイルに改めて書き込んでいくのですか?
間違ってます?

>羊さんへ
seekも知らなかったので調べました。
seek(HUNDLER,0,0)とすると
最初から0バイト目に書き込み位置を変更するという意味ですよね?
それで書き込み位置変更が成功したら1、失敗したら0を返すんですよね?
それをふまえて、自分なりに書いてみました。
open(DATA,"+<data.txt")|| die("ファイルを開けません");
    if(seek(DATA,0,0) == 1){
        print DATA "@data\n";
    }
close(DATA);
でも、このようにしたら・・・
データファイルの先頭行の最初が何文字か消えてしまいました。
あと、新しくデータを書き込むと、直前データが消えてしまいました。
う〜ん、なぜ?って感じです。
もう少しいろいろいじってみますが、
私のseekの理解の誤りや、原因がおわかりなら教えてください。

よろしくお願いします。

回答者 しあわせのツボ  [削除]  投稿日 2/9(金) 20:06:05
データを配列に読み込む方法はわかりますよね。
@array に読み込んだとして、
@array = reverse(@array);
とするとひっくり返せます(ただし、けっこうなサーバ負荷があります)。
unshift (@array, $new);
とすると、配列の先頭に新しい書き込みが来ます。そのまま@arrayをファイルに戻せば先頭に書き込んだことになります。
@array = split(/#/,<FILE>);
とすれば、<FILE>の内容を#で分割して順番に@arrayに格納してくれます。
これらの方式だと、ログを一旦全てメモリに読むことになるので、ログ量に応じたサーバ負荷がかかります。

テンポラリファイルは常に存在する訳ではないので問題ないと思いますが…。
実際、最近のレスキューさんのスクリプトは、ファイルロック処理のためにテンポラリファイルを使っています。

DBMはUNIX系OSで使われる簡易データベースです。一度接続すれば連想配列のような感覚で使えるので、場合によっては面白い選択肢です。


書き込みでopenしseekを使うと、そこに割り込むのでなく上書きしていきます(ビデオテープの上書き録画みたいなイメージです)。よって、先頭に追記していくという目的には使えません。
回答者 しあわせのツボ  [削除]  投稿日 2/9(金) 21:21:12
配列を使う方法についてもう少し。
$var = pop(@array);
とすると、@arrayの最後の要素が$varに入ります。ただし、読み込んだ分は@arrayから消えてしまうので注意して下さい。
消えてしまうのが嫌なら、カウンタを用意して
for ($i = 0; $i <= $#array; $i++) {
$count = $#array - $i;
$var = $array[$count];
# 何らかの処理
}
とすれば、@arrayの内容を保持したまま後ろから処理できます。
これらの方法なら、reverseを使うよりはサーバ負荷が少ないと思います。

また何か思いついたら書きます。
回答者  [削除]  投稿日 2/9(金) 22:27:18
1行目を読み込んで上書きして、
2行目を読み込んでさっき読み込んだ1行目を2行目に上書きして
……ってダメですね。
回答者 さくら  [削除]  投稿日 2/10(土) 00:54:47
方法は色々有りますが簡単な方法としてはこんな感じです。

1エントリーを1行にレコードする事にします。
日付、タイトル、名前、メール、URL、コメント、をレコードするとします。
各データのデリミタ(区切り文字)はタブにします。
それでは、やって見ましょう

undef @lines;  #配列を初期化
push @lines, (join "\t", $date,$title,$name,$mail,$url,"\n");

これは、join によって、各データをタブで区切った文字列ができます。
それを push によって @lines の先頭の要素に格納します。
この時、@lines の要素は、今書きこみのあった 1エントリーだけです。
この後ろに、データファイルのデータを追加していきます。
そうすると、@lines は 新しいデータから並びますね。
やって見ましょう

open DATA, "+<data.log" or die;     #データファイルを読み書き両用でオープン
while (<DATA>) { push @lines, $_ }   #データファイルを全て@lines に読込んだ
seek (DATA,0,0) or die;     #書きこみの為ポインタをデータファイルの先頭にセット
foreach(@lines) { print DATA $_ }    
#これでデータファイルも新しいエントリーから、書きこまれた。
truncate(DATA,(tell(DATA));   #データファイルの末尾にゴミが入らないようにする。
close DATA;

ここで、@lines のデータはまだ開放されていない。まだメモリ上にある
だから表示してしまおう
foreach(@lines) {
    ($date,$title,$name,$mail,$url) = split /\t/;
    #データをタブでスプリットしてリスト代入する。
  ここで、HTML タグを含めてデータを表示
}

この例は非常に簡単なものだが、これでも完全に機能する。
ただ、ファイルロックの機構をどこかに入れなければいけないだろう
一番簡単なのは flock を使用する事だが、ロック用フラグを立てるのが良いでしょう
(ファイルを作成、削除して書きこみ中である事を教えてあげること)

最初の join を使って文字列を作る方法は join は使わずに変数展開演算子を使っても良い
(ダブルクォートで全てのデータを括る方法の事。)

データファイルを読込む方法は
@lines = <DATA>;
これでも良いが、私がベンチマークテストをした結果では、
while (<DATA>) { push @lines, $_ } の方が高速に動作する。(なぜかは知らない)
seek や truncate の事は質問には無いが、書き込みの時は必ず入れるようにしたほうが良い
表示用のサブルーチンを書くと思いますが、書きこみ時に読込んだ配列を開放して
表示用にまた読込むのはもったいない気がするので書いてみました。

長々と余分な事まで書いてしまった気もするが、参考になれば幸いです。






  
回答者  [削除]  投稿日 2/10(土) 12:28:35
http://www.wakusei.ne.jp/twn/wwwlng.cgi?print+199804/98040066.txt
こんなのがありました。
質問者 futa  [削除]  投稿日 2/13(火) 05:48:26
>しあわせのツボさん
たしかにreverseもunshiftもサーバー負荷が多そうですね。
レスキューのminibbsを使っているので、
中を覗いたことがあるのですが、やっぱり長くなると
人の書いたスクリプトと言うのは読みづらく、
テンポラリファイルを使っていることわかりませんでした。
DBMの説明ありがとうございました。
データベース系の勉強をするゼミに入ったので
勉強することがあるかもしれません。
>書き込みでopenしseekを使うと、そこに割り込むのでなく上書きしていきます
納得です、それで消えてしまったのですね。

>さくらさん
undefとtruncateは知らなかったので勉強になりました。
そして大変ていねいなお応えに感謝します。
結局?しあわせのツボさんのように・・・
(いい言葉がないのでこんな言葉になってしまいましたが
気分を害されたなら申し訳ありません。)
全データを1つの配列に入れるのですね。
やはりこれはサーバー負荷の点が気になります。
flockはよくスクリプトで見るのですが、今までなんだかわかりませんでした。
手元の資料には書いてなかったので。
一応機能?はさくらさんの説明でわかったつもりですが、調べてみます。

>羊さん
(しあわせのツボさん、さくらさんには申し訳ないのですが)
結局、ここ(とほほのwww入門ですね)に書いてあった
open(F,"tail -r file |");を採用させて頂きました。
一行足すだけだったのですぐうまく動きました。
しかし・・・tailコマンドはわかるのですが「|」って何でしょう?
それと検索機能があるとは言え、よく見つけられましたね。
助かりました。

お返事が遅れてしまってすいません。
私は学生なのですが、バイトやら課題やらで忙しくて
・・・今日も今まで徹夜で課題をやってました、はあ。
ちなみに課題はJavaでした。

みなさんありがとうございました。
質問者 futa  [削除]  投稿日 2/13(火) 19:02:05
やっぱり疲れていたため、ボケていたのかもしれません。

「|」って、出力を受け渡す「パイプ」ですね。
って、ことは「tail -r file」を、
「$_」に格納しているのでしょうか?
回答者 しあわせのツボ  [削除]  投稿日 2/13(火) 19:56:00
この場合、tailコマンドを経由してファイルハンドルに入力されます。
逆に"| コマンド"とすると、perlからの出力をコマンドに渡すこともできます。
tailは「ファイルの末尾(tail)を表示」のコマンド、-rオプションは「逆順(reverse)で出力」なので、perlに入力されるものを見ると、あたかもファイルに逆順で記録されているかのように見えます。

昔、これを試みたことがあるのですが、プロバイダのサーバがtailコマンドの-rオプションをサポートしておらず、あきらめた記憶があります。
質問者 futa  [削除]  投稿日 2/22(木) 11:56:02
>しあわせのツボさんへ
「tail」コマンドは知っていましたが(UNIX使っているので)
openの中で使えることを知らなかったのです、当時。
今年からPerlを勉強し始めたので・・・
ありがとうございました。

このページは終了したので返信(回答)は書きこめません

Web裏技