반달가면 이글루에서 백업 - http://bahndal.egloos.com/522053
문서(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 script' 카테고리의 다른 글
[bash: sort, awk] 대용량 파일/디렉토리 찾기 (0) | 2021.09.24 |
---|---|
[bash: top, awk] CPU 또는 메모리 점유가 높은 프로세스 찾기 (0) | 2021.08.10 |
[bash: head] 여러개의 파일 내용을 출력하면서 파일명 함께 출력하기 (0) | 2021.07.13 |
[bash: grep] 특정 길이의 문자열에 대한 검색 (0) | 2021.07.06 |
[bash: stat, date] 현재 파일이 전송되고 있는 중인지 판단하기 (0) | 2021.06.28 |