컴퓨터 엑셀 워드 포토샵 구글어스 WINDOWS JAVASCRIPT JAVA C++

 
Wednesday, November 29, 2006

Perl/펄] 파일의 중복된 행 지우기; 같은 줄, 동일 값 제거, Remove Duplicate Lines Text File


텍스트 파일에서, 같은 내용의 줄이 여러 번 중복되어 있는 경우에, 그 중복된 줄을 모두 지우고 하나씩만 남기는 방법입니다.

즉 파일의 데이터를 정돈하는 스크립트입니다.

delDupLine.pl test.txt
이렇게 명령행 옵션으로 텍스트 파일을 지정해 주면, test.txt 에서 중복된 줄을 모두 지우고 유일한 줄만 화면에 출력합니다.


화면 출력을 다시 텍스트 파일로 저장하려면, 즉 "새 이름으로 저장(Save As)" 하려면
delDupLine.pl test.txt > out.txt
이렇게 재지향(Redirection)을 사용하면 됩니다.


텍스트 파일의, 중복 라인(데이터) 삭제 프로그램


파일명: delDupLine.pl
#!/usr/bin/perl
use strict; use warnings;

  foreach (<>) {
    chomp;
    print "$_\n" unless $_{$_}++;
  }



테스트용으로 사용할 텍스트 파일: test.txt
foo
foo
foo
bar

0
0
0
1
1
1
0
0
0

맹구는 복숭아를 먹었습니다.
맹구는 복숭아를 먹었습니다.
맹구는 복숭아를 먹었습니다.

1twyw
2yeyeye
3tuteute
4646464
557575
6yeyt
7ryryre
8ytrwyrw
968486
065yrhdhd

2yeyeye
6yeyt
2yeyeye

AAAAAAAAAAAAAA
BBBBBBBBBBBB
CCCCCCCCCC
EEEEEEEEEE
EEEEEEEEEE
FFFFFFFF
GGGGGG
HHHHHHH
IIII
JJJJJJ
KKKKKKK
LLLLLLL
MMMMMMM
NNNNNNN


AAAAAAAAAAAAAA
GGGGGG
HHHHHHH
IIII
JJJJJJ

자장면
자장면
자장면
탕수육
탕수육
자장면
탕수육
짬뽕
맹구는 복숭아를 먹었습니다.


GGGGGG
GGGGGG
GGGGGG
GGGGGG



실행 결과:
(윈도우에 액티브펄(ActivePerl)을 설치한 후, 실행한 결과임)
D:\Z>delDupLine.pl test.txt
foo
bar

0
1
맹구는 복숭아를 먹었습니다.
1twyw
2yeyeye
3tuteute
4646464
557575
6yeyt
7ryryre
8ytrwyrw
968486
065yrhdhd
AAAAAAAAAAAAAA
BBBBBBBBBBBB
CCCCCCCCCC
EEEEEEEEEE
FFFFFFFF
GGGGGG
HHHHHHH
IIII
JJJJJJ
KKKKKKK
LLLLLLL
MMMMMMM
NNNNNNN
자장면
탕수육
짬뽕

D:\Z>


중복된 줄이 말끔히 지워지고, 유일한 줄만 남았습니다.



코드 설명


  foreach (<>) {
    chomp;
    print "$_\n" unless $_{$_}++;
  }


소스 자체는 어처구니 없을 정도로 짧고 간단한데, 알고리즘이랄까 내용은 좀 복잡합니다.

foreach (<>) {...
명령행 옵션으로 입력해 준, 텍스트 파일을 1행씩 읽어, 기본 변수인 $_에 차례로 담습니다.

chomp;
라는 것은, 라인 끝의 개행문자를 제거합니다. 개행문자(줄바꿈 문자)를 제거하지 않으면, 줄 끝의 개행문자의 유무에 따라, 같은 문자열도 다른 문자열로 간주되는 문제가 생깁니다. chomp 뒤에 아무것도 적어 주지 않으면, 기본 변수인 $_ 의 끝에서 개행문자를 지웁니다.


print "$_\n" unless $_{$_}++;

unless 라는 것은 if문의 반대입니다. 조건에 맞지 않으면 뭘 하라는 뜻인데, 여기서는 $_{$_}++ 라는 조건이 거짓(undef; 초기화되지 않은 값)이라면 print문을 실행하라는 뜻입니다. 플러스 플러스(++)로 1 이상이 되면, 아까 처리했던 줄이니 출력하지 말라는 것입니다.



더 자세한 설명은 다음과 같습니다:


기본 디폴트 해쉬인 %_ 의 변수적 표현인 $_{} 이곳에
$_{현재 행}
이렇게 현재 행을 통째로 키(key)로 삼아 집어 넣고, 해쉬의 그 요소의 값(value)을 +1 하여 증가시킵니다. (뭘 계산하기 위해서 증가시키는 것이 아니고, 참/거짓의 논리값을 얻기 위한 트릭입니다.)

만약 처음 마주친 줄이라면, 그 해쉬 요소의 값은 초기화되지 않은 undef 즉 null 이기에 논리값이 거짓(false)입니다. 거짓이면 그 줄이 print 됩니다. (unless 는 if의 정반대이기에)

만약 아까 보았던 줄이라면, 즉 "중복된 줄"이라면, 그 해쉬의 값이 ++ 에 의해 undef 에 1이 더해져서 1이나 1이상의 값이 되어 있을 것입니다.

해쉬의 값이 1이나 1 이상일 경우, 펄에서 0 은 거짓, 0이 아닌 숫자는 참이기에, 그 중복된 줄은 "참(true)"이 되어 print 되지 않습니다. (unless 는 if의 정반대이기에)

따라서, 결국 유일한 줄, 즉 유니크(Unique)한 줄만 프린트되고, 중복된(Duplicated) 줄은 자연스레 무시되는 것입니다.


자장면 : 현재 $_{자장면} 의 값은 undef
자장면 : 이제 $_{자장면} 값은 1. 앞의 자장면에서 +1 했기에
자장면 : $_{자장면} 의 값은 이제 2로 증가
탕수육 : $_{탕수육} 은 처음 나왔으니 undef


위의 경우, 맨 처음 나온 "자장면"이라는 줄만 출력되고, 다음 줄의 자장면들은 모두 무시됩니다. 왜냐하면 2번째 "$_{자장면}" 부터는 값이 undef 이 아니기 때문입니다.





중복된 행만, 별도로 추출하는 스크립트:
출처: https://www.perlmonks.org/?node_id=592934
파일명: test2.pl
사용법: test2.pl A.txt
#!/usr/bin/perl
use strict; use warnings;

my %duplicates;

while (<>) {
    chomp;
    $duplicates{$_}++;
}

foreach my $key (keys %duplicates) {
    if ($duplicates{$key} > 1) {
        delete $duplicates{$key};
        print "$key\n";
    }
}



2개의 파일을 비교해, 파일B에 있는 행이, 파일A에서 발견되면 삭제한 후, 파일A 출력하기:
출처: https://stackoverflow.com/questions/14931656/using-perl-i-want-to-compare-two-files-and-how-do-i-keep-unique-lines-from-first
파일명: test3.pl
사용법: test3.pl A.txt B.txt
참고: test3.pl 자체에도, 파일 A의 중복된 행을 제거하는 기능이 있음.
#!/usr/bin/perl
use strict; use warnings;

my %skip;
{
   open(my $fh, '<', $ARGV[1])
      or die("Can't open \"$ARGV[1]\": $!\n");

   while (<$fh>) {
      chomp;
      ++$skip{$_};
   }
}

{
   open(my $fh, '<', $ARGV[0])
      or die("Can't open \"$ARGV[0]\": $!\n");

   while (<$fh>) {
      chomp;
      print "$_\n" if !$skip{$_}++;
   }
}

바로 위에 있는 2가지 코드 실행 결과 화면
D:\Z>type A.txt
111
222
111
111
333
444
555
666
D:\Z>
D:\Z>test2.pl A.txt
111

D:\Z>
D:\Z>
D:\Z>test2.pl A.txt > B.txt

D:\Z>test3.pl A.txt B.txt
222
333
444
555
666

D:\Z>





파일에서, 중복된 빈 줄들만 삭제하는 방법: ▶▶ 펄/Perl] 여러 개의 빈줄 제거, 하나의 빈줄로 합치기, Collapse Multiple Blank Lines Into One




tag: perl
Perl | 펄

5 Comments:
At January 17, 2018 at 6:23 PM, Blogger Unknown said...

해당 펄스크립트 경우 중복 된 행중 하나는 남아 있는 결과를 얻게 됩니다.
중복 되었던 항목을 모두 지우려면 어떻게 해야 할까요?

 
At January 17, 2018 at 8:27 PM, Blogger mwultong said...

test.txt 중에서, 어떤 행이 남아 있지요?

제가 지금 테스트해 보니, 남는 행이 없이 중복행들이 잘 제거됩니다.

ActivePerl의 현재 최신 버전인 5.24.3.2404 가 설치된 64비트 윈도우7에서, delDupLine.pl 을 실행해도 잘되고

옛날 버전인 5.12.4.1205 가 설치된 윈도우XP에서 위의 스크립트를 실행해도 잘 되었습니다.

 
At January 18, 2018 at 9:02 AM, Blogger Unknown said...

test.txt 파일을 예로 든것이 아니었는데 설명이 부족했었습니다.
111
222
111
111
333
444
555
666
위와 같은 내용의 파일이 있을때 중복된 111은 한행도 남기지 않고 지워지고 중복되지 않은 행들만 출력해주려면 어떻게 해야 하는지 질문 했었습니다.
쉘만 다루다가 perl로 와봤는데 여긴 진짜 신세계네요 ㅠㅠ 도움 주시면 감사하겠습니다.

 
At January 18, 2018 at 1:00 PM, Blogger mwultong said...

간단한 작업이 아닌 것 같습니다 ㅠㅠ
좀 복잡한 코드를 작성해야 할 것 같네요..

[1] 중복된 행만, 별도의 파일(파일B)로 추출한 후에

[2] 그 별도 파일(파일B)의 데이터를 사용해서, 원본 파일(파일A) 속의 행들을 지워야 할 것 같습니다..

필요한 코드 2개를 포스트 본문 끝에 올렸습니다.

----------

수정: 방금 올린, 1번째 코드에 문제가 있어서, 정정한 후 다시 올렸습니다.

 
At January 18, 2018 at 3:10 PM, Blogger Unknown said...

정말 큰 도움이 되었고, 감사드립니다. 누군가 perl과 sed만 잘 사용하면 유닉스 스크립트의 모든걸 아는거라고 했던 말이 생각나네요. 그만큼 쉘보다 빠르고 확장성이 넓은 거 같아서 감탄 했습니다. 다시 한번 감사드리고 블로그는 자주 들러 공부할때 활용하도록 하겠습니다. 좋은 하루 보내시기 바랍니다. 꾸벅 (__ )

 

Post a Comment

<< Home RSS 2.0 feed

구글 Google 에서 제공하는 무료 블로그 서비스인 블로거 Blogger 의 인터넷 주소는 www.blogger.com 입니다. Blogger 에 블로그를 만들면, blogspot.com 이라는 주소에 블로그가 생성됩니다.
블로그를 직접 방문하지 않고도 최신 게시물을 구독하려면 RSS 2.0 feed 주소를 리더기에 등록하시면 됩니다.
Previous Posts
Monthly Archives
Top