1 sed

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

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

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

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

1.1 GNU Sed

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

1.2 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"명령은 첫째 줄에만 적용이 되어서, 첫 줄만 삭제되는 거다.

1.3 Address ranges

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

1.4 Regular expressions

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

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

Character설명
^줄의 처음에 일치
$줄의 마지막에 일치
.모든 (단일)문자와 일치
*하나 혹은 그 이상의 앞에 나오는 문자와 일치
![]![]안의 모든 문자와 일치

실제 사용예다.
/./줄에 어떤 문자와도 일치
/../적어도 두개의 문자를 가진 줄과 일치
/^#/#로 시작하는 줄
/^$/공백 줄
/} *$/}로 끝나고 공백 문자가 있는 줄
/abc/'a', 'b', 'c'중 하나라도 포함하는 줄

1.5 범위 지정의 다른 예

기본적으로 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 패턴 사이의 모든 줄을 블럭으로 처리한다.

1.6 기본 문자열 치환

치환은 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의 의미다. 해석하면 "<이 등장한 다음에 처음 등장하는 >" 사이의 문자와 일치한다 라는 의미가 된다.

1.7 복잡한 치환

![ ]는 정규표현식에서 패턴에 추가적인 옵션을 적용하기 위해서 사용한다. 예컨데 '-'로 범위를 지정할 수 있다. 아래는 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]

1.8 매칭된 패턴 사용하기

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

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

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

()를 이용하면 일치한 패턴을 버퍼에 저장할 수 있다. 이 패턴은 순서대로 \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

2 다른 예제들

모든 파일에 포함된 문자열 "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


SED 명령어 사용법 o sed 스트림 편집기 ed명령어와 grep명령어 기능의 일부를 합친 것이 sed(stream editor)명령어이다. sed명령어도 grep명령어와 같은 필터이지만 이 명령어는 화일을 수정할 수 있게 하는 반면 ed처럼 대화식 처리는 불가능하다. sed 명령어는 1개 라인씩 입력 라인을 읽어들여 표준 출력에 출력한다. sed는 각 라인을 읽을 때마다 ed에서 사용하던 형식의 대치작업을 실행한다. 일치하는 문자열이 있으면 그 문자열을 대치한 후 출력하고 일치하는 문자열이 없으면 그 라인은 수정되지 않고 그대로 출력된다. 이 sed 명령어가 ed보다 좋은 점은 라인들을 하나씩 읽고 , 수정하고, 출력하기 때문에 기억장치 안의 버퍼를 사용하지 않는다는 것이다. 버퍼를 사용하지 않으면 화일의 크기에 제한 없이 작업을 할 수 있다. ed와 같이 버퍼를 사용하는 경우는 버퍼의 크기보다 큰 화일은 처리할 수 없으며 대개 버퍼의 크기는 1MB정도이다. 따라서 sed는 아주 큰 화일을 처리할 때 주로 사용된다. sed 명령어를 호출하는 형식은 grep명령어와 같지만 완전한 형식의 대치 연산자를 사용한다는 점만이 다르다. # sed "s/hello/goodbye" in.file 위의 명령어는 in.file이라는 화일에 있는 각 라인에서 첫번째 등장하는 hello라는 문자열을 goodbye로 교체한 후 그 라인을 표준 출력에 출력한다. # echo "1234hello5678" | sed "s/hello/goodbye/" 대치 명령어를 따옴표로 둘러싸야 올바로 사용할 수 있다. 여기서 문자열은 정규식으로 표현될수 도 있다. 그외에도 sed명령어에는 여러 가지 연산자를 사용할 수 있다. 다음의 명령어를 사용하면 hello라는 문자열을 포함하고 있는 모든 문자열을 삭제할 수 있다. # sed "/hello/d" in.file 위 명령어의 의미는 "hello라는 문자열을 포함하고 있는 라인을 찾아 그 라인을 삭제하라"는 것이다. 이 sed 명령어는 다음 명령어와 같은 의미이다. # grep -v hello in.file 라인을 전부 삭제하지 않고 hello라는 문자열만을 삭제하려면 다음 명령어를 사용하다. # sed "s/hello//" in.file ed와 같이 sed에서도 화일의 일부만을 대상으로 작업하는 경우는 라인의 범위를 지정할 수 있다. # sed "3,7s/hello//" in.file 위의 명령어는 in.file이라는 화일의 라인3에서 7까지만을 대상으로 첫번째 hello를 삭제하고 화일의 그 외의 부분은 변경시키지 않는다. 또한 다음과 같이 사용하면 라인 번호 대신 문맥을 범위로 지정할 수 있다. # sed "/hello/,/goodbye/s/bad/good/g" in.file 위의 명령어는 hello라는 단어를 포함하고 있는 첫번째 라인부터 goodbye라는 단어를 포함하고 있는 라인까지 검색하면서 bad라는 문자열을 모두 good으로 변경한다. 또한 문자열 goodbye를 만난 이후에도 다시 다른 hello가 등장하면 다음 goodbye가 나올 때까지 대치 작업은 반복된다. sed명령어의 기능은 지금까지 우리가 살펴본 것보다 더 강력하다. sed명령어의 -f(file)선택자를 사용하면 명령어를 일일이 키보드에서 입력하지 않고 하나의 화일에 기억시켜 놓고 사용할 수도 있다. # sed -f command.file in.file 여러 개의 명령어를 연속적으로 자주 사용할 때 이 명령어 화일이 유용하게 사용된다. 예를 들어 다음과 같은복수 개의 명령어가 화일에 기억되어 있는 경우는 # vi command.file s/hello/goodbye s/good/bad 다음과 같은 명령어를 입력하면 # echo "1234hello5678" | sed -f command.file 다음과 같이 출력된다. # echo "1234hello5678" | sed -f command.file 1234badbye5678 o sed 기본 # sed '' ljs --> cat ljs 와 동일 o sed 편집 명령어 일상적인 sed 명령 --------------------------------------------------------------------------------------- a\ 다음 라인(들)을 적용될 라인들에 부가한다 (라인뒤) c\ 적용될 라인들을 다음 라인(들)로 변경한다 (라인 대체) d 적용될 라인들을 삭제한다 g 단지 첫번째의 것만이 아니라 라인의 모든 부합 패턴 대체가 적용 되게 한다 i\ 다음 라인(들)을 적용될 라인들 위에 삽입한다 (라인앞) p - n 옵션하에 있을지라도, 라인을 프린트한다 q 명시된 라인에 도달할 때 중지한다 r filename filename을 판독한다. 내용을 출력에 부가한다 s/old/new/ "old"를 "new"로 대체한다 = 라인 번호를 프린트한다 !command 라인이 선택되지 않는 경우 command를 적용한다. -------------------------------------------------------------------------------------- o 라인 명시 sed명령은 두가지 방법을 사용한다. 첫 번째는 번지를 번호로 명시하는것이다. 여러분은 특정한 라인을 가리키기 위해 단일 번호를 사용할 수 있다. # sed '3d' ljs --> 세번째 라인을 삭제 또는, 라인들의 범위를 가리키기 위해 콤마(,)로 분리된 두 번호들을 사용할 수 있다. # sed '2,4 s/e/#/' ljs --> 대체 명령은 단지 2-4 라인들에만 적용된다. (단순 대체 명령은 라인에서 첫번째 어커런스에만 적용된다는 점을 기억하라. 따라서 각 적용 라인의 첫 번째 e만이 #로 대체된다) # sed -n '/kingdom/p' ljs --> kingdom이 들어있는 line만 프린트 # sed '/kingdom/p' ljs --> 모든 line이 나타나고 그와 동시에 kingdom line이 중복해서 나타남 # sed '[Pp]rincess/d' ljs --> princess 또는 Princess를 포함하고 있는 라인들을 삭제함 # sed '1,/fragrant/d' ljs --> 라인 1로부터 fragrant를 포함하고 있는 첫번째 라인까지의 모든 라인들을 삭제함 o sed 명령 하이라이트 # more ljs I am a boy You are a girk He is a doctor # sed 'a\\ Hey la la\! Doo de dah\!' ljs --> 각 라인뒤에다 Hey la la!를 입력 I am a boy Hey ! You are a girk Hey ! He is a doctor Hey ! # sed 'a\\ Oh\! good\\ --> \\을 사용함으로써 하나 이상의 라인들을 부가할 수 있다 yeh' ljs # sed '3a\\ Good Morning' ljs --> 3 line뒤에다 내용 삽입 # sed 'c\\ Oh marvelous delight! sing to me! ' ljs --> 기존의 라인들을 이것으로 대체시킴 Oh marvelous delight! sing to me! Oh marvelous delight! sing to me! Oh marvelous delight! sing to me! # sed '2q' ljs = sed 2q ljs --> q명령은 편집기로 하여금 그것이 명시된 라인에 도착한 뒤 중지하게 한다. 즉 2라인만 보여줌 # sed -n '1s/a/#/gp' ljs --> 전체적으로 바꿔줌 o sed의 패턴-부합 패턴-부합에 대한 sed메타 문자 ------------------------------------------------------------------- 메타 문자 작 용 ------------------------------------------------------------------- \ 다음 문자의 특수한 의미를 부정한다 ^ 라인의 시작과 부합한다 $ 라인의 끝과 부합한다 . 어떠한 단일 문자와도 부합한다 [ ] 둘러싸인 문자들 중의 어느 하나와 부합한다 [^...] ...리스트에 없는 어떠한 문자와도 부합한다 pat* 0 또는 그 이상의 pat 어커런스들과 부합한다 여기에서 pat는 단일문자 또는 [ ]패턴이다 & s 명령의 newpattern부분에서 사용되어 oldpattern 부분의 재 산출을 나타낸다 ------------------------------------------------------------------- o 간략한 예 ---------------------------------------------------------------------------------------- 명 령 결 과 ---------------------------------------------------------------------------------------- /Second/ Second를 포함하고 있는 어떠한 라인과도 부합한다. /^Second/ Second로 시작하는 어떠한 라인과도 부합한다. /^$/ 공백라인, 즉 라인의 시작과 끝 사이에 아무것도 없는 라인과 부합한다. 이것은 공백 스페이스들로 된 라인과는 부합하지 않는바, 스페이스 자체가 문자이기 때문이다. /c.t/ cat, cot, 기타 등을 포함하고 있는 라인들과 부합한다. 이 패턴은 단어의 일부일 수 있음에 유의하라. 예를 들어, apricot와 acute도 부합된다. /./ 적어도 한 문자를 포함하고 있는 라인들과 부합한다. /\./ 피리어드를 포함하고 있는 라인들과 부합한다. \는 .의 특수한 의미를 부정 /s[oa]p/ sop또는 sap와는 부합하지만 sip 또는 sup와는 부합하지 않는다. /s[ ^oa]p/ sip또는 sup와는 부합하지만 sop또는 sap와는 부합하지 않는다. s/cow/s&s/ cow를 scows로 대체한다. /co*t/ * --> 어떠한 수 ---------------------------------------------------------------------------------------- o 간단한 sed 해법 # sed '/^$/d' ljs --> 모든 공백 라인 제거 # sed '/^ *$/d' --> space로 만들어진 공백까지 제거 (조심! ^와 *사이에 공백이 있어야 한다) # sed 'a\\ ' ljs --> 각 line마다 공백라인 추가 # sed '/^#/d' ljs --> 첫번째 열에 #을 가진 라인 제거 # sed 's/^/ /' ljs --> 각 line의 시작을 5 space로 대체 o 다중 명령 # sed 's/Bob/Robert/g\ s/Pat/Patricia/g' ljs --> sh을 사용하는 경우에는 \을 생략하라 # sed 's/cat/dog/g\ s/dog/pigs/g' ljs --> 먼저 모든 cats를 dogs로 변환한 다음에 모든 dogs를 pigs로 변환한다. # sed 's/Bob/Robert/g\ s/Pat[^a-z]/Patricia/g' ljs --> ^a-z은 a에서 z까지의 문자들이 아닌 모든 문자를 의미한다는 점을 상기하라 o 태그 위에서 Pat!와 같은 것이 발견될때 !를 포함한 전체 문자열이 Patricia로 대체되므로 !가 소실된다. 우리는 !를 유지하면서 Pat를 대체하는 방법을 필요로 한다. 우리는 이것을 태그(tag)를 사용하여 수행할 수 있다. 패턴의 일부를 "태그"하려면, 그것을 좌측에는 \(로 우측에는 \)로 둘러싸라. 그 다음에, 명령의 newpattern부분에서, 여러분은 그렇게 둘러싸인 패턴의 첫 번째 것은 \1로, 두번째 것은 \2 등으로 인용할 수 있다. 이 방법을 사용하면 다음의 명령이 부여된다. # sed 's/\(Pat\)\([^a-z]\)/\1ricia\2/g' ljs o 쉘 스크립트와 sed # vi twospace sed 'a\\ ' $* --> $*은 모든 인자들을 나타냄 # twospace ljs | pr | lpr 위 예는 sed가 어떻게 하여 UNIX 프로그래밍과 쉘 스크립트에 적합한가를 나타낸다.



'UniX' 카테고리의 다른 글

UNIX system process trace 방법  (0) 2014.04.14
OS 기본  (0) 2014.03.11
HP-UX, IBM-AIX, SUN-Solaris 명령어 비교  (0) 2012.06.21
vi editor  (0) 2012.06.19
raw device to file system(dd copy) [펌]  (0) 2010.04.07

+ Recent posts