문자열 바꾸기(String Substitution): sed vs perl

앞으로는 이 작업을 할 때 sed 는 완전히 접어야만 하겠다. sed 가 강력한 도구이긴 하지만, delimiter 에 문제가 있어, 여러모로 신경써야할 점이 많기 때문이다.

이게 뭔 소리냐 하면, sed 의 기본 delimiter(구분자)는 /, 즉 Slash(우리말로는 ‘빗금‘)가 기본인데, 만약 Pattern 이나 Substitution 에 / 가 들어가 있으면 오류가 발생하기에, 주의를 해야한다는 뜻이다.

다음 예를 보자.

  • 원래 문자열 : 나는/너를/정말/미워해
  • 바꿀 문자열 : 나는/걔를!\정말/미워해

슬래시와 백슬래시(안타깝게도, \ 는 우리말 번역을 찾질 못했다.)가 복잡하게 들어가 있는데, 이걸 그냥 무식하게 다음과 같이 바꾸려고 해보면, (당연히) 오류가 발생한다.

위 상황은, ‘너를/’ 을 ‘걔를!\’ 로 변환하려 하고 있다.

echo "나는/너를/정말/미워해" | sed "s/너를//걔를!\/g"
bash: !\/g: event not found

당연히 오류가 날 수 밖에 없다. / p가 구분자인데, 패턴에도 / 가 들어가 있으니, sed 입장에선 헷갈릴 수 밖에. (사실, 그 문제 이전에, \ 가 들어가 있어서 더더욱 오류가 난다.)

sed 를 사용하여 이 문제를 회피하기 위해선, 구분자를 바꿔줘야만 한다.
단지 구분자만 바꿔선 또 안되고, 특수문자가 들어가있기 때문에 이스케이핑도 그야말로 복잡하게 해야만 한다.

#겹따옴표
echo "나는/너를/정말/미워해" | sed "s^너를/^걔를\!\\\\^g"
나는/걔를!\정말/미워해

#홑따옴표
echo "나는/너를/정말/미워해" | sed 's%너를/%걔를!\\%g'
나는/걔를!\정말/미워해

이스케이프 처리하다가 세월 다 가겠다.

변수 처리를 하면 어떨까?

pat='너를/'
subs='걔를!\\'
echo "나는/너를/정말/미워해" | sed "s%${pat}%${subs}%g"
나는/걔를!\정말/미워해

제대로된 결과를 얻으려면 역시나 이스케이프를 신경써야 하고, 구분자를 원문에 있지 않는 문자로 바꿔줘야만 한다.
즉, 신경써야하는 점은 마찬가지다.

아래처럼 / 를 구분자로 쓰면 오류가 발생한다.

echo "나는/너를/정말/미워해" | sed "s/${pat}/${subs}/g"
sed: -e expression #1, char 11: `s' 에 관한 알수 없는 옵션

이래 저래, sed 를 쓰기는 좀 번거롭다.
물론, 여기에서 겹따옴표를 쓰느냐 홑따옴표를 쓰느냐에 대한 구분도 있어야 한다.
홑따옴표를 쓰면 모든 Expansion($를 써서 변수명을 표현하거나, 기타 등등)이 불가능하고 문자 그대로 해석되므로, 홑따옴표가 더 나은 선택이 된다.

그러나, sed 의 특성 상, 여전히 \ 는 특수 문자로 인식되므로(Capture Group 으로 사용 등등), 여전히 이스케이프 문제는 고려해야만 한다.


perl 은 어떨까? 훨씬 더 유연하다. 특히 변수를 사용하면 더더욱.
무엇보다, 그냥 / 를 구분자로 쓸 수 있다는데 강점이 있다. 물론, 이스케이프는 해줘야만 한다.

변수 처리를 하지 않는다면, sed 나 perl 이나 별 다른 점은 없다.

➜ echo "나는/너를/정말/미워해" | sed 's%너를/%걔를!\%g'
나는/걔를!\정말/미워해

➜ echo "나는/너를/정말/미워해" | perl -pe 's%너를/%걔를!\%g'
나는/걔를!\정말/미워해

➜ echo "나는/너를/정말/미워해" | perl -pe 's/너를//걔를!\\/g'
Unrecognized character \xEA; marked by <-- HERE after s/너를//<-- HERE near column 11 at -e line 1.

당연히, / 를 구분자로 쓸 수도 없다.
하지만, 변수에 넣었을 땐 상황이 달라진다.

➜ pat='너를/'
➜ subs='걔를!\'
➜ export pat subs

# 홑따옴표 사용
➜ echo "나는/너를/정말/미워해/perl" | perl -pe 's/$ENV{pat}/$ENV{subs}/g'
나는/걔를!\정말/미워해/perl

# 겹따옴표 사용
➜ echo "나는/너를/정말/미워해/perl" | perl -pe "s/$ENV{pat}/$ENV{subs}/g"
나는/너를/정말/미워해/perl

위에서 눈여겨 봐야할 부분은 두가지. 첫번째는 export 항목. 두번째는 홑따옴표 사용.
여기서 겹따옴표를 쓰면 제대로된 결과가 나오지 않는다.

홑따옴표를 썼는데도 Expansion 이 이뤄졌다고 생각할 수도 있지만, 그건 그냥 perl 문법이라고 생각하는게 속 편하다.

$ENV{변수명} 은, perl 에서 Shell 변수를 사용하고자 할 때 쓰는 문법이다. $ 도 붙어있고, {} 도 있으므로, Shell Expansion 이라고 생각할 수 있지만, 그게 아니고 Perl 예약어로 생각하고, 그냥 홑따옴표를 쓴다고 이해하면 되겠다.

문제는 홑따옴표보다는 export 에 있다. 결론부터 말해서, export 를 하지 않으면 perl 에서 저 변수들을 사용할 수가 없다. 이에 관해선 예전에 쓴 글을 참고하고..
간단히 말해서, perl 은 현재 echo 가 실행된 shell 에서 보면 하위 프로세스가 되기 때문에 상위에서 생성된 변수가 거기까지 도달하지 못한다. 그를 위해 export 를 해줘서 전달해줘야 한다.


결론!!!

sed 나 perl 이나, 이스케이프를 해줘야한다는 점에선 다를게 없지만, 변수를 사용한다는 전제 하에, 기본 구분자인 / 를 어떤 문자열에도 사용할 수 있다는 점에서 perl 을 사용하는 게 맞다고 본다.

따라서,
Shell 스크립트에서, 문자열 바꾸기를 시행할 때, sed 보다는 perl 을 사용하라! (속도에 얼마나 차이가 있을 지는 모르겠고..)

주의점은 다음과 같다.

  • 변수를 사용하여 pattern, substitution 등을 미리 할당한다.
  • 변수 포함, 모두 홑따옴표를 기준으로 한다.
  • export 로 변수를 하위 프로세스까지 전달한다.
  • pattern, substitution 에 빗금(/)이 포함되어 있더라도, 그냥 / 을 구분자로 사용해도 된다.
export pattern subs
echo "어쩌고 저쩌고..." | perl -pe 's/$ENV{pattern}/$ENV{subs}/g'
perl -pe 's/$ENV{pattern}/$ENV{subs}/g' <<< echo "어쩌고 저쩌고..."
perl -pe 's/$ENV{pattern}/$ENV{subs}/g' file

등등..

또는, 간단한 찾기/바꾸기라면, 그냥 Shell Expansion 을 사용하는게 더 좋은 선택일 수 있겠다.

Author: 아무도안

안녕하세요. 글 남겨주셔서 고맙습니다.