2008. 4. 17. 09:26

원라인 펄 놀이 - 첫줄 빼고 sort

#perl 에서 saillinux님과 JEEN님의 "쉘에서 파일의 첫 줄을 빼고 소팅하려면"이라는 대화내용을 듣다가 이를 위한 몇가지 아이디어가 떠롤랐다.

파일의 첫줄을 출력한다 던가 마지막 줄을 출력하는 것은 head나 tail로 쉽게 할 수 있지만 첫 줄을 빼고 출력하는 방법은?

1. 다음과 같은 saillinux 님이 가르쳐 주신 위대한 방법도 있다.
   
tail -n `expr \`wc -l file-to-sort | awk '{ print $1 }'\` - 1` file.txt
오.. 왠만한 내공이 아니고는 만들기도 힘든 구문!! `` 안에 ``를 쓰는 센스!!
옛날 연구실의 한 선배는 awk 두개를 써서 더블 루프을 구현하기도 했었다.

그러나 다행히도 더 간단한 방법이 있다.

2. tail
tail -n +2 file.txt
언제부턴지는 모르겠지만 tail 에 "앞에서 몇줄부터" 출력해주는 기능이 생겼다.
간단히 +를 숫자앞에 붙이면 끝이다. 문제는, 이 기능이 언제부터 생긴건지 모르기 때문에 호환성이 좀 걱정된다는 사실.

3. sed
sed -n '2,$p' file.txt 
오래 전부터 이런 일은 awk,  sed 가 강자였지..

그러나 진정한 강자는 따로 있는 법

4. perl
perl -ne'$.>1 and print' file.txt
perl -ne'2..-1 and print' file.txt
perl -ne'2..eof and print' file.txt
오.. 가장 "리더블" 하지 않은가!!(물론, $.을 알아야 하지만)

이제 남은 건 sort 하는 일뿐!! 이건 뭐... 첫줄은 출력하고 나머지는 sort하면 된다.
head -1 file.txt; perl -ne'$.>1 and print' file.txt | sort

그러나 여기서 끝내기엔 너무 쉬운 내용을 너무 길게 썼다.
뭔가 더해보자.
소팅하고 출력하는 것을 펄 안에서 끝내보자.
목표는 : 텍스트를 다 배열에 저장하여 첫줄 빼고 소팅한 다음 출력한다.

perl -ne'push @L, $_;END{print $L[0],sort @L[1..$#L];}' file.txt

잘 되긴 하는데,, 뭔가 아쉬운 이 느낌은 뭘까, 너무 당연한 코드라서 재미가 없다.
게다가 매줄마다 push를 하는건,, 너무 비싸다는 생각이!!
모든 줄을 한꺼번에 배열에 넣을 방법은 없을까?
음, 이럴땐 첨부터 꽁수를 생각해보자.
perl -ne'$f=$_;@l=<>;print $f,sort@l;last' file.txt
오옷!! 이거 그럴 듯 하다!! 더 꼬아보자!!
 perl -ne'@l=<>;last}{print $_,sort@l' file.txt
매우 맘에 드는 코드가 나왔지만, 펄을 새로 시작하는 사람에게는 절대 보여줘선 안될 코드 같다.


오늘의 새로운 아이디어!!

1.  파일을 읽어서 배열에 넣는 방법
perl -ne'@L=($_,<>)' file.txt


2. perl -ne에서  END{}를 쓰기 귀찮다면 }{를 쓰면 된다.
perl -ne'push @L,$_ }{ print sort $L'




2007. 12. 11. 02:39

여러파일을 sorting 하기

갑자기 몇개 파일을 소트해야 할 일이 생겨서

하나의 파일을 sort 하는 방법

perl -i.bak -ne'push@L,$_;END{print sort @L;}' filename

여러 파일을 sort하려면

perl -i.bak -ne'push@L,$_;if(eof){print sort@L;@L=();}' file1 file2 file3

또는

for file in file1 file2 file3;do perl -i.bak -ne'push@L,$_;END{print sort@L;}' $file;done

더 좋은 방법은 당장 생각이 안난다.

find를 이용해서 만든 리스트들이 소트되어 있지 않아서 만든 것인데 실제로는 필요없는 디렉토리가 몇개 들어가 있었다. 필요없는 디렉토리들의 공통점은 숫자로 끝나지 않는다는 것이 었기 때문에 간단히

perl -i.bak -ne'/\d$/ or next;push@L,$_;eof and print sort@L and @L=();' input*10k.txt

다시 생각해 보니 펄을 안쓰는게 더 간단하다
for x in input*10k;do grep -v '[0-9]$' $x | sort > $x.tmp;mv $x.tmp $x;done;

이런....
뭐.. 더 빠르겠지 ㅎㅎ
2007. 11. 6. 15:02

리스트 비교

리스트를 비교하는 방법에는 "grep -f", "sort, diff", "comm" 등이 대표적이다.
"grep -f" 는 시간이 너무 오래 걸리는 단점이 있지만 diff, comm등은 강력한 도구이다.
하지막 역시 많은 파일과 복잡한 선택기준 앞에서 나의 가장 강력한 도구는 PERL!!

내가 주로 쓰는 커맨드를 소개한다면
perl -nle '/(regex)/ and $h[@ARGV]{$1}=$_;END{map{statement;}keys %{$h[0]}}' file1 file0
주의 할 점은 @ARGV(남은 파일 개수)을 인덱스로 사용하기 때문에 인자로 준 파일순서와 인덱스가 거꾸로라는 것.
위 예제에서 file1이 $h[1], file0 가 $h[0] 에 저장된다.(file1 과 file0의 순서에 주의)
statement를 위해서 여러가지 상상력을 동원할 수 있다.

간단한 예
perl -nle '/(\d{6}-\d{4})/ and $h[@ARGV]{$1}=$_;END{map{ exists $h[1]{$_} and print $h[1]{$_} }keys %{$h[0]}}' file1 file0

file1과 file0에서 123456-1234형태의 패턴(내가 주로 사용하는 런넘버-세그먼트넘버) 을 추출해서 file1과 file0의 공집합의 file1리스트를 출력한다.
비교할 리스트의 성질에 따라 코드는 많이 짧아질 수도 있다.
극단적으로 두 리스트의 똑같은 라인들만 검색하기 위해서는 다음과 같이 할 수도 있다.

perl -e '@s{`cat OLD`}=( ); exists $s{$_} && print for `cat NEW`' fire1 file2
2007. 11. 5. 20:23

한꺼번에 많은 파일 지우는 세가지 방법

원래 펄 사용의 주 목적이 리스트 및 파일 관리였기 때문에 다양한 요구조건에 대해서 가장 효율 적인 방법은 일일이 스크립트파일을 만드는 것 보다 awk, 나 sed 처럼 커맨드라인에서 바로 코드를 만드는 방법이라고 생각하고 있었다.  덕분에 프로그램을 만들때 조금만 길이기 길어져도 헤매는 부작용이 생기기는 했지만.

그 전까지는 그냥 상상력에 의존하여 나름 필요한 코드를 만들어오곤 했었는데 다음 코드를 보고는 그 포스에 감동해버렸다.
find ./ -name "*.bak" -type f| perl -nleunlink  # 코드 1
현재 디렉토리 아래의 모든 *.bak파일을 지우는 코드.

한 디렉토리에 지워야 할 파일이 매우 많을때(1024개 이상?)
rm -f *.bak        # 코드 2
는 작동하지 않는다. 이때 위의 perl 코드1은 잘 동작한다.(물론 코드1은 하위디렉토리까지 검색한다는 차이는 있다.)
 perl이외에도 방법은 있다.
find ./ -name "*.bak" -type f -exec rm -f \{\} \;       #코드3
find ./ -name "*.bak" -type f | xargs rm -f                   #코드4
코드3의 문제는 모든 파일마다 rm을 실행하므로 매우 느리다는 데 있다.
코드4의 문제는 xargs를 이용하기 때문에 rm을 호출하는 횟수가 더 적어진다.
코드1의 경우 단 하나의 perl 프로세스를 이용하여 파일을 지우기 때문에 이방법이 가장 빠를 것으로 예상할 수있다.

사실 코드4의 xargs 가  코드1의 perl보다 그리 느릴꺼라고 생각하지는 않는다. 적어도 한 프로세스로 100개이상의 파일을 처리할 수 있다면 소모되는 시간은 파일을 삭제 하는데 걸릴 시간이기 때문이다. 게다가 코드1은 사실상 perl의 아름다움을 보여주기 위하여 좀 과장된 측면이 있다. 예를 들어 처리중에 지우지 못하는 파일이 있어도, 혹은 아예 없는 파일이 리스트에 주어져도 아무런 경고를 내지 않는다. 이를 해결하기 위해서 우리는 "die" 나 "print" 또는 "warn"등의 구문을 추가해야한다.
find ./ -name "*.bak" -type f | perl -nle'unlink or die"Can't remove $_ : $!"'
빠른 방법이라고는 하지만 이제 코드쓰기가 매우 귀찮아졌다. 나라도 xargs가 쓰고싶어 질 것이다. (실제로 그러고 있다.) 하지만 반대로 생각해볼 수 있다. 지우지 못하는 파일에대한 경고를 추가했던 것처럼 우리는 필요한 무엇이든 추가할 수 있다. 텍스트 파일만 지워보자.
find ./ -name "*.bak" -type f | perl -nle'-T and unlink or print "Can't remove $_ : $!"
 find 의 옵션들을 perl로 옮겨 더욱 강력하게 만들 수도 있다.
find | perl -nle '/\.bak$/ && -f && -T and unlink or print "Can't remove $_ : $!"
(파일 테스트 연산자가 제대로 맞는지 잘 기억이...)
(주의: && and 등의 연산자가 헷갈린다면 언제나 괄호와 if 가 우리 앞에 있음을 있지 말자)

정말 지워야할 파일이 많을 수록. 지워야 할 파일을 선택하는데 기준이 복잡할 수록, 위의 코드 한줄이 점점 짧게 보일 것이다.

'Perl Recipe' 카테고리의 다른 글

두 문자열에서 중복되는 부분 찾기  (0) 2009.03.25
일전한 글자수의 단어 세기  (7) 2009.03.24
원라인 펄 놀이 - 첫줄 빼고 sort  (0) 2008.04.17
여러파일을 sorting 하기  (0) 2007.12.11
리스트 비교  (0) 2007.11.06