次のデータを含むファイルがあります(サンプルデータのみが表示されます。ファイルには最大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
質問
日付比較が機能しているかどうかはわかりません。 「整数式エラーが予想されます」というメッセージが表示されます。私は本当に Bash スクリプトを書く方法を知らず、これが私の最初の試みです。
この日付比較を有効にするにはどうすればよいですか?また、日付比較が機能した後は、一致するすべての行の最初と2番目の間の数字を取得する必要があります。
答え1
スクリプトはファイル全体を変数として読み込み、その変数の値を繰り返します。これには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
あなたの考えは正確ですが、スクリプトが期待どおりに機能するように修正できるいくつかの問題があります。
- ファイルを最初に使用し
cat
て変数に保存してから繰り返すのは、せいぜいアンチパターンです。このメソッドは文字列をスペースで区切ります。代わりに、whileループを使用してファイルリダイレクトを使用してください。 - 変数の内容を保存し、前のポイントで述べたトークン化を防ぐには、常にシェル変数を引用します。
- 代わりに、
grep
基本正規表現サポートを使用してbash
EPOCH変換の日付文字列を抽出してください。 - デフォルトでは、文字列を
bash
比較する方法はdate
提供されていません。等しいEPOCH値に変換して整数比較を実行する必要があります。
したがって、サードパーティ製のツールを使用せず、シェルの内側のみを使用して統合します。このフラグを使用するにはdate
GNUユーティリティのコマンドが必要です。-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