저는 7개의 열이 포함된 여러 줄의 텍스트 파일(.csv)로 작업하고 있습니다.
각 행에는 고유해야 하는 ID가 포함되어 있습니다. 일부 날짜 열도 있는데 그 중 하나가 "마지막 수정" 날짜입니다.
"고유"해야 하는 ID가 실제로 중복되는 경우가 있다는 것을 발견했습니다. 이는 하나를 제외한 모든 ID를 제거하여 해결해야 하는 문제입니다.
아래에는 gawk를 사용하는 예가 있지만 gawk, awk 또는 grep 등을 사용하여 "가장 최근에" 수정된 줄을 제외한 중복 줄을 제거할 수 있는 방법이 있습니까? 따라서 무엇이 가고 무엇이 남는지에 대한 논리가 있습니다.
예를 들어, 이 csv 발췌문에는 두 개의 행이 있습니다. 한 분야만 빼고 모든 분야가 동일합니다. ID 번호가 "동일"하다는 것은 나에게 "중복"이라는 의미입니다.
두 줄 모두완전히동일하지만.
csv 파일의 마지막(7번째) 필드에 있는 날짜로 인해 한 항목이 다른 항목보다 오래되었습니다.
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-08-26 17:32:00
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-09-11 22:15:00
파일에 대해 gawk, cat, grep, cut, awk 등의 작업을 수행할 수 있습니까?
a) 중복된 ID가 있는 콘텐츠를 식별합니다. b) 마지막 필드에 "최신" 날짜가 있는 중복 항목만 유지합니다.
이상적으로는 데이터베이스에 공급되는 CSV 헤더가 포함된 첫 번째 행을 유지해야 합니다.
이것이 거의 잘 작동하는 이유입니다.
gawk -i inplace '!a[$0]++' *.csv
실제로는 중복 항목을 제거하여 한 행만 남기는 것처럼 보이지만 최종 필드에서 가장 오래된 날짜 값을 기준으로 무엇을 유지할지 결정하는 논리가 없습니다.
도울 수 있니...
답변1
모든 파일이 아닌 각 파일 내에서만 중복 항목을 테스트하고 데이터의 입력 순서 유지에 관심이 없다고 가정하면 그는 원하는 작업을 수행하기 위해 force POSIX 도구의 모든 버전을 사용할 것입니다. 모든 Unix 시스템에서 작동합니다.
$ cat tst.sh
#!/usr/bin/env bash
tmp=$(mktemp) || exit 1
sep=','
for file in "$@"; do
{
head -n 1 "$file" &&
tail -n 2 "$file" |
sort -t "$sep" -r -k 7,7 |
awk -F "$sep" '$1 != prev { print; prev=$1 }'
} > "$tmp" &&
mv -- "$tmp" "$file"
done
예를 들어:
$ cat file
foo,bar
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-08-26 17:32:00
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-09-11 22:15:00
$ ./tst.sh file*
$ cat file
foo,bar
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-09-11 22:15:00
sort
위의 도구 만 모든 입력을 한 번에 처리해야 하고, 다른 도구는 한 번에 한 줄만 처리하며 sort
요구 페이징 등을 사용하여 대용량 파일을 처리하도록 설계되었습니다. 입력 파일이 매우 큽니다.
입력 행 순서를 정말로 유지하려면 위의 내용을 변경하여 적용할 수 있습니다.DSU 관용어이 작업을 수행할 수 있어야 합니다.
$ cat tst.sh
#!/usr/bin/env bash
tmp=$(mktemp) || exit 1
sep=','
for file in "$@"; do
awk -v OFS="$sep" '{ print (NR>1), NR, $0 }' "$file" |
sort -t "$sep" -k1,1 -k9,9r |
awk -F "$sep" 'NR==1{print; next} $1 != prev{ print; prev=$1 }' |
sort -t "$sep" -k1,1 -k2,2n |
cut -d "$sep" -f3- \
> "$tmp" &&
mv -- "$tmp" "$file"
done
sort
그러나 행을 선택한 후 입력을 원래 순서로 복원하는 데 1초가 걸립니다.
입력 순서를 유지하면서 한 번의 GNU awk 호출로 모든 작업을 수행하려면 다음과 같이 하십시오.
$ cat tst.awk
BEGIN { FS="," }
FNR == 1 {
delete id2maxTs
delete id2fnr
delete fnr2input
print
next
}
{ id=$1; ts=$7 }
!(id in id2maxTs) || (ts > id2maxTs[id]) {
if ( id in id2fnr ) {
prevFnr = id2fnr[id]
delete fnr2input[prevFnr]
}
id2maxTs[id] = ts
id2fnr[id] = FNR
fnr2input[FNR] = $0
}
ENDFILE {
for ( i=1; i<=FNR; i++ ) {
if ( i in fnr2input ) {
print fnr2input[i]
}
}
}
$ gawk -i inplace -f tst.awk file*
$ cat file
foo,bar
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-08-26 17:32:00
gawk 스크립트는 원래 입력 순서를 유지하지만 각 입력 파일의 전체 내용을 메모리로 읽어야 합니다.
답변2
GNU awk 사용MK타임()기능:
gawk -F, '
NR==1{ print; next }
{
svn=dTime=$7
gsub(/[-:]/, " ", dTime)
dTime=mktime(dTime)
sub(/,[^,]*$/, "")
}
dTime > gId[$0] {
gId[$0]=dTime
records[$0]=svn
}
END { for(rec in records) print rec, records[rec] }' infile
바라보다gawk를 통해 사전 정의된 배열 스캔 순서 사용( PROCINFO["sorted_in"]
) 출력 중 루프 순회를 위한 기본 배열을 설정합니다.
답변3
sort
결합하다awk
#get header line
head -1 infile
#work on data
tail +2 infile | sort -t, -r -k7 | awk -F, '!seen[$1]++'
=> 7번째 필드(날짜 필드), 즉 최신 항목부터 역순으로 정렬합니다. 그런 다음 첫 번째 고유 ID가 있는 행만 인쇄됩니다.
참고: 문자열에 추가 쉼표가 있습니다. 동일한 ID가 동일한 날짜에 발생하면 행은 정의된 대로 정렬됩니다. 날짜 문자열은 선행/패딩 0 또는 완전히 혼합된 형식을 사용하지 않습니다.