Substitute
s / regex / replacement / [flags]
sed 에서 pattern space 에 있는 내용을 수정할 때 사용하는 메인 명령으로 여러 flags 를 통해서 다양한 기능을 제공합니다.
i
,I
플래그는 차이가 없이 같은 역할을 합니다.[flags]
자리에s/foo/bar/{ ... }
와 같이 명령 그룹을 사용할 수 없습니다.
명령 성공에 상관없이 모든 라인이 표시된다
sed 는 기본적으로 명령 사이클이 종료될 때 pattern space 에 있는 내용을 출력하므로
s
명령의 성공에 상관없이 모든 라인이 출력됩니다.
이때 변경에 성공한 라인만 출력하고자 한다면 sed 명령의 -n
옵션과
s
명령의 p
flag 를 사용할 수 있습니다.
# 파일 내용
$ cat file
1_11
2_22
X_33
3_44
Y_55
# 기본적으로 's' 명령의 성공에 상관없이 모든 라인이 표시된다.
$ sed 's/^[A-Z]/@@/' file
1_11
2_22
@@_33
3_44
@@_55
# '-n' 옵션과 'p' flag 를 이용하면 's' 명령이 성공한 라인만 표시할 수 있다.
$ sed -n 's/^[A-Z]/@@/p' file
@@_33
@@_55
매칭 번호를 사용하지 않으면 default 는 1 이다.
# 매칭 번호를 사용하지 않았으므로 s/../../1 과 같다
$ echo "11 aa 22 bb 33 cc" | sed -E 's/[a-z]+/X&X/'
11 XaaX 22 bb 33 cc
$ echo "11 aa 22 bb 33 cc" | sed -E 's/[a-z]+/X&X/2'
11 aa 22 XbbX 33 cc
$ echo "11 aa 22 bb 33 cc" | sed -E 's/[a-z]+/X&X/3'
11 aa 22 bb 33 XccX
$ echo "11 aa 22 bb 33 cc" | sed -E 's/[a-z]+/X&X/g'
11 XaaX 22 XbbX 33 XccX
$ echo "11 aa 22 bb 33 cc" | sed -E 's/[a-z]+/X&X/2g' # 두번째 이후로 전부
11 aa 22 XbbX 33 XccX
-----------------------------------------------------
$ cat file
PHP is a server-side scripting language.
PHP is an open-source language and PHP is case-sensitive.
PHP is platform-independent.
# 두번째 PHP 단어 뒤에 New Text 추가
$ sed 's/\(PHP\)/\1 (New Text added)/2' file
PHP is a server-side scripting language.
PHP is an open-source language and PHP (New Text added) is case-sensitive.
PHP is platform-independent.
# PHP 단어가 2 번 존재하는 라인만 삭제
$ sed -e 's/PHP/PHP/2; tX; b' -e ':X d' file
PHP is a server-side scripting language.
PHP is platform-independent.
regex |
(or) 을 사용할 때는 g
플래그를 사용해야 합니다.
$ echo "11 aa 22 bb 33 cc" | sed -E 's/aa|bb/X&X/'
11 XaaX 22 bb 33 cc
$ echo "11 aa 22 bb 33 cc" | sed -E 's/aa|bb/X&X/g'
11 XaaX 22 XbbX 33 cc
Capturing Groups and Backreferences
$ echo "SED language" | sed 's/\(SED\)/Learn \1 programming/'
Learn SED programming language
$ echo "Learn SED language programming" | sed 's/\(language\) \(programming\)/\2 \1/'
Learn SED programming language
$ sed -En '/^(.)(.)(.)\3\2\1$/p' /usr/share/dict/words
redder
&
문자는 매칭된 영역을 나타냅니다.
$ echo "SED Language" | sed 's/[A-Z]\+/Learn & programming/'
Learn SED programming Language
# 일반 문자로 사용하려면 escape 해야합니다.
$ sed 's/user=&uid/user=\&sysuserid/'
대, 소문자 변환을 위한 \U
\u
\L
\l
\E
# '\U' 는 이후로 모든 영역을 대문자로 변환합니다.
$ echo "sed language" | sed 's/[a-z]\+/learn \U& programming/'
learn SED PROGRAMMING language
# 만약에 중간 중단하고 싶으면 '\E' 를 사용합니다.
$ echo "sed language" | sed 's/[a-z]\+/learn \U&\E programming/'
learn SED programming language
# '\u' 는 바로 뒤의 첫문자만 대문자로 변환합니다.
$ echo "sed language" | sed 's/[a-z]\+/learn \u& programming/'
learn Sed programming language
# '\L' 는 이후로 모든 영역을 소문자로 변환합니다.
$ echo "SED LANGUAGE" | sed 's/[A-Z]\+/LEARN \L& PROGRAMMING/'
LEARN sed programming LANGUAGE
# 만약에 중간 중단하고 싶으면 '\E' 를 사용합니다.
$ echo "SED LANGUAGE" | sed 's/[A-Z]\+/LEARN \L&\E PROGRAMMING/'
LEARN sed PROGRAMMING LANGUAGE
# '\l' 는 바로 뒤의 첫문자만 소문자로 변환합니다.
$ echo "SED LANGUAGE" | sed 's/[A-Z]\+/LEARN \l& PROGRAMMING/'
LEARN sED PROGRAMMING LANGUAGE
..............................................................
# 다음은 모든 단어의 첫문자를 대문자로 변환합니다.
$ echo "learn sed programming language" | sed -E 's/\b[a-z]+\b/\u&/g'
Learn Sed Programming Language
이전에 사용된 regex 중복 작성 방지하기
명령문을 작성하다 보면 종종 address 에서 사용된 regex 이나 이전 s
명령에서
사용된 regex 이 동일하게 다시 사용되는 경우가 있는데요.
이럴 경우 sed 는 중복 작성을 방지하기 위해 s//.../
형식을 제공합니다.
여기서 //
부분이 이전에 사용된 regex 으로 대체됩니다.
# 값 123 을 id 값에 붙이기
$ echo '<pagenum page="normal" id="page">123</pagenum>' |
sed -E '
/(<pagenum page="normal" id=")(\w+)(">)([0-9]*)(<\/pagenum>)/ {
# 첫 번째 방법
# s/(<pagenum page="normal" id=")(\w+)(">)([0-9]*)(<\/pagenum>)/\1\2\4\3\4\5/
# 두 번째 방법
# "//" 를 사용하면 중복 작성을 피할 수 있다.
s//\1\2\4\3\4\5/
}
'
# 결과 "page123"
<pagenum page="normal" id="page123">123</pagenum>
-------------------------------------------------
# 이전 's' 명령에서 사용된 '[A-Z]' 가 두 번째 's' 명령에서 재사용됨
$ echo "111 A 222 B 333 C 444 D 555" | sed '/111/{ s/[A-Z]/XXX/4; s//YYY/ }'
111 YYY 222 B 333 C 444 XXX 555
/address1/,/address2/ 와 같은 range address 를 사용할 경우 기본적으로 매칭 되는 라인도
함께 출력에 포함됩니다.
하지만 경우에 따라서 매칭 라인은 제외해야 될 때가 있는데요.
이럴 경우 //
를 이용하여 식을 간단히 할 수 있습니다.
$ readelf -WS /bin/date | sed -n '/Section Headers:/,/Key to Flags:/p'
Section Headers: <----- 매칭 라인이 출력에 포함됨
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
. . . .
. . . .
[26] .gnu_debuglink PROGBITS 0000000000000000 01a0a0 000034 00 0 0 4
[27] .shstrtab STRTAB 0000000000000000 01a0d4 00011d 00 0 0 1
Key to Flags: <----- 매칭 라인이 출력에 포함됨
# '//d' 으로 매칭 라인을 제거할 수 있습니다.
$ readelf -Wa /bin/date | sed -n '/Section Headers:/,/Key to Flags:/{//d;p}'
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
. . . .
. . . .
[26] .gnu_debuglink PROGBITS 0000000000000000 01a0a0 000034 00 0 0 4
[27] .shstrtab STRTAB 0000000000000000 01a0d4 00011d 00 0 0 1
//d
를 사용할때 한가지 주의할 점은 특정 라인부터 마지막까지 출력할 때입니다.
이때는 마지막 라인을 나타내는 $
를 사용하면 안되고 매칭이 되지 않는
임의의 스트링을 사용해야 합니다.
# 결과가 올바르지 않다. Displaying notes ... 라인이 모두 삭제가 된다.
$ readelf -Wa /bin/date | sed -En '/Displaying notes/,${ //d; p }'
# OK. 첫번째 Displaying notes ... 라인만 삭제가 된다.
$ readelf -Wa /bin/date | sed -En '/Displaying notes/,/\a/{ //d; p }'
/../../
구분자를 다른 문자로 사용하기
이 기능은 addresses 메뉴에서도 나왔는데요. s
명령에서도 동일하게 이용할 수 있습니다.
차이점은 s
명령에서는 구분자로 사용되는 첫 번째 문자를 escape 할 필요가 없습니다.
# '\' 문자를 모두 escape 해야 되기 때문에 가독성이 떨어진다.
$ sed -E 's/\/usr\/local\/bin\/(\w+)/\1/'
# 구분자로 임의의 문자를 선정해서 사용할 수 있다.
$ sed -E 's@/usr/local/bin/(\w+)@\1@'
$ sed -E 's#/usr/local/bin/(\w+)#\1#'
$ sed -E 's,/usr/local/bin/(\w+),\1,'
\a
non-printable 문자의 활용
s
명령을 사용하다 보면 이런 문제가 생기는 경우가 있습니다.
예를 들어 문서에 포함된 스트링을 ab -> bc
로 변경하고 bc -> ab
로 변경하는 것입니다.
두 가지 경우를 모두 s
명령으로 연이어 처리하면 안 되는 것이
ab 를 bc 로 변경한 후에 bc 를 ab 로 변경하면 모두 ab 가 돼 버리기 때문입니다.
이때는 약간 문서 수정을 해야 합니다.
먼저 ab 를 __ab__
로 변경하고 bc 를 ab 로 변경한후에 마지막으로 __ab__
를 bc 로 바꾸는 것입니다.
하지만 문서 내에 __ab__
스트링이 존재할 수도 있기 때문에 주로 사용하는 방법은 non-printable 문자인 \a
를 이용하는 것입니다.
# ab -> bc
# bc -> ab
$ echo 'abbc' | sed 's/ab/bc/g; s/bc/ab/g'
abab
$ echo abbc | sed 's/ab/\a/g; s/bc/ab/g; s/\a/bc/g'
bcab
\a
문자를 이용하는 방법은 여러 가지 상황에서 활용될 수 있습니다.
w filename 플래그 사용시 주의할 점
filename 은 해당식에서 마지막에 위치해야 하고 뒤에 이어서 다른 문자나 명령이 오면 안됩니다.
이후 연이어서 명령을 사용하려면 -e
옵션을 이용해 식을 분리해야 합니다.
Quiz
입력 라인에서 foo
와 zoo
단어 사이에 bar
가 있을 경우만 출력하려면
.*
메타문자를 이용해서 다음과 같이 간단히 처리할 수 있는데요.
$ echo -e 'x foo bar zoo x' | sed -En '/.*foo.*bar.*zoo.*/p'
foo bar zoo
$ echo -e 'x foo xxx zoo x' | sed -En '/.*foo.*bar.*zoo.*/p'
$
반대로 foo
와 zoo
단어 사이에 bar
가 없을 경우만 출력하려면 어떻게 할까요?
$ echo -e 'x foo bar zoo x' | sed -En -e 'h; s/.*foo(.*)zoo.*/\1/; tX; b; :X /bar/!{g;p}'
$
$ echo -e 'x foo xxx zoo x' | sed -En -e 'h; s/.*foo(.*)zoo.*/\1/; tX; b; :X /bar/!{g;p}'
x foo xxx zoo x
1. h; 먼저 입력값을 hold space 에 저장해 놓습니다.
2. s/.*foo(.*)zoo.*/\1/ 매칭에 실패하면 b; 명령으로 종료합니다.
매칭에 성공하면 foo 와 zoo 단어가 있는 것이므로 tX; 명령으로 :X 레이블로 분기합니다.
3. :X 레이블에서는 현재 pattern space 에 남아있는 값에서 bar 있는지 체크하고
없을 경우 g; 명령으로 저장해둔 원본 값을 가져와 출력합니다.
---------------------------------------------------------------------
# perl 의 경우는 다음과 같이 negative lookahead 를 사용할 수 있습니다.
$ echo -e 'x foo xxx zoo x' | perl -ne '/.*foo(?!.*bar).*zoo.*/ && print'
x foo xxx zoo x
2 .
bc
명령을 이용하면 입력과 출력에 사용되는 base ( 진법 ) 을 마음대로 조정할 수가 있는데요.
출력을 2 진법으로 했을 때 byte 단위로 숫자를 8 개씩 구분해서 출력하려면 어떻게 할까요?
# 입력은 16 진수, 출력은 2 진수로 했을때 byte 단위로 구분하기가 어렵다.
$ echo "obase=2; ibase=16; EAB080" | bc
111010101011000010000000
$ echo "obase=2; ibase=16; EAB080" | bc | sed -E 's/.{8}/& /g'
11101010 10110000 10000000
$ echo "obase=2; ibase=16; EAB080" | bc | fold -b8
11101010
10110000
10000000
bc 명령을 사용할땐 obase 가 ibase 앞에 와야 합니다.
3 .
다음 file 내용의 마지막 라인을 "Unix and Linux" 로 만들려고 합니다.
>>
redirection 을 이용해 append 를 하면 결과가 오른쪽과 같이 되는데요.
어떻게 하면 될까요?
$ cat file $ echo " and Linux" >> file
Hello
Welcome to $ cat file
Unix Hello
Welcome to
Unix
and Linux
아래 첫 번째 $
문자는 sed 에서 사용되는 메타문자로 마지막 라인을 의미하고
두 번째 $
문자는 regex 에서 사용되는 메타문자로 라인의 끝을 나타냅니다.
결과적으로 마지막 라인의 끝에 " and Linux" 가 추가되게 됩니다.
$ sed '$ s/$/ and Linux/' file
Hello
Welcome to
Unix and Linux
4 .
다음 파일 내용을 보면 시작과 끝부분에 여분의 newline 들이 존재하는데요. 제거 하려면 어떻게 할까요?
sh$ cat file
<---- newlines
Hello
Welcome to
Unix and Linux
<---- newlines
이때는 sed 의 -z
옵션을 사용해 해결할 수 있습니다.
sed 의 -z
옵션을 사용하면 파일 내용을 한 번에 전부 읽어 들일 수 있습니다.
그다음 시작 부분 (^
) 과 마지막 부분 ($
) 의 newline 들을 제거하면 됩니다.
$ sed -Ez 's/^\n+//; s/\n+$/\n/' file
Hello
Welcome to
Unix and Linux