본문 바로가기
TYLER SONGBlog
← Blog index
CS학습6 min read

sed: 스트림 에디터로 텍스트를 한 줄씩 변환하는 방법

sed는 입력을 한 줄씩 읽어 편집 명령을 적용한 뒤 출력하는 스트림 에디터(stream editor)다. 치환(s), 삭제(d), 출력(p) 같은 명령과 주소(address) 지정으로 파일이나 파이프 입력을 비대화식으로 가공한다. 이 글에서는 동작 원리, 기본 명령, 실무에서 자주 쓰는 패턴과 GNU sed와 BSD sed의 차이를 다룬다.

By 송민성

1. 개념

sed(Stream EDitor)는 입력 스트림(stream)을 한 줄씩 읽어 지정한 편집 명령을 적용하고 결과를 표준 출력(stdout)으로 내보내는 유닉스 계열 도구다. 기본적으로 원본 파일은 수정하지 않는다. 편집기처럼 화면을 열지 않고 명령줄에서 바로 실행하므로 비대화식(non-interactive) 편집 도구로 분류된다.

기본 문법은 다음과 같다.

shell
sed [옵션] '주소 명령' 파일
  • 주소(address): 명령을 적용할 줄의 범위. 줄 번호, 정규표현식, 범위(2,5) 등을 쓴다. 생략하면 모든 줄에 적용된다.
  • 명령(command): s(치환), d(삭제), p(출력), i/a(삽입/추가) 등.

2. 왜 사용하는가

  • 설정 파일, 로그, CSV 같은 텍스트를 스크립트 안에서 자동으로 수정할 수 있다.
  • 파이프(|)로 다른 명령과 연결해 데이터를 가공하는 파이프라인(pipeline)을 만들 수 있다.
  • 파일 전체를 메모리에 올리지 않고 한 줄씩 처리하므로 큰 파일에도 적합하다.
  • 배포 스크립트, CI, Dockerfile에서 별도 설치 없이 쓸 수 있다. 대부분의 리눅스 배포판에 기본 포함되어 있다.

3. 동작 원리

sed는 각 입력 줄에 대해 다음 사이클(cycle)을 반복한다.

  1. 입력에서 한 줄을 읽어 패턴 공간(pattern space)이라는 버퍼에 넣는다. 줄 끝의 개행 문자는 제외된다.
  2. 스크립트의 명령을 순서대로 실행한다. 주소가 일치하는 명령만 적용된다.
  3. 사이클이 끝나면 패턴 공간의 내용을 출력한다. -n 옵션을 쓰면 자동 출력이 꺼진다.
  4. 다음 줄로 넘어가 반복한다.

보조 버퍼인 홀드 공간(hold space)도 있어 h, H, g, G, x 명령으로 여러 줄에 걸친 처리를 할 수 있다. 입문 단계에서는 패턴 공간만 이해하면 충분하다.

치환 명령의 형식은 다음과 같다.

text
s/패턴/대체문자열/플래그
  • 플래그 g: 줄 안의 모든 일치 항목을 치환한다. 생략하면 줄마다 첫 번째 일치만 치환한다.
  • 플래그 i(GNU sed): 대소문자를 무시한다.
  • 구분자는 / 말고 다른 문자도 가능하다. 경로를 다룰 때 s|/usr|/opt|처럼 쓰면 이스케이프가 줄어든다.

기본 정규표현식은 BRE(Basic Regular Expression)이며, -E(또는 GNU의 -r)를 쓰면 ERE(Extended Regular Expression)를 사용한다.

4. 코드 예제

실습용 파일을 만들고 주요 명령을 차례로 실행해 본다.

shell
cat > sample.txt <<'EOF' host=localhost port=3000 debug=true # comment line name=app app EOF # 1) 줄마다 첫 번째 일치만 치환 sed 's/app/service/' sample.txt # 2) 줄 안의 모든 일치를 치환 (g 플래그) sed 's/app/service/g' sample.txt # 3) 3번째 줄 삭제 sed '3d' sample.txt # 4) 주석 줄 삭제 (정규식 주소) sed '/^#/d' sample.txt # 5) 2~3번째 줄만 출력 (-n + p) sed -n '2,3p' sample.txt # 6) 확장 정규식으로 key=value에서 value만 바꾸기 sed -E 's/^(port)=[0-9]+/\1=8080/' sample.txt # 7) 파이프 입력 처리 echo "hello world" | sed 's/world/sed/'

파일을 직접 수정하는 -i 옵션은 GNU sed와 BSD sed(macOS)에서 문법이 다르다.

shell
# GNU sed (대부분의 리눅스): 백업 없이 수정 sed -i 's/debug=true/debug=false/' sample.txt # GNU sed: .bak 백업을 만들며 수정 sed -i.bak 's/debug=true/debug=false/' sample.txt # BSD sed (macOS): -i 뒤에 백업 확장자 인자가 반드시 필요 (빈 문자열이면 백업 없음) sed -i '' 's/debug=true/debug=false/' sample.txt

두 환경에서 모두 동작시키려면 백업 확장자를 붙이는 -i.bak 형태가 가장 무난하다.

5. 시간 복잡도 또는 성능 특성

  • 입력 크기를 n줄이라 할 때, 줄마다 정해진 명령을 적용하므로 처리 시간은 입력 크기에 비례한다. 줄 길이와 명령 수, 정규식의 복잡도에 따라 상수 배가 달라진다.
  • 기본 동작은 스트리밍이라 한 번에 한 줄만 패턴 공간에 둔다. 따라서 큰 파일도 메모리 부담이 작다. 다만 홀드 공간에 계속 누적하는 스크립트(예: 파일 전체를 합쳐 처리)는 그만큼 메모리를 쓴다.
  • -n과 q 명령을 조합하면 필요한 줄까지만 읽고 종료할 수 있다. sed -n '10{p;q}' file은 10번째 줄을 출력하고 바로 끝낸다.

6. 실무 사용 사례

  • 설정 값 변경: 배포 스크립트에서 환경별로 sed -i.bak 's|^API_URL=.*|API_URL=https://api.example.com|' .env처럼 값을 바꾼다.
  • 로그 필터링: sed -n '/ERROR/p' app.log로 오류 줄만 추출한다.
  • 주석과 빈 줄 제거: sed -e '/^#/d' -e '/^$/d' config.conf
  • 줄 범위 추출: sed -n '100,120p' app.log로 특정 구간만 확인한다.
  • 일괄 치환 파이프라인: grep -rl 'oldName' src | xargs sed -i.bak 's/oldName/newName/g'
  • 공백 정리: sed 's/[[:space:]]*$//' file로 줄 끝 공백을 제거한다.

7. 주의할 점

  • -i는 원본을 덮어쓴다. 처음에는 -i 없이 결과를 확인하거나 -i.bak으로 백업을 남긴다.
  • GNU sed와 BSD sed는 -i 문법 외에도 차이가 있다. 예를 들어 \n, \t, \+ 같은 이스케이프 지원 여부가 다를 수 있다. 이식성이 필요하면 POSIX 범위의 문법만 쓴다.
  • 작은따옴표(')로 스크립트를 감싸야 셸이 $나 \를 먼저 해석하지 않는다. 셸 변수를 넣어야 한다면 큰따옴표를 쓰되, 변수 값에 /나 & 같은 특수문자가 있으면 치환이 깨질 수 있다.
  • 대체 문자열의 &는 일치한 전체 문자열을 의미한다. 문자 그대로 쓰려면 \&로 이스케이프한다.
  • 구분자와 같은 문자가 패턴에 들어 있으면 이스케이프하거나 다른 구분자(|, #)를 쓴다.
  • sed는 줄 단위 도구다. 여러 줄에 걸친 복잡한 구조(JSON, XML 등)를 정규식으로 고치는 것은 오류가 나기 쉬우므로 jq 같은 전용 도구를 쓰는 편이 안전하다.

8. 핵심 정리

  • sed는 입력을 한 줄씩 패턴 공간에 읽어 명령을 적용하고 출력하는 스트림 에디터(stream editor)다.
  • 기본 형식은 sed '주소 명령' 파일이며, 가장 많이 쓰는 명령은 치환 s/패턴/대체/플래그다.
  • -n과 p는 선택 출력, d는 삭제에 쓴다. g 플래그가 없으면 줄마다 첫 일치만 바뀐다.
  • 처리 시간은 입력 크기에 비례하고, 스트리밍 방식이라 큰 파일에도 메모리 부담이 작다.
  • -i는 원본을 직접 수정하며 GNU와 BSD의 문법이 다르다. -i.bak으로 백업을 남기는 습관이 안전하다.