プロが教える店舗&オフィスのセキュリティ対策術

お世話になります。ご指南いただければ幸いです。
Perl初心者です。

自前のサイトで会員ログファイルから各ユーザーの最終ログインを抽出したいと思っています。
ログファイルを作るところまでは何とかなったのですが、抽出する方法が見当もつきません。
(ログファイルではない閲覧用のファイルが作れればありがたいですが抽出するロジックがわかりません。)

現在は以下のようなログファイルができています。

ユーザーA,2016-08-28 12:44:14
ユーザーA,2016-08-25 12:44:55
ユーザーB,2016-08-28 12:45:47
ユーザーC,2016-08-27 12:45:54
ユーザーC,2016-08-28 12:45:54
ユーザーB,2016-08-28 12:45:54
ユーザーA,2016-08-28 12:45:54

それぞれユーザーA-Cさんの最終ログイン行を抽出したいのですが、
ご教授いただけませんでしょうか。


ログファイル上の時刻は以下のように取得しています。
my ($sec, $min, $hour, $day, $mon, $year) = localtime(time);
my $now = sprintf('%04d-%02d-%02d %02d:%02d:%02d', $year + 1900, $mon + 1, $day, $hour, $min, $sec);

以上です。よろしくお願いします。

質問者からの補足コメント

  • うーん・・・

    ありがとうございます。日々学習を続けていこうと思います。

    頂いた回答をもとにKENTWEBさんのWEBPATIOを改造しています。
    メイン実行ファイルの中にサブルーチンで動かし、実行結果を外部ファイルに書き出そうとしています。
    ログをusrlog.cgiに保存し、そのログから抽出したものをusrlog2.cgiに保存しようとしていますが、うまくいきません。

      補足日時:2016/08/28 23:04
  • うーん・・・

    sub usr_logrec
    {
    my ($sec, $min, $hour, $day, $mon, $year) = localtime(time);
    my $now = sprintf('%04d-%02d-%02d %02d:%02d:%02d', $year + 1900, $mon + 1, $day, $hour, $min, $sec);

    open(FH,">> $cf{logdir}/usrlog.cgi") or error("open err: usrlog.cgi"); #追加モード
    print FH "$au{name},$now,$in{id},$au{rank}\n"; #書き込み
    close(FH); #ログ保管完了

      補足日時:2016/08/28 23:04
  • my %users = open(FH,"$cf{logdir}/usrlog.cgi") or error("open err: usrlog.cgi");
    while(<>){
    my $user = split(',',$_); #カンマで分割した最初のフィールドを取得
    $users{$user} = $. #その行番号を記憶
    }
    #全ユーザー名と行番号を印字
    open(FH,"> $cf{logdir}/usrlog2.cgi") or error("open err: usrlog2.cgi");
    foreach my $user (keys %users){
    print FH "user_name=%s line_no=%d\n",$user,$users{$user};
    }
    close(FH);
    }

      補足日時:2016/08/28 23:05
  • うーん・・・

    というように処理を行っています。

    もしアドバイスいただけるなら大変ありがたいですm(__)m

      補足日時:2016/08/28 23:05
  • うーん・・・

    コメントありがとうございます。
    ログをusrlog.cgiに保存はできており、usrlog2.cgiにもアクセスができております。
    usrlog2.cgiに結果を書き込むのがおかしい様で、usrlog2.cgi内には結果というよりも
    プレーンテキストで 
    user_name=%s line_no=%d
    だけが書き込まれます。原因がファイルの扱いなのか、変数の扱いなのか、手順なのか
    そのあたりがわからず困っています。

      補足日時:2016/08/29 00:24
  • どう思う?

    期待した結果はNo1様の頂いた内容で

    user_name=ユーザーA, line_no=7
    user_name=ユーザーB, line_no=6
    user_name=ユーザーC, line_no=5

    を、外部ファイル(usrlog2.cgi)に書き込みたいと思います。

      補足日時:2016/08/29 00:27
  • うーん・・・

    ご報告です。
    皆様にご指南頂きながら組み込んで、試行錯誤しました^^;

    以下のログに対して
    ユーザーA,2016-08-28 12:44:14
    ユーザーA,2016-08-25 12:44:55
    ユーザーB,2016-08-28 12:45:47
    ユーザーC,2016-08-27 12:45:54



    35行が最新

    結果は
    user_name=4 line_no=35
    と1行だけ表示されます。

    %sなのでユーザー毎が出るのかと思いながら色々やってみましたが
    結果が数値表示となり変わらずです。やはり勉強が足りないことを痛感しました。
    何か方策があればご教授いただければ幸いです。

    何度も申し訳ありません。

      補足日時:2016/08/30 22:23
  • うれしい

    ご指南ありがとうございます。当初の形で結果を得ることができました!
    頂いた内容をもとに改めて学習をして、最終は、

    ユーザーA,2016-08-25 12:44:55 (ユーザー毎の最新)
    ユーザーB,2016-08-28 12:45:47
    ユーザーC,2016-08-27 12:45:54

    というような形で結果が得れるようにチャレンジしてみます。
    ありがとうございます!

      補足日時:2016/08/31 20:17

A 回答 (9件)

#7の方が指摘されていますように


my $user = split(',',$_); #カンマで分割した最初のフィールドを取得

my ($user) = split(',',$_); #カンマで分割した最初のフィールドを取得
に変えてください。
(#1で投稿したスクリプトもそうなっています)

一応、解説しておきますと、
($変数1,$変数2,....) = split(....);
の場合は、分割された文字列の配列を順番に$変数1,$変数2、・・・に格納します。
これが通常の使い方です。
従って、my ($user) = split(...)は、分割された文字列の最初のものを$userに格納します。

一方、
$変数 = split(...);
は、分割された文字列の配列の数(今回は4)を$変数に格納します。
(つまり、usrlog.cgi の1行はカンマで4つのフィールドに区切られているはずです。)
この使い方は、あまり行われません。(分割された数を求めたい場合は別ですが・・・)

従って、splitの左辺は、
($var1,$var2,...) = split(....);
とするか
@var = split(...);
のようにするかのいずれかが一般的です。
@varの場合は、分割された結果を配列として@varの格納するので、
それを個々に参照する場合は、$var[0],$var[1]のようになります。
今回のケースでは
$var[0]にユーザー名、
$var[1]に時刻が格納されるようになります。
    • good
    • 0
この回答へのお礼

いつもご丁寧にありがとうございます。よく分かりました。
また改めて自分でも調べて恥ずかしながら納得しているところです。

この後、ユーザー名、時刻を抽出するように改造するつもりですので、しばらく自力でチャレンジしてみようと思います。
この数日間、ご教授いただいてありがとうございます!m(__)m

お礼日時:2016/08/31 20:14

結論は #8 の通り (もうちょっと効率的にはできるけど大した意味はない) なんだけど, ちょっと指摘しておきたい.



#7 の「split が何を返すのか確認すること.」に対して「splitのWEBリファレンスを読んでいます。」の結果として「split(',',$_)」と「split /,/, $_」の「双方で処理を行ってみた」んだよね. だとすると, 「splitのWEBリファレンス」を読んだことにより, この 2つで異なるものが返ると判断しないとおかしい. では, その「splitのWEBリファレンス」では「split が何を返すのか」についてどのように書いてあるのでしょうか? そして, それを読んでどう考えた結果としてこの 2つで異なるものが返ると判断したんでしょうか?
    • good
    • 0
この回答へのお礼

ご指摘ありがとうございます。

実際調べた(http://www.perlplus.jp/ 他)ときは「split /,/, $_」の説明ばかりで「split(',',$_)」は見つけられずに、実際動かしてみて見た目の結果で判断したのですが、ご指摘いただいた通り「 2つで異なるものが返ると判断しないとおかしい」ということに気づきませんでした。というか正直に申し上げて思い至らなかったというのが本音です。

皆さんのお知恵を借りながらそのようなところに思いが及ばず、逆に申し訳ないです。コメントしていただいてありがとうございました。

お礼日時:2016/08/31 20:12

あぁ,


my $user = split(',',$_);
がおかしい.

split が何を返すのか確認すること.
    • good
    • 0
この回答へのお礼

ご指摘ありがとうございます。splitのWEBリファレンスを読んでいます。

split(',',$_) or split /,/, $_;

双方で処理を行ってみましたが、見た目の結果は変わらずです。アドバイス頂いた意図をよく理解していないのかもしれません。。。。。


ログの読み出しについては、

$user = split(',',$_)
まず1行ずつ[,]で分割し、各要素を特殊変数$_に配列として格納

$users{$user} = $.;
その後ユーザー毎のファイルハンドルの現在ある位置の行数を$users{$user} に格納

これをwhileまたはforeach文ですべて読みだす(行数をつけ、配列として格納)という流れと理解しています。

お礼日時:2016/08/31 02:31

>結果は


>user_name=4 line_no=35
>と1行だけ表示されます。

ログを読み込んで、その結果をファイルへ出力していると思いますが、
その部分のソースを提示していただけますでしょうか。
必ず、コピー&ペーストで行ってください。(手入力で転記すると、転記で誤りがあった場合、原因が特定しにくくなります)
    • good
    • 0
この回答へのお礼

毎夜毎晩ありがとうございます。感謝です。

my %users = ();

open(FH,"$cf{logdir}/usrlog.cgi") or error("open err: usrlog.cgi");
while(<FH>){
my $user = split(',',$_); #カンマで分割した最初のフィールドを取得
$users{$user} = $. #その行番号を記憶
}
#全ユーザー名と行番号を印字
open(FH,"> $cf{logdir}/usrlog2.cgi") or error("open err: usrlog2.cgi");
foreach my $user (keys %users){
printf FH "user_name=%s line_no=%d\n",$user,$users{$user};
}

こちらになりますm(__)m

お礼日時:2016/08/31 01:01

単に %s とか %d とかで出力するだけなら printf を使うよりも


print FH "user_name=$user line_no=$users{$user}\n";
と埋め込んじゃった方が簡単じゃないかなと思ったりする.

今どきの Perl では 3引数の open を使った方がいいかなと思ったりもするけど.
    • good
    • 0
この回答へのお礼

ありがとうございます。こういうやり方もあるんですね!
配列の勉強をしないといけないことを痛感しています。。。

お礼日時:2016/08/30 22:17

#3です。

もう、一か所、修正点です。
print FH "user_name=%s line_no=%d\n",$user,$users{$user};

printf FH "user_name=%s line_no=%d\n",$user,$users{$user};
に変えてください。(print-->printf)
printとprintfは似ていますが、異なります。
    • good
    • 0
この回答へのお礼

ありがとう

何度もありがとうございますm(__)m
チャレンジさせていただきます、本当にありがとうございます!

お礼日時:2016/08/29 08:30

こちらで、確認していないので、推測になりますが、明らかにおかしいところは、以下の箇所です。


my %users = open(FH,"$cf{logdir}/usrlog.cgi") or error("open err: usrlog.cgi");
while(<>){
上記を
my %users = ();
open(FH,"$cf{logdir}/usrlog.cgi") or error("open err: usrlog.cgi");
while(<FH>){
に変えてください。

一応、解説しておきますと、
my %users = ();・・・連想配列の初期化
open(FH,"$cf{logdir}/usrlog.cgi") or error("open err: usrlog.cgi"); ・・ファイルのオープン
openの結果は、真、または偽を返すので、
my %users = open(FH,....) ・・・とすると、%usersを初期化したことになりません。

while(<FH>){ は、オープンしたファイルについて、先頭から読む場合です。
while(<>){ は、コマンドラインで与えられた第1引数(及び第2引数以降)をファイル名として先頭から読む場合です。
私が#1で投稿したのは、コマンドラインで第1引数にファイル名を与えたので、while(<>){ としました。
今回は、open(FH,"$cf{logdir}/usrlog.cgi") でオープンしたファイルを読み込むので、
while(<FH>){ とします。
    • good
    • 0
この回答へのお礼

ありがとう

細かな解説までありがとうございます。実際にやってみます!

お礼日時:2016/08/29 08:32

なにがどう「うまくいかない」んでしょうか? 具体的に「期待した動作」と「実際の動作」をそれぞれ書いてくれませんか?

    • good
    • 0
この回答へのお礼

ご指摘ありがとうございます。追記しました!

お礼日時:2016/08/29 00:25

以下のようなスクリプトを作成します。

(sample.plとします。)
-----------------------------------------------
my %users = ();
while(<>){
my($user) = split(',',$_); #カンマで分割した最初のフィールドを取得
$users{$user} = $. #その行番号を記憶
}
#全ユーザー名と行番号を印字
foreach $user (keys %users){
printf("user_name=%s line_no=%d\n",$user,$users{$user});
}
-------------------------------------------------------
perl sample.pl ログファイル
と入力すると、結果が表示されます。
以下、提示されたログファイルを読み込んで実行した結果です。
user_name=ユーザーA, line_no=7
user_name=ユーザーB, line_no=6
user_name=ユーザーC, line_no=5
    • good
    • 0
この回答へのお礼

ありがとうございます!
こういう風に考えるんですね!実際にチャレンジさせていただきます!

お礼日時:2016/08/28 22:05

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!