sed는 스트림 편집기다. 파일을 한꺼번에 메모리에 올리지 않고 한 줄씩 읽어, 스크립트에 적힌 치환과 삭제만 적용한 뒤 내보낸다. 이름은 stream editor에서 왔다. 리눅스의 `sed`는 대부분 GNU sed다. `sed --version`으로 확인할 수 있다.
grep이 줄을 고르고 awk가 칸을 계산한다면, sed는 줄을 고치거나 빼는 일에 맞다. 설정 파일의 값 하나를 바꾸거나, 빈 줄과 주석을 지우거나, 특정 범위만 출력할 때 자주 쓴다.
한 줄이 처리되는 순서
sed는 입력 줄을 읽을 때마다 같은 순환을 반복한다.
1. 한 줄을 읽어 패턴 공간에 넣는다. 줄 끝의 개행은 패턴 공간에 포함되지 않는다. 2. 스크립트의 명령을 위에서 아래로 실행한다. 주소가 있는 명령은 그 주소에 맞을 때만 실행된다. 3. `-n`이 없으면 패턴 공간을 출력하고 개행을 붙인다. 4. 패턴 공간을 비우고 다음 줄을 읽는다.
그래서 치환 명령만 적어도 나머지 줄은 그대로 나온다. 조건에 맞는 줄만 보고 싶으면 `-n`과 `p`를 함께 쓴다.
sed 's/foo/bar/' file.txt
sed -n '/error/p' app.log첫 명령은 각 줄에서 처음 나오는 `foo`만 `bar`로 바꾸고 모든 줄을 출력한다. 둘째 명령은 `error`가 들어 있는 줄만 출력한다.
프로그램은 작은따옴표로 감싼다. 큰따옴표로 감싸면 `$`와 역슬래시를 셸이 먼저 해석한다.
스크립트가 길면 파일로 두고 `-f`로 읽는다.
sed -f script.sed file.txt주소
주소는 그 명령을 어느 줄에 적용할지 고른다. 주소가 없으면 모든 줄에 적용한다.
- 숫자 하나는 그 줄 번호다. 번호는 1부터 시작하고, 파일을 여러 개 줘도 이어서 센다. - `$`는 마지막 줄이다. - `/정규식/`은 그 정규식이 맞는 줄이다. - `10,20`은 10번 줄부터 20번 줄까지다. - `/start/,/end/`는 `start`가 맞는 줄부터 `end`가 맞는 줄까지다. `end`가 나오지 않으면 끝까지 포함한다. - 주소 뒤의 `!`는 그 주소를 부정한다.
sed -n '1,5p' file.txt
sed -n '$p' file.txt
sed '1,/^$/'d file.txt
sed '/^#/d' config.txt
sed '3!s/foo/bar/' file.txt마지막 명령은 3번 줄을 제외한 모든 줄에서 치환한다.
GNU sed는 `0,/regex/`로 파일 맨 앞에서 정규식이 처음 맞는 줄까지를 범위로 잡을 수 있다. `1,/regex/`는 1번 줄이 이미 정규식에 맞으면 범위가 1번 줄에서 끝나 버린다. 첫 번째 항목만 바꾸고 싶을 때 `0,/regex/`가 필요하다. 이 0번 주소는 POSIX 필수 기능이 아니다.
sed '0,/OLD/s//NEW/' file.txt치환 명령의 빈 검색식 `//`는 바로 앞에 쓴 정규식을 다시 쓴다는 뜻이다. 여기서는 주소의 `OLD`다.
치환
가장 많이 쓰는 명령은 `s`다. 구분자는 슬래시가 아니어도 된다. 경로처럼 슬래시가 많은 문자열은 `s#old#new#`처럼 다른 글자를 고른다.
sed 's/old/new/' file.txt
sed 's#/var/log#/data/log#' paths.txt기본은 한 줄에서 처음 한 번만 바꾼다. 그 줄의 나머지도 바꾸려면 `g`를 붙인다. 몇 번째만 바꾸려면 숫자를 붙인다.
sed 's/old/new/g' file.txt
sed 's/old/new/2' file.txt`i` 또는 `I`는 영문 대소문자를 구분하지 않는다. GNU sed와 최근 BSD sed에 있다. `p`는 치환이 일어난 줄을 한 번 더 출력한다. `-n`과 같이 쓰면 실제로 바뀐 줄만 볼 수 있다.
sed -n 's/error/ERROR/p' app.log`w 파일`은 치환이 일어난 줄만 그 파일에 쓴다. 파일이 이미 있으면 덮어쓴다.
맞는 문자열 전체는 치환 결과에서 `&`다. 리터럴 `&`가 필요하면 `\&`로 쓴다.
sed 's/error/[error]/' app.log
sed 's/note/\&/' file.txt첫 명령은 `error`를 `[error]`로 만든다. `&`가 맞은 단어 `error`를 대신한다.
일부를 다시 쓰려면 묶는다. 기본 정규식에서는 괄호 앞에 역슬래시가 있어야 그룹이 된다. `\1`, `\2`가 그 그룹이다. 그룹은 9개까지다.
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\1년 \2월 \3일/' dates.txt`-E`는 확장 정규식이다. 괄호와 `+`, `?`, `|`, `{n}`을 역슬래시 없이 쓴다. GNU sed는 `-r`도 같은 뜻으로 받는다. 스크립트를 다른 유닉스로 가져갈 때는 `-E`가 더 널리 통한다. 확장 정규식을 끄면 위 식은 다음처럼 써야 한다.
sed 's/\([0-9]\{4\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\)/\1년 \2월 \3일/' dates.txt`\s`, `\d`, `\w`는 GNU 확장이다. POSIX만 가정하면 공백은 `[[:space:]]`, 숫자는 `[0-9]`로 쓴다.
삭제와 출력
`d`는 패턴 공간을 지우고 그 줄의 남은 명령을 건너뛴 뒤 다음 줄을 읽는다. 지운 줄은 출력되지 않는다.
sed '/^$/d' file.txt
sed '/^[[:space:]]*#/d' config.txt
sed '1,3d' file.txt첫 명령은 완전히 빈 줄을 지운다. 스페이스만 있는 줄은 남는다. 둘째는 공백 뒤에 `#`으로 시작하는 주석 줄을 지운다. 줄 중간의 `#`은 지우지 않는다.
`p`는 패턴 공간을 그 자리에서 한 번 출력한다. `-n` 없이 쓰면 기본 출력과 겹쳐 두 번 나온다. 그래서 고른 줄만 볼 때는 `-n`과 함께 쓴다.
sed -n '10,20p' file.txt`=`는 줄 번호를 패턴 공간보다 먼저 한 줄로 출력한다.
sed -n '/TODO/=' src.txt`q`는 그 줄까지 처리한 뒤 입력을 멈춘다. 큰 파일에서 앞부분만 필요할 때 전체를 읽지 않는다. GNU sed의 `Q`는 현재 패턴 공간을 출력하지 않고 멈춘다.
sed '10q' file.txt
sed -n '/marker/{p;q}' file.txt줄 넣기와 바꾸기
`i`는 주소에 맞는 줄 앞에 텍스트를 넣고, `a`는 그 줄 뒤에 넣는다. `c`는 그 줄 전체를 새 텍스트로 바꾼다. 여러 줄 텍스트는 `\`로 줄을 이어서 적는다.
sed '1i # generated' file.txt
sed '$a # end' file.txt
sed '/^NAME=/c NAME=thinktree' config.txtGNU sed는 `i`, `a`, `c` 뒤에 역슬래시 없이 한 칸을 두고 텍스트를 쓸 수 있다. 다른 sed는 `i\` 다음 줄에 텍스트를 적는 예전 문법을 요구하는 경우가 있다.
`y`는 글자를 같은 위치의 다른 글자로 옮긴다. 길이가 같아야 한다. 정규식이 아니다.
sed 'y/abc/ABC/' file.txt명령 여러 개
한 주소에 명령을 여러 개 쓰려면 중괄호로 묶는다. 명령 사이에 `;`를 넣어도 된다. `i`, `a`, `c`처럼 텍스트가 뒤를 따르는 명령은 `;`로 잇기 어렵다. 그때는 `-e`를 반복하거나 줄을 나눈다.
sed -e '/^$/d' -e 's/[[:space:]]*$//' file.txt이 명령은 빈 줄을 지운 다음, 남은 줄의 오른쪽 공백을 지운다. `-e`의 순서가 실행 순서다.
sed '{
s/foo/bar/g
s/old/new/g
}' file.txt파일을 직접 고치기
인자 없이 실행하면 결과는 표준 출력으로만 나가고 원본은 그대로다. 원본을 바꾸려면 결과를 임시 파일에 저장한 뒤 바꾸거나, `-i`를 쓴다.
GNU sed는 접미사 없이 `-i`만 써도 원본을 바로 고친다. 접미사를 주면 그 이름으로 백업을 남긴다.
sed -i.bak 's/old/new/g' file.txt실행 뒤에 `file.txt`는 바뀌고 `file.txt.bak`은 이전 내용이다. 확인 뒤에 백업을 지운다.
macOS의 BSD sed는 `-i` 뒤에 빈 접미사라도 인자가 필요하다. `sed -i '' 's/old/new/' file`이다. 리눅스용 `-i` 스크립트를 그대로 가져가면 인자 오류가 나거나, 다음 인자를 백업 접미사로 오해한다.
`-i`는 파일을 통째로 다시 쓴다. 심볼릭 링크를 주면 구현에 따라 링크가 아니라 링크가 가리키던 파일을 고치거나, 링크를 일반 파일로 바꿔 버리기도 한다. 중요한 파일은 백업 접미사를 붙이고 diff로 확인한 뒤에 쓴다. 파이프로 받은 표준 입력에는 `-i`를 쓸 수 없다. 고칠 대상 파일이 없기 때문이다.
패턴 공간과 홀드 공간
패턴 공간은 지금 처리 중인 줄이다. 홀드 공간은 그 옆에 두는 보관 장소이고, 처음에는 비어 있다.
- `h`는 패턴 공간을 홀드 공간에 덮어쓴다. - `H`는 홀드 공간 끝에 개행을 붙이고 패턴 공간을 더한다. - `g`는 홀드 공간을 패턴 공간에 덮어쓴다. - `G`는 패턴 공간 끝에 개행을 붙이고 홀드 공간을 더한다. - `x`는 둘을 맞바꾼다.
바로 앞 줄을 현재 줄 앞에 붙여 보려면 홀드 공간을 쓴다.
sed '1{h;d};H;x' file.txt이 스크립트는 1번 줄을 홀드에 넣고 출력하지 않는다. 다음 줄부터는 현재 줄을 홀드 끝에 더한 뒤 둘을 맞바꾼다. 결과만 보면 한 줄씩 밀린 누적이다. 이런 스크립트는 읽기 어려우므로, 줄 단위를 넘어 상태를 기억해야 하면 awk가 더 분명한 경우가 많다.
`N`은 다음 줄을 패턴 공간에 개행과 함께 이어 붙인다. `D`는 패턴 공간에서 첫 개행까지를 지우고, 남은 내용이 있으면 다음 줄을 읽지 않은 채 스크립트 처음으로 돌아간다. 연속된 빈 줄을 한 줄로 줄일 때 이 쌍을 쓴다.
sed '/^$/{N;/^\n$/D}' file.txt자주 쓰는 형태
첫 번째 `foo`만 `bar`로 바꾼다.
sed '0,/foo/s//bar/' file.txt모든 줄을 바꾼다.
sed 's/foo/bar/g' file.txt앞뒤 공백을 지운다.
sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' file.txt특정 단어가 있는 줄만 지운다.
sed '/deprecated/d' notes.txt10번 줄부터 20번 줄의 `false`를 `true`로 바꾼다.
sed '10,20s/false/true/g' config.txt여러 파일의 같은 문자열을 백업과 함께 바꾼다.
sed -i.bak 's/old.example/new.example/g' *.confGNU sed와 다른 sed
리눅스 스크립트가 다른 유닉스에서 깨지는 지점은 보통 다음이다.
- `-i`의 백업 접미사 규칙이 다르다. - `\s`, `\d`, 0번 주소, `Q`는 GNU 확장이다. - 기본 정규식의 `+`, `?`, `|`, 괄호는 역슬래시가 있어야 특수 문자가 된다. `-E`를 주면 역슬래시 없이 쓴다. - `i`, `a`, `c`의 줄 바꿈 문법이 구현마다 조금 다르다.
공통으로 둘 범위는 줄 번호, `$`, 정규식 주소, `s///g`, `d`, `p`, `-n`이다. GNU sed 설명서는 다음에 있다.
결과가 어긋날 때
`s/a/b/`는 한 줄의 첫 `a`만 바꾼다. 같은 줄의 두 번째가 남으면 `g`가 없는 것이다.
슬래시를 포함한 경로를 `s///`에 그대로 넣으면 구분자가 중간에 끊긴다. 구분자를 바꾸거나 슬래시를 `\/`로 이스케이프한다.
`*`와 `.`는 정규식이다. 파일 이름의 점 그대로를 찾으려면 `\.`로 쓴다. `.*`는 빈 문자열에도 맞으므로 치환 위치가 예상보다 앞으로 당겨질 수 있다.
`-n`만 켜고 `p`를 빼면 아무것도 나오지 않는다. 기본 출력을 껐기 때문이다.
`-i` 없이 출력을 눈으로만 보면 원본은 그대로다. 화면에 바뀐 내용이 나왔다는 것은 표준 출력이 바뀌었다는 뜻이지, 파일이 저장되었다는 뜻은 아니다.
따옴표로 감싼 CSV의 칸 안 쉼표와 줄바꿈은 sed의 줄 단위 치환으로 지키면서 고치기 어렵다. 그 파일은 CSV를 이해하는 도구로 다룬다.
COMMENTS
댓글 0
이 글에 대한 의견이나 질문을 남겨 주세요.
댓글은 구글이나 카카오로 시작한 뒤에 남길 수 있습니다.
등록된 댓글이 없습니다.