-
sed는 가벼운 stream 에디터로 거의 모든 리눅스(유닉스) 시스템에 설치돼있다. 장점은 다음과 같다.
- 아주 가볍기 때문에 스크립트 언어와 궁합이 잘 맞는다.
- stream 에디터이기 때문에 파이프와 같은 표준입력을 통해서 데이터를 받아서 처리할 수 있다. 쉘 스크립트는 파이프를통해서 데이터를 처리하는데, 쉘과 함께 사용하면 파일을 처리하는 강력한 프로그램을 만들 수 있다.
sed는 편집 명령을 이용해서 입력 데이터를 처리한다. 또한 line-base로 작동한다. 그래서 각 줄 단위로 편집명령이 적용된다.편집 명령이 적용된 결과는 표준출력된다. (입력 파일을 직접 수정하지는 않는다.)
sed 명령 사용하기 (치환)
좋은 지침서로는 O'Reilly handbook for sed and awk 이랍니다.
sed가 하는 일은 딱 하나, 무조건 표준 입력에서 입력되는 내용 중에,
"Index"라는 문자열이 있으면, 그것을 "색인"이라는 문자열로 바꾸어서
표준 출력으로 보내는 일 밖에 하지 않습니다.
결국 나머지 일들은 쉘 스크립트에서 또 다른 명령어나,
쉘 스크립트 언어를 사용해야겠지요.
그 사용법은
sed 's/찾는문자열/바꿀문자열/g' 입력파일
이것이 가장 기본적인 사용법이랍니다.
이렇게 하면 앞에서 말했듯이 그 결과는 표준 출력으로 보내집니다.
또한 문자열을 지정할 때, 쉘 스크립트 작성때와 마찬가지로,
쉘에서 특별한 의미로 쓰이는 문자들은 '' 문자를 앞세워서
사용합니다.
.*[]^$
이 문자들이 sed, shell에서 특별한 의미로 쓰임으로
이 문자 자체를 의미하려면, '' 문자를 먼저 사용해야겠지요.
예:
sed 's/[J.S. Bach {$ for music}]/[Bach, J.S {$ for music}]/' filename
이게 아니라,
sed 's/[J.S. Bach {$ for music}]/[Bach, J.S {$ for music}]/' filename
이렇게.
여기서 하나 신기한 것은 "찾는 문자열"에서만 이런 것이 적용된다는 것입니다.
"바꿀 문자열"에서도 똑같이 지정하면, 그 지정한 그대로 출력되더군요.
(신기하나? 원래 당연한 것 아닌감? 표준 출력이니까.)
그럼 이것은 무엇일까요?
sed 's//usr/bin//bin/g' filename
"/usr/bin" -> "/bin" 으로 바꾸는 것?
물론 오류가 있는 사용법은 아닙니다. 하지만, 경로명이 길어지면,
알아 보기가 힘들겠지요. 그래서, 경로명을 사용할 때는
sed 's#/usr/bin#/bin#g' filename
이렇게 한답니다.
또한 "Index"라는 문자열은 "색인"으로 바꾸고, "Contents"라는 문자열은
"차례"라는 내용으로 바꾸고 싶다면,
sed를 두번 사용하느냐? 이런 멍청한 짓(?) 하는 사람은 없겠지요.
이럴 때는
sed -e 's/Index/색인/g' -e 's/Contents/차례/g' 파일이름
이런 식으로 한답니다. '-e' 옵션은 "또 있다"는 뜻이랍니다.
이 옵션이 빠지면, 첫번째 경우만 처리하고 두번째 경우("차례"로 바꾸는 것)는
무시된다고 하네요.
3. 정규표현식(Regular expression) 사용하기
모든 찾기가 그러하듯이 여기서도 여전히 정규식이 사용되는군요.
정규식 사용하는데 무리가 없는 사람은 별로 어려운 문제가 아니겠지요.
저는 잘 모르니까, 몇가지 예제를 들어보지요.
sed 's/^Thu /Thursday/' filename
이건 저도 아네요. 껄껄. '^' 기호는 그줄의 첫칸을 의미하지요.
즉, 줄 첫 칸에만 있는 "Thu" 문자열을 "Thursday"로 바꾸는 경우네요.
그런데, 여기서는 파일 전체를 대상으로 한다는 'g' 문자가 빠졌군요.
왜 빠져도 되는지는 모르겠지만, 아무튼 실행되더군요.
sed 's/ $//' filename
이건 반대로 줄 끝에 있는 공백 문자를 없애는 것입니다.
즉 " $" 문자열을 "" 문자열로. 텍스트 파일에 대한 기본적인 지신이 부족한
사람들에게는 "그게 뭐 어때서?" 하면서 의아해 하겠지만,
엄격히 따지면, " $" 문자열이란, 공백문자랑, 그 다음에 줄 바꿈 문자가
있는 것을 말합니다. 이것을 윗 명령으로 그 공백 문자를 없애는 경우입니다.
과연 이런 게 어디 쓰일까? 회의를 둘 수도 있겠지만,
의외로 필요할 때가 많습니다.
한 예를 들어 awk (요곤 기회 주어지면 다음에 하지요. sed 보다 복잡
하더군요.) 에서 마지막 필드 다음에 줄바꿈 문자가 있어야 되는데,
이렇지 못하고, 공백문자가 있어, 원하는 결과 값을 못 얻어 내는 경우가
있거든요. 이럴 때 유용하게 쓰입니다.
sed 's/^$/이건 빈 줄이다/' filename
줄 첫칸에 줄 바꿈 문자가 있다? 당연히 빈줄이지요. 껄껄.
sed 's/Apr .. ..:..:.. 1980/Apr 1980/g' filename
이런 식으로 많이 사용하지 않았나요?
vi 편집기나, grep 명령에서 말입니다.
'.'(점)은 임의한 한 문자를 뜻하지요.
윗 예제는 뭔고하면, "Apr 11 11:09:25 1980" 이런 식으로
(어디서 많이 보았지요?) 된 여러 경우들을 무조건 "Apr 1980"으로
통일하는 경우입니다.
sed 's/[Oo]pen[Ww]in/openwin/g' filename
sed 's/ [A-Z]. / /g' filename
이것도 그럴 것 같은데. " D. " 문자열 같은게, " " 문자열로 바꾸는 것입니다.
sed 's/ [^A-DHM-Z]. / /g' filename
이건 윗 예제와 반대지요.
즉, A,B,C,D,H,M,O,P,Q,R,S,T,U,V,W,Z,Y,Z 문자는 제외한 다른 단일 문자의
경우는 모두 공백문자로.
그 외에도 몇개 더 있는데, 뭐 그다지 중요한 것은 아니네요.
grep나, ls 명령에서 이미 잘 쓰고 있는 것들이니까,
* : 임의의 문자열.
[][^] 등.. 여러 표현식을 섞어서 사용하는 것.
4. sed 안에서도 자체적으로 저장을 한다는군요. (신기하기도해라)
이 말이 무신 말인고 하면,
앞에서 말한 sed의 개념으로 본다면, 단지, 표준 입력으로 입력받아,
표준 출력으로 단순히 변환해서 보내는 역활밖에 하지 않는데,
저장할 여력이 어디 있을까 하는데.....
이런 경우를 생각해 보지요.
"*남자*와 *여자*"라는 정규식에 맞는 문자열에서 공백문자를 두고
서로 바꾸어야할 경우. 지금까지의 개념으로 본다면,
이런 문제는 sed에서 불가능하지요.
먼저 발견되는 첫번째 환경을 저장하고,
다음 두번째 환경이 곧이어 발견되면 그것을 서로 바꾸면 되겠지요.
이렇게 하는 것이 "(" ")" (괄호)입니다.
sed 's/^([A-Z][A-Za-z]*), ([A-Z][A-Za-z]*)/2 1/' filename
이것은 "문자열1, 문자열2" 이런 내용을 "문자열2 문자열1"로
바꾸는 것입니다. 어떻게 1, 2를 서로 바꾸느냐 하면, "바꿀문자열 지정부분에,
2 1 이런식으로 써주면 되지요.
영어 표현 중에, "Lastname, Firstname" 이런 것을 "Firstname Lastname"
이런 식으로 바꾸고자 할 때 유용하게 쓰인답니다.
필요에 따라 입력 파일의 특정 범위 내에서만 문자열 바꾸기를 해야할
경우가 생기는데, 이때는 s 앞에다 그 범위를 지정합니다.
sed '1,20s/foobar/fubar/g' filename
이것은 첫번째 줄부터 20번째 줄까지만 찾아서 바꾸는 것입니다.
sed '/^Aug/s/Mon /Monday /g' filename
이런 식도 가능하다네요. 즉, 줄 처음에 Aug (팔월인가?) 라는 문자열이
있는 그 줄의 Mon 이라는 문자열을 Monday 로 바꾸는 것입니다.
이 반대라면,
sed '/^Aug/!s/Mon /Monday /g' filename
이런 식으로.
또한 조건을 여러게 줄 수도 있습니다.
sed '/^Aug/,/^Oct/s/Mon /Monday /g' filename
어떻게 작동할 지 알겠지요?
지금까지 설명 예로든 것은 모두 바뀌는 부분을 포함해서, 입력된
모든 내용을 다시 표준 출력으로 보여줍니다.
필요에 따라 그 바뀌는 줄에 대해서만 출력해야 할 경우가 생기지요.
sed -n 's/fubar/foobar/gp' filename
이런 식으로 사용하는데, 이때 주의 할 것은 -n 옵션을 사용한다는 것과
끝부분에 g가 아니라, gp라는 것.
5. 파일로 저장된 sed 명령. sed 스크립트.
여러 sed 명령을 계속 사용해야 할 경우는 이 작업을 또 보다 편하게
할 수 없을까? 해서, 생겨난 것이 스크립트인데, 가령,
s/color/colour/g
s/flavor/flavour/g
s/theater/theatre/g
한 파일에 이렇게 입력하고, sample.sed 라는 이름으로 저장한 후,
이것을 사용하려면 다음과 같이 합니다.
sed -f sample.sed filename
그럼 쉘 스크립트에서 실행 프로그램을 지정하는 #! 기호를 사용하면,
#!/usr/bin/sed -f
# This file is named "sample2.sed"
s/color/colour/g
s/flavor/flavour/g
s/theater/theatre/g
이렇게 작성하고, sample2.sed 라는 이름으로 저장한 후,
chmod u+x sample2.sed
./sample2.sed filename
이렇게 바로 하나의 독자적인 스크립트로 사용할 수도 있겠지요.
-----------
이상이 sed 맛보기 내용의 전부입니다.
얼마나 도움이 되었을지 의문이네요.
sed 단독으로는 그리 유용하게 사용되지 못합니다.
이것을 제대로 사용하려면, 결국 유닉스에서의 텍스트 처리 명령들에
대해서 모두 꾀뚤고(?) 있어야 하겠더군요.아래는 간단한 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 I 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