perl 정규표현칙 치환 - 텍스트에서 주석문 제거하기
글쓴이: raymundo / 작성시간: 토, 2006/03/11 - 9:25오후
안녕하세요,
텍스트 파일을 읽고 작업을 하는 CGI코드를 수정하여, 그 텍스트 파일 안에 "#"으로 시작하는 주석을 달 수 있도록 하고 싶습니다. 텍스트를 읽는 루틴과 작업을 하는 루틴 사이에 적절하게 "주석문 제거 루틴"을 넣어서, 주석이 전혀 없는 것과 동일하게 만들어 준다면 뒷부분에 작업을 하는 루틴을 고칠 필요가 없겠죠.
원래의 텍스트 파일은 아래와 같이 한 쌍의 스트링 조합의 나열입니다
aaaaa AAAAAAAAAAAAAAAAAAAAAAA bbbb BBBBBBBBBBBBBBBBBB ccccccc CCCCCCCCCCCCCCC
주석은 다음과 같은 형태로 붙일 수 있게 하고자 합니다.
# 줄의 처음부터 #으로 시작하는 주석 aaaaa AAAAAAAAAAAAAAAAAAAAAAA # 앞에 공백이 있어도 허용 bbbb BBBBBBBBBBBBBBBBBB ccccccc CCCCCCCCCCCCCCC # 이렇게 내용이 있는 줄 뒷부분에 달린 주석
일단 파일을 읽는 루틴은, $data 라는 변수에 전체 텍스트가 다 들어갑니다. (한 줄씩 읽는 게 아니라)
아래는 제가 만든 치환 루틴입니다.
$data =~ s/^\s+//gm; # 각 라인의 앞에 공백 제거 $data =~ s/\s+$//gm; # 각 라인의 뒤에 공백 제거 $data =~ s/^#.*$//gm; # 샵으로 시작하는 라인 제거 $data =~ s/\s+#.*$//gm; # 공백 이후 샵으로 시작하면 거기서부터 라인 끝까지 제거
이렇게 했더니만, 치환 후의 결과가 아래와 같이 됩니다.
- 빈 줄!! aaaaa AAAAAAAAAAAAAAAAAAAAAAA - 빈 줄!! bbbb BBBBBBBBBBBBBBBBBB ccccccc CCCCCCCCCCCCCCC
즉 주석만 있던 라인들이 빈 줄로 존재하게 됩니다. 그래서 결과적으로 이후 작업을 하는 루틴에서 문제가 됩니다. 완전히 저 빈 줄까지 없애줘야 합니다.
그래서 치환 두 번을 더 해야 했습니다.
$data =~ s/^\s+//gm; $data =~ s/\s+$//gm; $data =~ s/^#.*$//gm; $data =~ s/\s+#.*$//gm; $data =~ s/(\r?\n)+/\n/gs; # 줄바꿈이 하나 이상 있으면 그걸 하나로 치환. 빈 줄제거. $data =~ s/^\r?\n//gs; # 텍스트 제일 앞에 빈 줄 제거
제가 테스트하기에는 일단 원하는 대로 빈 줄도 다 없애고 제일 처음의 텍스트 형식으로 되돌려 주는 것 같습니다만... 스트링을 여섯번이나 스캔하면서 치환을 한다는 것이 매우 기분이 나쁩니다! 텍스트가 그다지 긴 편이 아니라서 지금은 괜찮습니다만, 효율성 면에서도 안 좋을 것 같고요.
좀 더 깔끔하게, 적은 횟수의 치환으로 해결할 수 없을까요?
Forums:
sed를 이용하면 않되나요?
굳이 perl을 쓰지 않아도 된다면
sed 는 어떨까요?
$ sed -e "/^#\|^\s\+#/d; s/#.*$//" TARGET_FILE
깔끔하지 않나요?
ed 도 쓸만하구요..
放下着-----
내려놓으려는 마음도 내려놓기
perl이어야 합니다 ^^;
제 홈에 쓰고 있는 CGI 프로그램을 수정하는 거여서요... perl로 해야 합니다 ^^; 답변 감사드려요~
좋은 하루 되세요!
아마 이걸로 될 것 같습니다.
$data =~ s/(?:\r?\n|\r)?[ \t]*#.*?(?=\r?\n|\r)//g;
저는 펄은 못 하지만 펄 정규 표현식은 할 수 있습니다 -,.- 펄 5.8.7에서 잘 돌아 가는 듯 합니다.
- 토끼군
tokigun님 감사합니다.
정규표현식을 좀 공부했는데, 저 (?: (?= 등의 기호만 나오면 여전히 못 알아 듣겠더군요. ^^; 일단 그대로 가져다 테스트를 했습니다.
제가 원문에 언급했던 코멘트들은 정확히 제거해 주는 것 같습니다.
그런데 (제가 원문에서 빼먹은 탓입니다만), 중간 중간 가독성을 위해 빈 줄을 일부러 넣은 경우도 지원해 주길 원했는데 그 경우는 그냥 빈 줄이 나오더군요.
그래서 다른 건 그대로 쓰고 저 #.*? 부분을 다시 괄호로 묶었습니다.
혹시 제가 수정한 것 때문에 엉뚱한 부작용이 일어날 수 있을까요? 원문에 언급한 경우 이외의 것을 지울 수 있다던가... (아, 이 텍스트 파일에는 #이 전혀 들어가지 않습니다. 즉 #은 항상 주석을 위해 따로 넣은 경우만 있으니 주석이 아니면서 # 때문에 지워질 염려는 없음)
부작용이 없다면 실제로 홈에 적용할 텐데 아직 제가 고친 것을 제가 믿지를 못하겠군요 ^^; 한 번 살펴봐 주시면 감사하겠습니다. 번거롭게 해드려 정말 죄송합니다.
좋은 하루 되세요!
궁금해서 글 남깁니다만..
파일을 스칼라 변수에 읽어오도록 하는 특별한 이유가 있습니까? 배열에다 읽어와서 라인 단위로 처리하는 것이 일반적이라고 생각합니다만.. (perldoc -f grep 하시면 바로 나오는 예문처럼요.)
War doesnt determine whos right, just whos left.
제가 고치려는
제가 고치려는 프로그램이 UseModWiki 입니다. 그걸 제 홈에 설치한 다음, 제가 적당히 고쳐가면서 부족한 기능을 추가해서 사용하는 건데요.
파일 내용을 통채로 스칼라 변수로 담는 이유는,
첫째로 그 위키 소스가 원래 그렇게 짜여져 있기 때문입니다. ^^; 그러니 기존 코드의 수정을 최소한으로 하기 위해서 어떻게든 스칼라 변수에 담긴 상태에서 주석을 제거하고 다시 기존 코드에 넘기고 싶은 거고,
둘째로 그럼 왜 기존 소스는 그리 되었냐 하면... 그 스칼라를 다시 split 해서 한번에 해쉬변수 쌍으로 바꿔 버리거든요.
즉 파일을 읽어서,
$InterSite{"aaaaa"} = "AAAAAAAAAAAAAAAAAAAAAAA";
$InterSite{"bbbb"} = "BBBBBBBBBBBBBBBBBB";
$InterSite{"ccccccc"} = "CCCCCCCCCCCCCCC";
의 형태로 해쉬변수를 채워넣는 건데, 제가 테스트 해보니 중간에 빈줄이 들어가면 거기서부터 어긋나는지 제대로 채워지지 않더라고요. (제가 펄을 잘 아는게 아니라서 더 자세히는...) 그래서 아예 빈 줄을 없애버리는 과정을 여쭈는 거고요 ^_^
좋은 하루 되세요!
그런 사정이라면
그런 사정이라면 위에 토끼군님께서 제시하신 정규표현식을 사용하시면 되겠습니다. 다만 길이가 매우 긴 스칼라 변수에 대해 global match 치환하는 것은 비용이 매우 많이 들어가므로, 아래와 같이 쓰시면 속도상의 이점을 얻을 수 있습니다.
%InterSite = map { s/\s*#.*//; split /\s+/; } grep { /^[^ #]/ } split /\n/, $data;
# 또는 공백으로 시작하지 않는 라인에 대해서 # 문자 이후는 무시하도록 처리합니다.
War doesnt determine whos right, just whos left.
ai님 감사합니다.
허걱... 암호문 같습니다 ^^;;;
해석하는 순서가,
$data 를 \n을 구분자로 쪼개어 배열을 만들고
grep 을 서서 "공백 또는 #"이 아닌 글자로 시작하는 원소만 남기고,
남은 배열의 각 원소에 대하여,
공백(의 0번 이상반복)과 #으로 시작하는 경우 그 이하를 없애버리고,
공백을 구분자로 쪼개어 두 개로 가른 후에
앞의 것을 키로 뒤의 것을 값으로 해서 mapping 한다....
는 것이 되는 것으로 보이네요. (맞나요?^^)
도대체 어떤 경우 소괄호를 쓰고 어떤 경우 중괄호를 쓰는지는 예나 지금이나 영 이해가 안 됩니다만 ^^; 확실히 라인단위로 처리를 하는 게 비용은 훨씬 절감할 거라는 것은 알겠습니다~ 감사합니다~
좋은 하루 되세요!
좋은 하루 되세요!
ai님 한가지만 더 여쭙겠습니다..
"(공백)(공백)(공백)aaa(공백)(공백)AAAAA" 와 같이, 줄 앞에 공백이 있더라도 유효한 라인으로 인식을 시키고 싶습니다. 그러나 "[^ #]"을 "[^#]"으로 바꿔주기만 해서는 빈 줄이 남는 문제가 또 생겨서, 일단 "줄 처음에 있는 공백을 다 제거"한 후에 나머지 처리를 하기로 했습니다.
그래서
%InterSite = map { s/\s*#.*//; split /\s+/; } grep { s/^\s*//; /^[^#]/ } split /\n/, $data;
위와 같이 수정했습니다. grep 뒤 중괄호 안에, 공백 제거를 먼저 하고, 그 다음 #으로 시작하지 않는 라인만 잡아내라는 의미로 쓴 건데, 저렇게 쓴게 맞는지요? (제 테스트로는 잘 되는 것 같긴 한데 확신이 없어서...) 저 중괄호 안에 세미콜론을 썼을 때와 컴마를 썼을 때 어떤 차이가 있는지요?
좋은 하루 되세요!
수정하신 그대로
수정하신 그대로 사용하시면 원하시는 동작이 이루어 집니다. 중괄호 안에 들어가는 표현에 대해서는 매뉴얼대로
block 에 해당하는 것이므로 statement 를 나열한 것 뿐입니다. 최종적인 evaluation 결과가 참인 원소만 배열에서 추려내는 것이구요.
grep(1) 과는 다르게 EXPR, BLOCK 은 정규표현식이 아니라도 관계가 없습니다. 이를테면
한편 grep 과 다르게 map 은 주어진 LIST 의 원소를 직접 수정하기 때문에 주의하지 않으면 가끔 예상치 못한 결과를 가져오는 경우가 있습니다. map 은 foreach 와 같다고 생각하시면 편합니다.
참고로 perl 5.6 이전에서는 foreach 를 사용해 hash 의 value 를 변경하고자 할 때 perl 5.6 이후의 동작과 다른 부분이 있습니다. 자세한 내용은 perlfaq4 의 How do I process/modify each element of an array? 를 참고하세요.
War doesnt determine whos right, just whos left.
댓글 달기