다음 데이터가 포함된 파일이 있습니다(샘플 데이터만 표시됩니다. 파일에는 최대 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 스크립트를 작성하는 방법을 정말로 모르고 이것이 나의 첫 번째 시도입니다.
이 날짜 비교를 유효하게 하려면 어떻게 해야 합니까? 또한 날짜 비교가 작동한 후에는 일치하는 모든 행의 첫 번째와 두 번째 사이의 숫자를 가져와야 합니다.
답변1
스크립트는 전체 파일을 변수로 읽은 다음 해당 변수의 값을 반복합니다. 여기에는 세 가지 문제가 있습니다.
- 가장 일반적인 경우에는 입력 파일의 크기를 알 수 없습니다. 이는 어떤 경우에는 변수가 다음과 같이 커질 수 있음을 의미합니다.매우큰.
- 루프 변수의 인용되지 않은 값은 셸을 사용하여 공백(공백, 탭 및 줄 바꿈)으로 데이터를 분할합니다. 데이터에 줄 바꿈 이외의 공백이 포함되어 있으면 루프가 잘못된 작업을 수행할 수 있습니다.
- 쉘은 루프 전에 인용되지 않은 변수의 값에 대해 파일 이름 글로빙을 수행합니다. 즉, 데이터에
*
또는 같은 와일드카드 패턴이 포함되어 있으면[...]
이러한 패턴이 기존 파일 이름과 일치됩니다.
이 답변은 사용된 타임스탬프가 그 뒤에 오는 타임스탬프가 이전 타임스탬프(적어도 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
귀하의 생각은 정확하지만 스크립트가 예상대로 작동하도록 수정할 수 있는 몇 가지 문제가 있습니다.
- 파일을 먼저 사용
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
출력이 두 번째 줄에서 시작되도록 허용하여 출력에서 일치하는 줄을 효과적으로 제거합니다.
$ grep < <(sort -t : -k 3,4 < file.txt) \
-A2000 -Fe '2018/07/16:12.35.10' | tail +2 | sort -n