[스크립트] sed 명령 사용하기

에디터 선택하기

유닉스에는 파일을 편집할 수 있는 여러 옵션들이 있다. 지금도 머리에 vi, emacs, jed 등이 떠오른다. 각자(키 바인딩을 포함하여) 선호하는 에디터가 있을 것이다. 이러한 에디터를 사용하여 유닉스를 관리하고, 프로그래밍을 쉽게 수행할 수 있다.

대화형(interactive) 에디터가 좋기는 하지만 한계도 있다. 대화형이란 특징이 강점이 될 수 있지만, 또한 약점도 될 수 있다. 파일 그룹에 비슷한 유형의 변경을 수행해야 한다고 생각해 보라. 당장 좋아하는 에디터를 사용하여 반복적이면서 시간이 많이 걸리는 편집 작업을 직접 수행할 수는 있을 것이다. 하지만 더 나은 방법이 있다.

sed

파일 편집 과정을 자동화하여 파일 편집을 일괄로 처리하거나, 기존 파일을 변경할 수 있는 고급 기능을 가진 스크립트를 작성할 수 있다면 참 좋을 것 같다. 다행히도 우리에게 “sed”가 있다.

sed는 리눅스 및 거의 모든 유닉스 제품군에 포함된 경량의 스트림 에디터이다. sed는 좋은 기능들이 많이 갖추었다. 우선, 매우 가볍다. 기존 스크립팅 언어보다 훨씬 작다. 둘째, sed는 스트림(stream) 에디터이기 때문에 stdin에서 받은 데이터를 편집할 수 있다. 따라서 편집할 데이터를 디스크 상의 파일에 저장할 필요가 없다. 데이터는 sed로 쉽게 연결(pipe)될 수 있기 때문에 강력한 쉘 스크립트의 길고 복잡한 파이프라인의 일부로서 sed를 쉽게 사용할 수 있다. 한번 시도해보라.

GNU sed

리눅스 사용자들에게 가장 적합한 sed 버전들 중 하나가 GNU sed일 것이다. 현재 버전은 3.02 이다. 모든 리눅스 배포판에는 GNU sed가 있다. GNU sed는 소스가 무료로 배포되고, POSIX sed 표준에 대한 확장도 많기 때문에 대중적으로 사용된다. GNU sed는 라인 길이 제한 같은 이전의 상용 버전의 한계에서 벗어나서 어떤 라인이라도 쉽게 다룰 수 있다.

최신 GNU sed

이 글을 쓰면서 많은 sed 사용자들이 GNU sed 3.02a를 참조하고 있다는 것을 알았다. 이상하게도 나는 ftp.gnu.org(참고자료)에서 sed 3.02a 를 찾지 못했다. 그래서 다른 곳을 찾아야 했다. 결국 /pub/sed의 alpha.gnu.org에서 찾았다. 이것을 다운로드하여 컴파일하고 설치하자. 5분 후에 최신 버전의 sed, 3.02.80이 생겼다.

올바른 sed

이 시리즈에서는 GNU sed 3.02.80을 사용한다. 이 글에서는 물론이고 앞으로의 시리즈에서도 GNU sed 3.02나 3.02a 는 사용하지 않을 예정이다. 비 GNU sed를 사용하고 있다면 결과는 다양하다. 시간을 잠깐 들여서 GNU sed 3.02.80를 설치해보는 것은 어떨까? 이후 시리즈를 읽을 때도 실질적으로 도움을 받을 수 있고, 최고의 sed를 경험해보는 놀라운 기회가 될 테니 말이다.

sed 예제

sed는 인풋 데이터에 대해 사용자가 지정한 편집 연산(“commands”)을 수행하여 작동한다. sed는 라인 기반(line-based)이기 때문에 명령어는 순서대로 각 라인에서 수행된다. sed는 결과를 표준 아웃풋(stdout)에 작성한다. 어떤 인풋 파일들도 수정하지 않는다.

예제를 보도록 하자. sed가 중요한 태스크를 어떻게 수행하는가 보다는 sed가 어떻게 작동하는지를 설명할 것이기 때문에 먼저 제공되는 코드들은 약간 이상하다. 하지만 sed가 처음이라면 이것을 이해하는 것도 중요하다.

$ sed -e ‘d’ /etc/services

이 명령어를 입력하면 아웃풋이 없다. 왜 그럴까? 이 예제에서 편집 명령어인 ‘d’와 함께 sed를 호출했다. sed는 /etc/services 파일을 열고, 패턴 버퍼로 라인을 읽어 들이고, 편집 명령어(“delete line”)를 수행한 다음, 패턴 버퍼를 프린트했다.(비어있다.) 그리고 나서 후속 라인들에 대해서도 이 같은 단계들을 반복했다. 이것은 어떤 아웃풋도 만들지 않았다. ‘d’ 명령어는 패턴 버퍼에서 모든 라인을 영구히 삭제하기 때문이다.

이 예제의 포인트는 두 가지이다. 우선, /etc/services는 절대 변경되지 않았다. 이것은 sed는 이것을 인풋으로서 사용하여 명령행 라인에서 여러분이 지정한 파일에서 읽기만 한다. 파일을 수정하지 않는다. 두 번째로 알아야 할 것은 sed는 라인(line) 지향이란 점이다. ‘d’ 명령어는 sed에게 모든 인커밍 데이터들을 삭제하라고 명령하지 않는다. 대신 sed는 /etc/services의 각 라인을 하나씩 내부 버퍼(패턴 버퍼)로 읽어 들인다. 일단 라인이 패턴 버퍼에서 읽혀지면 ‘d’ 명령어가 작동하고 패턴 버퍼의 내용을 프린트한다. (이 예제에서는 아무것도 없었다.) 나중에 명령어가 적용될 라인을 정하는 주소 범위에 대해 설명하겠다. 주소가 없다면 명령어는 모든 라인에 적용된다.

마지막으로, ‘d’ 명령어에 싱글 쿼트를 사용한다는 것이다. sed 명령어에는 싱글 쿼트를 사용하는 습관을 들이는 것이 좋다.

기타 sed 예제

다음은 sed를 사용하여 아웃풋 스트림에서 /etc/services 파일의 첫 번째 줄을 제거하는 방법을 보여주는 예제이다.

$ sed -e ‘1d’ /etc/services | more

여러분도 보다시피 이 명령어는 첫 번째 ‘d’ 명령어와 매우 비슷하다. 앞에 숫자 ‘1’이 붙은 것만 다르다. ‘1’ 이 라인 번호를 의미한다고 생각했다면 여러분이 맞다. 첫 번째 예제에서는 ‘d’만 사용했다. 이번에는 앞에 숫자(주소)가 붙은 ‘d’ 명령어를 사용한다. 이 주소를 사용하면 sed에게 특정 라인 또는 라인들에서만 편집을 수행하도록 명령할 수 있다.

주소 범위

이제 주소 범위를 설정하는 방법을 설명하겠다. 이 예제에서 sed는 아웃풋의 1번부터 10번 라인을 삭제한다.

$ sed -e ‘1,10d’ /etc/services | more

두 개의 주소를 콤마로 구별하면 sed는 첫 번째 주소부터 두 번째 주소까지 명령어를 적용한다. 이 예제에서 ‘d’ 명령어는 1에서 10까지의 라인에 적용되었다. 다른 라인들은 무시된다.

정규식이 있는 주소

이제 보다 유용한 예제를 생각해보자. /etc/services 파일의 내용을 보고싶지만, 여기에 삽입된 주석에는 별 관심이 없다면? 알다시피 /etc/services 파일에서 주석을 표시할 때, ‘#’문자를 가진 라인에 붙인다. 주석을 보지 않으려면 sed에게 ‘#’으로 시작하는 라인을 삭제하도록 한다.

$ sed -e ‘/^#/d’ /etc/services | more

어떤 일이 일어나는가? sed가 거뜬히 원하는 작업을 수행한다. 어떻게 이것이 가능한지 분석해보자.

‘/^#/d’ 명령어를 이해하려면 하나씩 분리해서 생각해보자. 우선 ‘d’를 제거해보자. 떼어내고 보니 ”/^#/’가 남아있다. 이것은 새로운 유형의 정규식 주소이다. 정규식 주소에는 언제나 슬래시가 사용된다. 이들은 패턴(pattern)을 지정하고, 정규식 주소를 바로 뒤따르는 명령어가 이 특정 패턴과 매치될 경우 라인에 적용된다.

따라서, ”/^#/’는 정규식이다. 하지만 이것이 무슨 일을 하는가? 정규식에 대해 좀 더 알아보자.

정규식

우리는 정규식을 사용하여 텍스트의 패턴을 표시한다. 쉘 명령행에서 ‘*’ 문자를 사용했다면 같은 것은 아니지만, 정규식과 비슷한 것을 사용했던 것이다. 다음은 정규식에 사용할 수 있는 특수 문자이다.

문자        설명

^        라인의 시작을 찾는다.

$        라인의 끝을 찾는다.

.        한 개의 문자를 찾는다.

*        이전 문자의 0 또는 그 이상 발생한 선행 문자를 찾는다.

[ ]        [ ]안에 있는 모든 문자들을 찾는다.

정규식을 이해하려면 예제만큼 좋은 것이 없다. 이 모든 예제들은 sed에서 허용된다. 명령어 왼쪽에 유효 주소가 나타나도록 한다.

정규식

표현식        설명

/./        최소한 한 개 이상의 문자를 포함하고 있는 라인을 찾는다.

/../        최소한 두 개 이상의 문자를 포함하고 있는 라인을 찾는다.

/^#/        ‘#’으로 시작하는 라인을 찾는다.

/^$/        모든 공백 라인을 찾는다.

/}^/        ‘}’ (공간 없음)로 끝나는 라인을 찾는다.

/} *^/        0 또는 그 이상 공간이 뒤에 붙는 ‘}’ 로 끝나는 라인을 찾는다.

/[abc]/        소문자 ‘a’, ‘b’, ‘c’를 포함하고 있는 라인을 찾는다.

/^[abc]/        ‘a’, ‘b’, ‘c’로 시작하는 라인을 찾는다.

이 예제를 시도해 보기 바란다. 시간을 들여 정규식을 익히기 바란다. 그리고 정규식을 직접 만들어보라. regexp를 다음과 같이 사용할 수 있다.

$ sed -e ‘/regexp/d’ /path/to/my/test/file | more

여기에서 sed는 매치된 라인을 삭제하게 될 것이다. sed에게 regexp 매치들을 프린트(print)하고, 매치하지 않는 것을 삭제하라고 명령하여 정규식에 익숙해 지는 것이 다른 어떤 방법들 보다 쉽다.

$ sed -n -e ‘/regexp/p’ /path/to/my/test/file | more

새로운 ‘-n’ 옵션을 주목하라. 명확한 명령이 없는 한 패턴을 프린트하지 말 것을 sed에게 명령한다. ‘d’ 명령어를 ‘p’ 명령어로 대체했다는 것을 알게 될 것이다. 이것은 명확하게 sed에게 패턴 공간을 프린트할 것을 명령하고 있다. 당연히 매치들만 프린트된다.

주소

지금까지 라인 주소, 라인 범위 주소, regexp 주소를 살펴보았다. 하지만 더 많은 가능성이 있다. 두 개의 정규식을 콤마로 분리하여 지정할 수 있다. sed는 첫 번째 정규식을 찾는 첫 번째 라인부터 시작하여, 두 번째 정규식을 찾는 라인을 포함할 때까지 모든 라인들을 찾을 것이다. 예를 들어, 아래 명령어는 “BEGIN”을 포함하고 있는 라인으로 시작해서, “END”를 포함하는 라인으로 끝나는 텍스트 블록을 프린트 할 것이다.

$ sed -n -e ‘/BEGIN/,/END/p’ /my/test/file | more

“BEGIN”을 찾지 못하면 어떤 데이터도 프린트되지 않는다. 그리고 “BEGIN”은 찾았지만, “END”를 찾지 못했다면 모든 라인들은 프린트되지 않는다. 이것은 sed가 스트림 지향 에디터이기 때문이다.

C 소스 예제

C 소스 파일에서 main() 함수만 프린트하려면 다음을 입력한다.

$ sed -n -e ‘/main[[:space:]]*(/,/^}/p’ sourcefile.c | more

이 명령어는 두 개의 정규식, ‘/main[[:space:]]*(/’ and ‘/^}/’, 한 개의 명령어 ‘p’를 갖고 있다. 첫 번째 정규식은 “main” 과 그 뒤를 따르는 공간과 탭 그리고 열린 괄호를 찾는다. 이것은 ANSI C main() 선언의 시작을 찾는다.

이 정규식에서 ‘[[:space:]]’ 문자 클래스를 주목하라. 이것은 sed에게 탭 또는 공간을 찾도록 명령하는 특별한 키워드이다. 원한다면, ‘[[:space:]]’ 를 타이핑하는 대신에 ‘[‘를 타이핑하고, 그런 다음 빈 공간, Control-V, 탭, ‘]’순서로 타이핑한다.

Control-V 는 명령어 확장을 수행하기 보다 실제 탭을 삽입해야 한다는 것을 의미한다. 좋다. 이제 두 번째 regexp로 넘어가 보자. ‘/^}’는 ‘}’ 문자를 찾는다. 이것은 새로운 라인의 시작에 나타난다. 코드가 잘 포맷되었다면 main() 함수의 닫기 괄호를 찾을 것이다.

‘p’ 명령어는 언제나 같은 일을 수행한다. sed에게 라인을 프린트할 것을 명령한다. 우리가 ‘-n’ 모드에 있기 때문이다. C 소스 파일에서 명령어를 실행하라. 이것은 전체 main() { } 블록을 아웃풋할 것이다. 여기에는 초기 “main()”과 닫기 ‘}’가 포함된다.

서진우

서진우

슈퍼컴퓨팅 전문 기업 클루닉스/ 상무(기술이사)/ 정보시스템감리사/ 시스존 블로그 운영자

You may also like...

페이스북/트위트/구글 계정으로 댓글 가능합니다.