[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



핑백

덧글

  • ㅇㅇ 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 전체보기를 설정한 이글루입니다.

Google Analytics


B-Side


adsense(w160_h600)2

통계 위젯 (화이트)

71342
4132
1862262

ad_widget_2