Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

sed

유닉스 시스템 관리자하는 텍스트 파일을 편집하는데 거의 대부분의 시간을 보낸다. 보통 vi나 emacs, jed 같은 전문 텍스트 에디터를 이용해서 이런 일을 한다. 이런 (유저와 상호작용하는)전문 에디터는 훌륭하긴 하지만 한계역시 가지고 있다. 상호작용성이 강점이지만 약점이 될때도 있기 때문이다. 매우 큰(혹은 매우 많은 파일)에 특정 문자열을 다른 문자열로 모두 치환해야 하는 경우를 생각해보자. 전문 에디터를 사용할 경우 엄청나게 많은 시간이 걸릴 것이다.

작업해야 할 문서가 매우 많고, 시간 역시 충분히 많다면 CC++로 프로그램을 만들 수도 있을 것이다.

작업해야 할 문서가 매우 많고, 굳이 많은 시간을 쓰고 싶지 않다면, sed를 이용해서 짧은 시간에 이런 일들을 할 수 있다.

sed는 가벼운 stream 에디터로 거의 모든 리눅스(유닉스) 시스템에 설치돼있다. sed의 장점은 다음과 같다.
  1. 아주 가볍기 때문에 스크립트 언어와 궁합이 잘 맞는다.
  2. stream 에디터이기 때문에 파이프와 같은 표준입력을 통해서 데이터를 받아서 처리할 수 있다. 쉘 스크립트는 파이프를 통해서 데이터를 처리하는데, 과 함께 사용하면 파일을 처리하는 강력한 프로그램을 만들 수 있다.

GNU Sed

모든 리눅스 운영체제는 GNU Sed를 가지고 있다. 2012년 Ubuntu 12.04를 기준으로 Sed 최신버전은 4.2.1이다.

Sed 예제

Sed는 편집 명령을 이용해서 입력 데이터를 처리한다. 또한 line-base로 작동한다. 그래서 각 줄 단위로 편집명령이 적용된다. 편집 명령이 적용된 결과는 표준출력된다. (입력 파일을 직접 수정하지는 않는다.)

아래는 간단한 sed 사용 예다.
# sed -e 'd' /etc/services 
이 명령을 실행하면 화면에 아무것도 출력하지 않을 것이다. 왜 그런지 살펴보자. 우리는 sed를 "d" 명령으로 실행했다. sed는 /etc/services 파일을 열어서 모든 줄에 대해서 "d" 명령을 수행한다. "d"는 "delete line"명령을 수행한다. 줄을 지우라는 의미다. 모든 줄에 대해서 "delete line"이 적용되므로 결국 빈 줄을 표준출력한다.

혹시라도 /etc/services의 내용이 모두 사라지진 않았을지 걱정할 필요는 없다. sed는 편집한 결과를 표준출력 할 뿐, 파일의 내용을 수정하지는 않는다. 파일의 내용을 수정하길 원한다면, 표준출력을 임시파일로 저장해서 원본파일을 덮어써야 한다.

다음 명령을 실행해보자
# sed -e '1d' /etc/services | more
이 명령을 실행하면 /etc/service의 첫 라인만 삭제 된다. 앞에 1은 명령이 적용될 범위를 의미하는데, 첫번재 줄을 의미한다. 따라서 "d"명령은 첫째 줄에만 적용이 되어서, 첫 줄만 삭제되는 거다.

Address ranges

첫째 줄부터 10번째 줄까지 명령 범위를 지정해 보자.
# sed -e '1,10d' /etc/services
"d"명령은 첫번째 줄에서 10번째 줄까지만 적용되고 나머지 줄은 무시한다.

Regular expressions

이번에는 좀 더 복잡한 예제다. 당신은 /etc/services 파일의 내용을 살펴보길 원한다. 그런데, 주석이 너무 많아서 읽을 맛이 나지 않아서 주석줄은 제외하고 살펴보길 원한다. /etc/service 파일에서 주석은 "#"로 시작이 되므로 줄의 시작 문자가 "#"이면, 삭제하면 될테다.
# sed -e '/^#/d' /etc/services | more
이 코드를 실행하면 주석을 제외한 내용을 보여주는 걸 확인할 수 있다. 어떻게 이런일이 가능한지 분석해보자. 이 코드는 "/^#/"와 "d" 두 부분으로 이루어져있다. d는 삭제하라는 명령이고, 그렇다면 문제는 "/^#/"이 부분의 해석이다. sed는 "//"를 만나면 정규표현식으로 간주해서 해석을 한다. "^#"이 정규표현식인데, ^는 줄의 처음을 의미한다. 즉 이 정규표현은 줄의 "처음에 #이 등장하는 것을 패턴일치 시킨다." 결국 줄의 처음에 #이 등장하면(/^#/) + 삭제(d) 삭제하라는 명령을 수행한다.

정규표현은 아주 강력한 문자열 처리 도구다. 그런만큼 배워야할 내용도 많다. 여기에서는 정규표현에서 자주 사용하는 유용한 몇가지 패턴만 살펴볼 것이다. 자세한 내용은 정규표현의 문서들을 참고한다.

Character 설명
^ 줄의 처음에 일치
$ 줄의 마지막에 일치
. 모든 (단일)문자와 일치
* 하나 혹은 그 이상의 앞에 나오는 문자와 일치
![] ![]안의 모든 문자와 일치
실제 사용예다.
/./ 줄에 어떤 문자와도 일치
/../ 적어도 두개의 문자를 가진 줄과 일치
/^#/ #로 시작하는 줄
/^$/ 공백 줄
/} *$/ }로 끝나고 공백 문자가 있는 줄
/[abc]/ 'a', 'b', 'c'중 하나라도 포함하는 줄

범위 지정의 다른 예

기본적으로 sed는 줄단위로 명령을 수행한다. 아래와 같은 코드가 있다고 가정해 보자.
#include <stdio.h>

int main()
{
    printf("Hello world");
}

void hello()
{
}
지금까지의 방법으로는 main 함수만 추려낼 수는 없다. 패턴 상으로 보면 "main("에서 부터 첫줄이 "}"끝났을 때까지를 main 함수로 정의할 수 있지만, 줄단위로만 패턴을 일치하기 때문이다.

이 경우 다음과 같이 main 함수만 가져올 수 있다.
# sed -n -e '/main/,/^}/p' main.c 
int main()
{
    printf("Hello world");
}

사용법은 다음과 같다.
# sed -n -e '/BEGIN/, /END/p' file
이렇게 하면 "BEGIN"패턴과 END 패턴 사이의 모든 줄을 블럭으로 처리한다.

기본 문자열 치환

치환은 s(substitution)명령을 이용한다. 정규표현vi에 익숙하다면 이해하기 쉬울 것이다.

모든 5678을 xxxx로 치환
# sed -e s/5678/xxxx/g test.txt 
1234 xxxx hello world 1234xxxx xxxx

파일의 내용을 치환하기 위해서는 표준출력을 임시파일로 저장한 다음 복사하는 스크립트를 만들어야 한다.
sed -e s/5678/xxxx/g test.txt > test.txt.tmp
mv test.txt.tmp test.txt

s를 이용하면 파일의 모든 줄을 검사해서 치환한다. 치환할 구간을 줄 단위로 지정할 수 있다. 첫번째 줄에서 10번째 줄까지만 치환을 적용하려면
# sed -e '1,10s/1234/xxxx/g' test.txt 

공백라인 다음 줄의 처음에 등장하는 bus 문자열을 texi로 변경한다.
# sed -e '/^$/,/^END/s/bus/texi/g' test.txt 

/usr/local을 /usr로 바꾼다.
# sed -e 's/\/usr\/local/\/usr/g' test.txt 

'\'를 포함한 문자열을 치환하려면 역슬래쉬를 사용해야 하는데, 코드가 지저분해 진다. 이럴 땐 ":"을 이용하자.
# sed -e 's:/usr/local:/usr:g' test.txt 

아래와 같은 HTML 문서가 있다.
<b>This</b> is what <b>I</b> meant.

HTML 태그를 없애기 위해서 다음과 같이 sed 코드를 만들었다.
# sed -e 's/<.*>//g' test.txt

우리가 원하는 결과는 "This is what meant."이지만, 실제 결과는 " meant."다. 위의 정규식 대로라면 처음 등장하는 <과 마지막 등장하는 >사이의 모든 문자를 치환해 버리기 때문이다. 다음과 같이 표현식을 바꿔서 원하는 결과를 얻을 수 있다.
$ sed -e 's/<[^>]*>//g' test.txt
![^>]에서 ^는 none의 의미다. 해석하면 "<이 등장한 다음에 처음 등장하는 >" 사이의 문자와 일치한다 라는 의미가 된다.

복잡한 치환

![ ]는 정규표현식에서 패턴에 추가적인 옵션을 적용하기 위해서 사용한다. 예컨데 '-'로 범위를 지정할 수 있다. 아래는 a에서 부터 x까지 일치하는 단어가 있을 경우 패턴일치한다.
'[a-x]*'

![:alnum:] ![a-z A-Z 0-9], 모든 알파벳 문자와 숫자
![:alpha:] ![a-z A-Z], 모든 알파벳 문자
![:blank:] 스페이스 문자와 탭문자
![:cntrl:] 모든 컨트롤 문자들
![:digit:] 숫자, ![0-9]
![:graph:] 모든 visible 문자들
![:lower:] 알파벳 소문자, ![a-z]
![:print:] Non-control 문자
![:space:] 공백문자
![:upper:] 알파벳 대문자, ![A-Z]
![:xdigit:] 16진수 문자![0-9 a-f A-F]

매칭된 패턴 사용하기

&를 이용하면 패턴 매칭된 문자열을 저장하고 불러올 수 있다.

echo "yundream 33" | sed -e 's/[a-z]*/My name is &. Age is/'
My name is yundream. Age is 33

매칭된 패턴 여러 개를 사용하기

()를 이용하면 일치한 패턴을 버퍼에 저장할 수 있다. 이 패턴은 순서대로 \1, \2, ... 으로 불러올 수 있다.
# echo "yundream programmer" | sed -e 's/\([a-z]*\) \([a-z]*\)/My name is \1. Job is \2. Hello \1/'
My name is yundream. Job is programmer. Hello yundream

다른 예제들

모든 파일에 포함된 문자열 "xxx"를 "yyy"로 치환
#!/bin/bash

for file in ls *
do
    if [ -f $file ]
    then
        tmpfile="$file.tmp"
        sed -e 's/xxx/yyy/g' $file > $tmpfile
    fi
done

참고 문헌