1

一度に数か月分のログ エントリを含む大きなログ ファイルを分割しようとしています。また、ログ ファイルを日付ごとに分割しようとしています。次のように何千もの行があります。

Sep 4 11:45 kernel: Entry
Sep 5 08:44 syslog: Entry

ファイル logfile.20090904 と logfile.20090905 にエントリが含まれるように分割しようとしています。

各行を読み取って適切なファイルに送信するプログラムを作成しましたが、実行がかなり遅くなります (特に、月の名前を数値に変換する必要があるため)。ファイル内の最初の日付を見つける必要がある毎日のgrepを実行することを考えましたが、それも遅いようです。

より最適なソリューションはありますか? たぶん、よりうまく機能するコマンドラインプログラムがありません。

これが私の現在の解決策です:

#! /bin/bash
cat $FILE | while read line; do
  dts="${line:0:6}"
  dt="`date -d "$dts" +'%Y%m%d'`"
  # Note that I could do some caching here of the date, assuming
  # that dates are together.
  echo $line >> $FILE.$dt 2> /dev/null
done
4

4 に答える 4

2

@OP は、bash の while read ループを使用して大きなファイルを反復しないようにします。その速度が遅く、さらに、読み取ったファイルのすべての行に対して外部の日付コマンドを呼び出していることが実証済みです。gawk のみを使用する、より効率的な方法を次に示します。

gawk 'BEGIN{
 m=split("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",mth,"|")     
}
{ 
 for(i=1;i<=m;i++){ if ( mth[i]==$1){ month = i } }
 tt="2009 "month" "$2" 00 00 00" 
 date= strftime("%Y%m%d",mktime(tt))
 print $0 > FILENAME"."date
}
' logfile

出力

$ more logfile
Sep 4 11:45 kernel: Entry
Sep 5 08:44 syslog: Entry

$ ./shell.sh

$ ls -1 logfile.*
logfile.20090904
logfile.20090905

$ more logfile.20090904
Sep 4 11:45 kernel: Entry

$ more logfile.20090905
Sep 5 08:44 syslog: Entry
于 2009-10-29T02:12:06.837 に答える
1

すでに行ったことを考えると、ファイルに「Sep 4」などの名前を付け、最後にすべての名前を変更するのが最も簡単です。この方法では、特定の数の文字を読み取るだけで、余分なものはありません。処理。

何らかの理由でそれをしたくないが、日付が正しいことがわかっている場合は、両方の形式で前の日付をキャッシュし、文字列比較を行って、日付を再度実行する必要があるか、単に使用する必要があるかを確認できます古いキャッシュ日付。

最後に、速度が本当に問題である場合は、bash の代わりに perl または python を試すことができます。ただし、ここではおかしなことは何もしていないので (行ごとにサブシェルと日付プロセスを開始する以外に、これを回避する方法はすでにわかっています)、それがどれほど役立つかはわかりません。

于 2009-10-28T18:51:19.580 に答える
1

スクリプトのスケルトン:

BIG_FILE=big.txt

# remove $BIG_FILE when the script exits
trap "rm -f $BIG_FILE" EXIT

cat $FILES > $BIG_FILE || { echo "cat failed"; exit 1 }

# sort file by date in place
sort -M $BIG_FILE -o $BIG_FILE || { echo "sort failed"; exit 1 }

while read line;
   # extract date part from line ...
   DATE_STR=${line:0:12} 

   # a new date - create a new file
   if (( $DATE_STR != $PREV_DATE_STR)); then 
       # close file descriptor of "dated" file
       exec 5>&- 
       PREV_DATE_STR=$DATE_STR

       # open file of a "dated" file for write
       FILE_NAME= ... set to file name ...
       exec 5>$FILE_NAME || { echo "exec failed"; exit 1 }
   fi

   echo -- $line >&5 || { echo "print failed"; exit 1 }
done < $BIG_FILE
于 2009-10-28T19:22:53.303 に答える
0

このスクリプトは、ログ ファイルの各行を反復処理する代わりに、内部ループを 365 回または 366 回、1 年の各日に 1 回実行します。

#!/bin/bash
month=0
months=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
for eom in 31 29 31 30 31 30 31 31 30 31 30 31
do
    (( month++ ))
    echo "Month $month"
    if (( month == 2 ))    # see what day February ends on
    then
        eom=$(date -d "3/1 - 1 day" +%-d)
    fi
    for (( day=1; day<=eom; day++ ))
    do
        grep "^${months[$month - 1]} $day " dates.log > temp.out
        if [[ -s temp.out ]]
        then
            mv temp.out file.$(date -d $month/$day +"%Y%m%d")
        else
            rm temp.out
        fi
        # instead of creating a temp file and renaming or removing it,
        # you could go ahead and let grep create empty files and let find
        # delete them at the end, so instead of the grep and if/then/else
        # immediately above, do this:
        # grep --color=never "^${months[$month - 1]} $day " dates.log > file.$(date -d $month/$day +"%Y%m%d")
    done
done
# if you let grep create empty files, then do this:
# find -type f -name "file.2009*" -empty -delete
于 2009-10-28T22:10:42.050 に答える