날짜/시간 문자열 비교

날짜/시간 문자열 비교

다음 데이터가 포함된 파일이 있습니다(샘플 데이터만 표시됩니다. 파일에는 최대 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. 이 날짜 비교를 유효하게 하려면 어떻게 해야 합니까? 또한 날짜 비교가 작동한 후에는 일치하는 모든 행의 첫 번째와 두 번째 사이의 숫자를 가져와야 합니다.

답변1

스크립트는 전체 파일을 변수로 읽은 다음 해당 변수의 값을 반복합니다. 여기에는 세 가지 문제가 있습니다.

  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가 됩니다. 비교가 참이면 파일의 행을 인쇄하십시오.

라인의 끝 부분과 시작 부분을 제거하여 라인의 타임스탬프를 구문 분석하기 위한 두 가지 별도의 대체 방법은 다음과 같이 대체될 수 있습니다.

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여기서는 파일에서 세 번째와 네 번째 분리 열(타임스탬프)을 가져오는 데 사용합니다 :. 즉, 원래 행을 구문 분석할 필요가 없습니다.

관련된:

답변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출력이 두 번째 줄에서 시작되도록 허용하여 출력에서 ​​일치하는 줄을 효과적으로 제거합니다.

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

관련 정보