日付/時刻文字列の比較

日付/時刻文字列の比較

次のデータを含むファイルがあります(サンプルデータのみが表示されます。ファイルには最大2001行が含まれています)。

0001:3002:2018/07/16:12.34.31:ERR 
0002:3002:2018/07/16:12.34.44:ERR 
0003:3002:2018/07/16:12.34.57:ERR 
0004:3002:2018/07/16:12.35.10:ERR 
0005:3002:2018/07/16:12.35.23:ERR 
0006:3002:2018/07/16:12.35.36:ERR 
0007:3002:2018/07/16:12.35.49:ERR 
0008:3002:2018/07/16:12.36.02:ERR 
0009:3002:2018/07/16:12.36.15:ERR

たとえば、2018/07/16:12.36.15のようにbashスクリプトに日付を渡しましょう.このファイルの各行を読み、その行の日付を渡された日付と比較し、渡された日付より大きい日付を持つ行を返したいと思います。

今まで何をしたのか

#!/bin/sh

SEARCH_DATE=$1
errorCodeFilePath=/home/.errorfile.log
lines=`cat $errorCodeFilePath`
for line in $lines; do
   errorCodeDate=$(echo $line |grep -Eo '[[:digit:]]{4}/[[:digit:]]{2}/[[:digit:]]{2}:[[:digit:]]{2}.[[:digit:]]{2}.[[:digit:]]{2}');  
   if [ $errorCodeDate -ge $SEARCH_DATE ];
    then
        echo $errorCodeDate
    fi
done

質問

  1. 日付比較が機能しているかどうかはわかりません。 「整数式エラーが予想されます」というメッセージが表示されます。私は本当に Bash スクリプトを書く方法を知らず、これが私の最初の試みです。

  2. この日付比較を有効にするにはどうすればよいですか?また、日付比較が機能した後は、一致するすべての行の最初と2番目の間の数字を取得する必要があります。

答え1

スクリプトはファイル全体を変数として読み込み、その変数の値を繰り返します。これには3つの問題があります。

  1. 最も一般的なケースでは、入力ファイルのサイズがわかりません。これは、場合によっては、変数が次のように大きくなる可能性があることを意味します。非常に大きい。
  2. ループ変数の引用符で囲まれていない値は、シェルを使用して空白(スペース、タブ、および改行)にデータを分割します。データに改行以外のスペースが含まれている場合、ループは誤った操作を実行する可能性があります。
  3. シェルは、ループの前に引用されていない変数の値に対してファイル名のグロービングを実行します。つまり、データに*または同じワイルドカードパターンが含まれている場合、[...]これらのパターンは既存のファイル名と一致します。

この回答は、使用されたタイムスタンプがその後のタイムスタンプが以前のタイムスタンプ(少なくともPOSIXロケールでは)の後にソートされるという点で合理的であるという事実を利用します。

#!/bin/bash

while IFS= read -r line; do
    timestamp=${line%:*}            # Remove ":ERR" at the end
    timestamp=${timestamp#*:*:}     # Remove numbers from start ("0001:3002:")
    if [[ "$timestamp" > "$1" ]]; then
        # According to the current locale, the timestamp in "$timestamp"
        # sorts after the timestamp in "$1".
        printf "Greater: %s\n" "$line"
    fi
done <file

スクリプトは、ファイルと同じ形式のタイムスタンプを唯一の引数として使用します。ファイルの内容を繰り返しfile、各行のタイムスタンプを解析し、それをコマンドラインのタイムスタンプと比較します。>演算子 in を使用した比較は、ファイルbashのタイムスタンプが現在のロケールで指定されたタイムスタンプの後に辞書式でソートされた場合に true になります。比較が真の場合は、ファイルの行を印刷します。

行の終わりと始まりを削除して行のタイムスタンプを解析するための2つの別々の代替方法は、次のように置き換えることができます。

timestamp=$( cut -d ':' -f 3,4 <<<"$line" )

ただし、外部ユーティリティを呼び出すため、実行が遅くなります。

テスト:

$ bash script.sh '2018/07/16:12.36.00'
Greater: 0008:3002:2018/07/16:12.36.02:ERR
Greater: 0009:3002:2018/07/16:12.36.15:ERR

生の行ではなくファイルのタイムスタンプのみを出力するには、コマンドでに変更します"$line""$timestamp"printf

この場合、次のループを実行して作業を高速化することもできます。

#!/bin/bash

cut -d ':' -f 3,4 file |
while IFS= read -r timestamp; do
    if [[ "$timestamp" > "$1" ]]; then
        # According to the current locale, the timestamp in "$timestamp"
        # sorts after the timestamp in "$1".
        printf "Greater: %s\n" "$timestamp"
    fi
done

cutここでは、ファイルから3番目と4番目の分離列(タイムスタンプ)を取得するために使用します:。つまり、元の行を解析する必要はありません。

関連:

答え2

あなたの考えは正確ですが、スクリプトが期待どおりに機能するように修正できるいくつかの問題があります。

  1. ファイルを最初に使用しcatて変数に保存してから繰り返すのは、せいぜいアンチパターンです。このメソッドは文字列をスペースで区切ります。代わりに、whileループを使用してファイルリダイレクトを使用してください。
  2. 変数の内容を保存し、前のポイントで述べたトークン化を防ぐには、常にシェル変数を引用します。
  3. 代わりに、grep基本正規表現サポートを使用してbashEPOCH変換の日付文字列を抽出してください。
  4. デフォルトでは、文字列をbash比較する方法はdate提供されていません。等しいEPOCH値に変換して整数比較を実行する必要があります。

したがって、サードパーティ製のツールを使用せず、シェルの内側のみを使用して統合します。このフラグを使用するにはdateGNUユーティリティのコマンドが必要です。-dいいえdate* BSDシステムでデフォルトで動作します。

#!/usr/bin/env bash   

errorCodeFilePath="/home/.errorfile.log"

re='[0-9]+/[0-9]+/[0-9]+:[0-9]+\.[0-9]+\.[0-9]+'

convDateString() {
    day="${1##*:}"
    time="${1%%:*}"
    printf '%d' "$(date -d"$time ${day//./:}" +%s)"
}

while IFS= read -r line; do
    inputArg="$1"
    inputEPOCH="$(convDateString "${inputArg}")"
    if [[ $line =~ $re ]]; then
        lineEPOCH="$(convDateString "${BASH_REMATCH[*]}")"
        if [ "$lineEPOCH" -gt "$inputEPOCH" ]; then
            echo "${BASH_REMATCH[@]}" is greater
        fi
    fi
done<"$errorCodeFilePath"

以下のような問題のあるサンプル入力でファイルをテストしてください。

$ bash script.sh "2018/07/16:12.36.00"
2018/07/16:12.36.02 is greater
2018/07/16:12.36.15 is greater

要約すると、読書を検討する必要があります。シェルループを使用してテキストを処理するのはなぜ悪い習慣と見なされますか?。テキスト処理にシェルを使用するのは、ファイル処理専用の他のツールよりも遅いからです。

答え3

この試み、

#!/bin/sh

SEARCH_DATE="$1"
errorCodeFilePath=/home/nagios/temp/test1
lines=`cat $errorCodeFilePath`
for line in $lines; do
   errorCodeDate=$(echo $line |grep -Eo '[[:digit:]]{4}/[[:digit:]]{2}/[[:digit:]]{2}:[[:digit:]]{2}.[[:digit:]]{2}.[[:digit:]]{2}');
if [ $(date -d "`echo $errorCodeDate| tr ':' ' '| tr '.' ':'`" +%s) -ge $(date -d "`echo $SEARCH_DATE| tr ':' ' '| tr '.' ':'`" +%s) ];
    then
        echo $errorCodeDate
    fi
done

答え4

行を繰り返すには、改行にfor設定する必要がありますIFS。 whileループを使用すると、少し高速です。

#!/bin/bash

IFS=$'\n'
for a in $(<file.txt); do
    [[ $1:ERR < ${a#*:*:} ]] && echo "$a"
done
$ ./script.sh 2018/07/16:12.35.10

(awkバージョン)

#!/usr/bin/awk -bf

BEGIN { FS=OFS=":" } {
    if (d < $3 FS $4) { print $0 }
}
$ ./script.awk -vd=2018/07/16:12.35.10 file.txt

日付が存在することを既に知っていて、残りの行だけを印刷したい場合は、ファイルを日付、時間ごとに並べ替え、それを使用して一致する行の後grep -Aのコンテキストを取得できます。tail +2出力が2行目から始まるようにすることで、出力から一致する行を効果的に削除します。

$ grep < <(sort -t : -k 3,4 < file.txt) \
    -A2000 -Fe '2018/07/16:12.35.10' | tail +2 | sort -n

関連情報