adsense_in_article_test


[bash: sort, awk] 중복되는 행 제거하기 리눅스 bash script

문서(txt) 파일에서 중복되는 행(line)을 제거하는 방법은 두가지를 생각해 볼 수 있는데, 하나는 정렬한 후에 제거하는 것이고 나머지 하나는 정렬하지 않고 제거하는 것이다.

행 순서가 바뀌어도 문제가 없는 경우라면 sort 명령을 이용해서 정렬한 후에 중복을 제거하면 된다. -u 옵션을 사용하면 중복된 행이 제거된다. 아래의 예시를 보자.

# my_file.txt 내용 확인
cat my_file.txt
def
abc
abc
def

# 정렬 및 중복 제거
sort -u my_file.txt
abc
def

또는 아래와 같이 사용할 수도 있다. 결과는 동일하다.

cat my_file.txt | sort -u

그렇다면 행 순서를 바꾸지 않은 상태에서 중복된 행들만 제거하려면 어떻게 할까?

심오하고도 위대한 awk 명령을 이용하면 되겠다. 좀 복잡하긴 한데, 일단 아래의 예시를 보자.

# 정렬하지 않고 중복 제거
awk '!x[$0]++ {print $0}' my_file.txt
def
abc

awk의 기본 동작이 화면에 출력하는 것이기 때문에 {print $0} 부분은 생략해도 무관하다.

awk '!x[$0]++' my_file.txt


아래과 같이 해도 된다. 결과는 동일.

cat my_file.txt | awk '!x[$0]++'

'!x[$0]++ {print $0}' 이 표현이 나름 심오한데, awk의 배열(array)이 가진 특징을 생각해 보면 감을 잡을 수 있다. awk에서는 배열의 인덱스(index)가 반드시 0 이상의 정수일 필요가 없고 문자열도 인덱스가 된다. C언어에서는 x[0], x[1] 이렇지만 awk에서는 x[my name is john], x[foo] 이런 것이 가능하다. 게다가 배열의 최대 크기를 지정할 필요도 없다.

자, 그러면 위의 표현이 어떤 일을 하는지 생각해 보자. awk에서 행 전체를 지칭하는 변수는 $0이다. 즉, 한 행의 문자열을 x라는 배열의 인덱스로 사용하는 것이다. 차근차근 보면 대강 이렇다.

명령을 실행하면 awk는 우선 my_file.txt의 첫 행을 읽어서 배열을 만든다. 해당 변수의 값의 기본 초기값은 0이다. 위의 예시에서 첫 행은 def였다. 즉 x[def]=0 (false)

그런데 앞에 ! 기호가 있으니 해당 값의 역(inverse)을 취한다. !x[def]=1 (true)

조건값이 non-zero, 즉 0이 아니므로 지정된 명령을 실행한다. {print $0} (문자열 출력)

그리고 나서 x[def]++(post-increment)에 의해 x[def]의 값은 1이 된다. x[def]=1

my_file.txt의 두번째 행을 읽어서 동일한 절차를 수행한다. 두번째 행은 abc이므로 출력이 끝나고 나면 x[abc]=1

이런식으로 가다가 어떤 행을 읽었는데 문자열 def였다고 하자. 즉 중복이 발생한 것이다. 이전에 이미 x[def]=1이므로 !x[def]=0, 즉 조건값이 0(false)이므로 출력되지 않는다. 그리고나서 x[def]++에 따라 x[def]=2 이렇게 된다.

이런 원리로 행 순서가 바뀌지 않으면서 중복된 행만 깔끔하게 제거된 결과를 얻을 수 있다. 속도 역시 상당히 빠르다.

만약 행 전체가 아니라 첫번째 단어만 기준으로 삼아서 중복을 제거하고 싶다면 x[$0] 대신에 x[$1] 이렇게 바꾸면 된다. (두번째 단어라면 x[$2])

awk '!x[$1]++ {print $0}' my_file.txt

만약 첫번째 단어만 기준으로 삼아 중복을 제거한 후, 출력은 두번째 단어만 골라서 하고 싶다면 print 부분의 인자를 바꿔주면 되겠다. 아래와 같이 해 보자.

awk '!x[$1]++ {print $2}' my_file.txt



핑백

  • 반달가면 : [bash: uniq] 중복되는 행 또는 중복되지 않는 행만 골라서 출력하기 2016-03-30 23:35:47 #

    ... uniq에 옵션을 붙이지 않으면 중복된 행을 제거하고 출력한다. 단순 중복 제거는 sort, awk를 이용하는 방법이 더 효율적일 수 있다. 이전 게시물을 참고하자. 여기로 # 중복 제거 cat test.txt | sort | uniq abc 123 abc 234 hij 234 hij 456 만약 중복이 발생한 행만 골라서 출력하고 ... more

  • 반달가면 : [bash: grep] 두개의 파일에서 공통으로 출현하는 문자열 찾기 2016-11-30 21:00:12 #

    ... 되겠다. 빈 줄 제거는 grep에서 공백이 아닌 캐럭터 전체를 검색하고, 중복 제거는 sort의 -u 옵션을 이용하자. sort에 대한 내용은 이전 게시물을 참고하자. 여기로 a.txt 파일에서 빈 줄과 중복되는 행을 제거하여 a.txt.tmp 파일을 만든 후, 이 파일을 이용해서 b.txt에 대한 검색을 실행하면 된다. # 빈 줄과 ... more

  • 반달가면 : tshark에서 패킷 내용을 상세하게 출력(-V 옵션) 2020-07-25 18:49:54 #

    ... ET 패킷만 골라낸 후에, "co.kr" 문자열을 포함한 HTTP 호스트만 골라서 중복을 제거하고 정렬할 수 있다. (중복 제거에 대한 내용은 이전 게시물을 참고하자. 여기로) # test.pcap 파일에서 HTTP GET 패킷을 골라낸 후 grep과 sort를 조합 tshark -r test.pcap -Y "http.request. ... more

덧글

  • ㅇㅇ 2018/03/05 22:13 # 삭제 답글

    좋은 내용입니다 근대 왜 ++ 하는지 이해가안가는대 설명부탁드려도될까요?
  • 반달가면 2018/03/06 14:12 #

    '!x[$0] { print $0}' 이 부분을 보시면 !x[$0]의 값이 0이 아닐 때만(즉, x[$0]의 값이 0일 때만) 출력합니다.

    중복제거이므로 일단 한번 출력했으면 그 다음에 같은 내용의 행이 나왔을 경우 출력하지 말아야 하기 때문에, 출력한 후 ++로 x[$0]의 값을 증가시키면 그 다음부터는 !x[$0]의 값이 항상 0이 되므로 중복되는 행이 출력되지 않습니다.
  • ㅇㅇ 2018/03/07 18:51 # 삭제 답글

    감사합니다 도움됬씁니다 ㅎㅎ
  • lms1712 2019/11/15 13:57 # 삭제 답글

    좋은 글 감사합니다.
    만약 awk '!x[$0]++' 를 사용해서 중복된 행을 제거하고, 그 수를 알고 싶으면 어떻게 해야 하나요?
  • 반달가면 2019/11/16 08:57 #

    wc -l 명령으로 출력의 행 수를 세면 될 듯하네요.

    awk '!x[$0]++ {print $0}' my_file.txt | wc -l
댓글 입력 영역
* 비로그인 덧글의 IP 전체보기를 설정한 이글루입니다.


B-Side


adsense(w160_h600)2

통계 위젯 (화이트)

47678
5116
2105700

2019 대표이글루_IT

Google Analytics