테이블 다시 포맷하기

테이블 다시 포맷하기

table.txt다음과 같이 잘못 작성되어 결과에 중복된 것으로 나타나는 일부 테이블( )이 있습니다 .

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...

대신에 나는 다음을 원한다:

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...

그래서 문제는 결과가 표에 두 번 나타난다는 것입니다. 이는 (제공된 예를 사용하여) "1971" 이후에는 다시 "1971"이 아닌 "1972" 연도를 예상해야 함을 의미합니다. sh/bash를 사용하여 중복된 결과를 제거하는 방법이 있습니까?

내 데이터는 1971년부터 2099년까지이며 2000년 이후에도 아래 표시된 것과 정확히 같은 형식이라는 점에 유의해야 합니다.

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
2000 1     1   875
2000 1     2   456
...
2099 12    31  321

답변1

다음은 상호 배타적인 두 개의 sed루프입니다.

sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' <<""
YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...
1972 12    31  999
1972 1     1   933
1972 1     2   837
...
1972 12    31  343

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...
1972 12    31  999

기본적으로 rint와 rint sed의 두 가지 상태가 있습니다.p먹다. 첫 번째 상태( p린트 상태) 에서는 각 입력 행을 sed자동으로 p린트한 다음 패턴과 비교하여 확인합니다 / 12 * 31 /. 현재 패턴 공간이 !일치 하지 않으면 d제거되고 sed다음 입력 줄이 당겨지며 elete 명령 다음에 아무것도 p실행하지 않고 rint 명령에서 스크립트가 맨 위에서 다시 시작됩니다.d

입력라인이 있을 때하다/ 12 * 31 /그러나 경기는 sed대본 후반부에 들어갑니다 -먹다반지 모양. 먼저 :이라는 분기 레이블을 정의한 n다음 현재 패턴 공간을 외부 입력 라인으로 덮어쓴 다음 n현재 패턴 공간을 //마지막 일치 패턴과 비교합니다. 이전에 일치한 줄이 ext 에 의해 덮어쓰기 되었기 때문에 n이 첫 번째 반복은먹다루프가 일치하지 않으며 매번 레이블 로 !역추적 하지 않아 외부 입력 라인을 가져와서 마지막 일치 패턴과 다시 비교합니다.sed b:nn//

마지막으로 또 다른 일치 항목(나중에 약 365개 추가 줄)을 만들면 n스크립트가 완료될 때 자동으로 인쇄하지 않고 다음 입력 줄을 가져온 다음 sed첫 번째 상태 시작의 rint 명령에서-n 맨 위에서 다시 시작합니다. p따라서 각 루프 상태는 다음 키를 가능한 한 적게 찾으면서 동일한 키의 다음 상태로 점프합니다.

단일 편집 루틴을 호출하지 않고 전체 스크립트를 완료할 수 있으며 단일 정규식만 컴파일하면 됩니다. 결과로 나오는 자동 장치는 매우 간단합니다. 즉, [123 ]및 만 이해합니다 [^123 ]. 게다가 비교의 적어도 절반은 컴파일 없이 수행될 것입니다.먹다루프는 단순히 //빈 루프입니다. 따라서 sed입력 행당 한 번의 호출로 루프를 완전히 완료할 수 있습니다.regexec()sed 가능한p린트 루프와 비슷한 작업을 수행하십시오.


타이밍


여기에 있는 다양한 답변이 어떻게 수행될 수 있는지 궁금하여 나만의 테이블을 생각해냈습니다.

dash <<""
    d=0 D=31 IFS=: set 1970 1
    while   case  "$*:${d#$D}" in (*[!:]) ;;
            ($(($1^($1%4)|(d=0))):1:)
                     D=29 set $1 2;;
            (*:1:)   D=28 set $1 2;;
            (*[3580]:)
                     D=30 set $1 $(($2+1));;
            (*:)     D=31 set $(($1+!(t<730||(t=0)))) $(($2%12+1))
            esac
    do      printf  '%-6d%-4d%-4d%d\n' "$@" $((d+=1)) $((t+=1))
    done|   head    -n1000054 >/tmp/dates

dash <<<''  6.62s user 6.95s system 166% cpu 8.156 total

이렇게 하면 100만 개 이상의 행이 입력되고 /tmp/dates1970년부터 3338년까지 매년 출력이 두 배로 늘어납니다. 파일은 다음과 같습니다.

tail -n1465 </tmp/dates | head; echo; tail </tmp/dates

3336  12  27  728
3336  12  28  729
3336  12  29  730
3336  12  30  731
3336  12  31  732
3337  1   1   1
3337  1   2   2
3337  1   3   3
3337  1   4   4
3337  1   5   5

3338  12  22  721
3338  12  23  722
3338  12  24  723
3338  12  25  724
3338  12  26  725
3338  12  27  726
3338  12  28  727
3338  12  29  728
3338  12  30  729
3338  12  31  730

...어쨌든, 몇 가지 있습니다.

그런 다음 다른 명령을 시도했습니다.

for  cmd in "sort -uVk1,3" \
            "sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn'" \
            "awk '"'{u=$1 $2 $3 $4;if (!a[u]++) print;}'\'
do   eval   "time ($cmd|wc -l)" </tmp/dates
done

500027
( sort -uVk1,3 | wc -l; ) \
1.85s user 0.11s system 280% cpu 0.698 total

500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.64s user 0.09s system 110% cpu 0.659 total

500027
( awk '{u=$1 $2 $3 $4;if (!a[u]++) print;}' | wc -l; ) \
1.46s user 0.15s system 104% cpu 1.536 total

sort그리고 두 명령 sed모두 절반도 안 되는 시간 안에 완료되었습니다 awk. 이러한 결과는 일반적입니다. 나는 그들을 여러 번 실행했습니다. 모든 명령이 올바른 행 수를 작성하는 것처럼 보입니다. 따라서 아마도 모두 작동할 것입니다.

sort각 실행의 완료 시간은 꽤 좋지만(보통 조금 앞당김), sed결과를 얻으려면 다른 두 명령보다 더 많은 실제 작업이 필요합니다. 작업을 완료하기 위해 병렬 작업을 실행하고 있으며 멀티 코어 CPU의 이점을 크게 누리고 있습니다. 그리고 처리되는 동안 할당된 단일 코어에 고정된 상태로 유지됩니다.sedsortawksed

여기에 있는 결과는 표준 최신 GNU 버전에서 가져온 것이지만 sed다른 버전을 시도해 보았습니다. 사실, 다른 바이너리로 세 가지 명령을 모두 시도했지만 sed실제로 이 명령만 가보 도구에서 작동했습니다. 나는 다른 사람들이 비표준 구문으로 인해 작업을 시작하기 전에 오류로 종료했다고 추측합니다.

가능할 때마다 표준 구문을 사용하는 것이 좋습니다. 대부분의 경우 더 간단하고 완전하며 효율적인 구현을 자유롭게 사용할 수 있습니다.

PATH=/usr/heirloom/bin/posix2001:$PATH; time ...

500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.31s user 0.12s system 136% cpu 0.318 total

답변2

awk로 파이핑해 보세요

awk '!a[$0]++' files.txt > new_files.txt
mv new_files.txt files.txt

그러면 행이 한 번만 출력됩니다.

편집: (var를 연결하면 문제가 해결될지 확실하지 않음)

awk '{u=$1 $2 $3 $4 ; if ( !a[u]++ ) print ; } ' ...

답변3

$ (head -1 table.txt ; tail -n +2 table.txt | sort -u -V -k1,3)
YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
1971 2     1   587
1971 12    31  685
1972 1     1   549
1972 1     2   746
2000 1     1   875
2000 1     2   456
2099 12    31  321

관련 정보