Addresses

Address 는 입력되는 라인들 중에서 원하는 라인을 선택하기 위해 사용하는 방법입니다. 각 라인들은 라인 넘버를 가지고 있으니까 첫 번째로 라인 넘버를 이용하는 방법이 있구요. 두 번째는 regex 매칭을 이용해서도 지정을 할 수가 있습니다. address 뒤에 ! 문자를 붙이면 logical NOT 역할을 합니다. , 문자를 이용해 두개의 address 를 연결하면 일종의 address range 를 형성할 수도 있습니다.

address 에 해당하는 명령이 복수개 이상일 경우 { } 명령 그룹을 이용합니다.

Numeric Addresses

다음은 라인 넘버를 이용해 address 를 지정하는 예 입니다.

# 10번 라인만 프린트
$ sed -n '10p'

# 10번 라인만 삭제
$ sed '10d'

# 20번 라인만 foo 를 bar 로 수정
$ sed '20s/foo/bar/'

$ (마지막 라인)

address 에서 사용되는 $ 는 마지막 라인을 뜻합니다.
이문자가 /regex/ 에서 사용되면 라인의 끝을 나타냅니다.

# 마지막 라인만 프린트
$ seq 10 | sed -n '$p'
10

first ~ step

first 에 계속해서 step 을 더해나간다고 생각하면 됩니다.

# 1, 4, 7, 10, 13, 16 ...
$ seq 10 | sed -n '1 ~ 3p'
1
4
7
10

# 4, 8, 12, 16, 20 ...  주소 0 의 의미는 뒤에도 나옵니다.
$ seq 10 | sed -n '0 ~ 4p'
4
8

# 위와 같은 결과입니다.
$ seq 10 | sed -n '4 ~ 4p'  
4
8
$ ip -4 address | cat -n
     1  1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN ...
     2      inet 127.0.0.1/8 scope host lo
     3         valid_lft forever preferred_lft forever
     4  2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc ...
     5      inet 220.85.92.78/24 brd 220.85.92.255 scope global dynamic ...
     6         valid_lft 4903sec preferred_lft 4903sec
     7  4: lxdbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state ...
     8      inet 10.24.244.1/24 scope global lxdbr0
     9         valid_lft forever preferred_lft forever
    10  5: br-958737dcc5b0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc ...
    11      inet 172.18.0.1/16 brd 172.18.255.255 scope global br-958737dcc5b0
    12         valid_lft forever preferred_lft forever
    13  6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue ...
    14      inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
    15         valid_lft forever preferred_lft forever

$ ip -4 address | sed -n '2 ~ 3p'
    inet 127.0.0.1/8 scope host lo
    inet 220.85.92.78/24 brd 220.85.92.255 scope global dynamic noprefixroute enp0s25
    inet 10.24.244.1/24 scope global lxdbr0
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-958737dcc5b0
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0

Regex Addresses

regex 를 이용한 address 지정은 /regex/ 형식을 사용합니다.

# 라인에 apple 이 포함될 경우만 프린트
$ sed -n '/apple/p' 

# 라인이 file[0-9]+ 과 매칭될 경우만 프린트
$ sed -En '/file[0-9]+/p' 

# 라인에 apple 이 포함될 경우만 foo 를 bar 로 수정
$ sed '/apple/ s/foo/bar/'

/regex/I

I 옵션을 사용하면 매칭을 할때 대, 소문자를 구분하지 않습니다.
( s 수정 명령에서도 동일하게 사용할 수 있습니다.)

# 대문자 'LL' 과 소문자 'll' 이 매칭 되지 않는다.
$ echo heLLo | sed -n '/hello/p'
$

# 'I' 옵션을 사용하면 대, 소문자 구분 없이 매칭할 수 있다.
$ echo heLLo | sed -n '/hello/Ip'
heLLo

/regex/M

pattern space 에 라인이 여러개 들어있을 경우 multi-line 모드를 사용할 수 있는데 이 부분은 따로 Multiple lines 메뉴에서 다루겠습니다.

/.../ 구분자를 다른 문자로 사용하기

regex 패턴에 디렉토리명과 같이 / 문자가 포함될 경우 /.../ 문자와 겹치므로 모두 escape 해야하는데요. 이때 다음과 같은 방법을 사용하여 식을 간단히 할 수 있습니다.

# '/' 문자를 모두 escape 해야 되기 때문에 가독성이 떨어진다.
$ sed -n '/\/usr\/local\/bin/p'

# 구분자로 사용할 임의의 문자를 선정한 후 첫번째 문자만 escape 한다.
$ sed -n '\@/usr/local/bin@p'

$ sed -n '\#/usr/local/bin#p'

$ sed -n '\,/usr/local/bin,p'

/^$/ (공백 라인)

sed 에서 공백 라인은 /^$/ 로 표현됩니다. 라인의 처음과(^) 끝($) 사이에 아무것도 없으니 공백 라인이 되겠죠.

# 공백라인을 모두 제거하고 프린트
$ sed -n '/^$/!p' file

# 공백라인에 space 나 tab 문자가 포함될 경우
$ sed -n '/^[[:blank:]]*$/!p' file

Range Addresses

address range 에 사용되는 address1, address2 는 numeric 과 regex 를 혼합해서 사용할 수 있습니다.

# 10번 부터 20번 라인까지 프린트
$ sed -n '10,20p'

# 'BEGIN' 이 포함되는 라인부터, 'END' 가 포함되는 라인까지 프린트
$ sed -n '/BEGIN/,/END/p'

# 'BEGIN' 이 포함되는 라인부터, 공백 라인까지 프린트
$ sed -n '/BEGIN/,/^$/p'

# 10번 라인부터 'END' 가 포함되는 라인까지 프린트
$ sed -n '10,/END/p'

# 'MIDDLE' 이 포함되는 라인부터 마지막 라인까지 프린트
$ sed -n '/MIDDLE/,$p'

# 2번 부터 4번 라인까지만 [0-9] 를 X 로 치환 
$ cat <<\--- | sed '2,4 s/[0-9]/X/g'
11
22
33
44
55
66
---
11
XX
XX
XX
55
66

$ cat <<\--- | sed -n '/[0-9]/,/[0-9]/p'
AA
11
BB            
22               
CC                   # 'CC' 가 빠졌고 '33', 'DD' 까지 프린트 됩니다.
---                  # 항상 sed 는 stream editor 라는 생각을 가지고 있어야 합니다.
11                   $ cat <<\--- | sed -n '/[0-9]/,/[0-9]/p'
BB                   AA
22                   11
                     BB
                     22
                     CC
                     33
                     DD
                     ---
                     11
                     BB
                     22
                     33
                     DD

address1 , +N

address1 부터 이후 +N 라인 까지에 해당합니다.

# 3 부터 이후 +2 라인 까지 이므로 결과적으로 3,5p 가 됩니다. 
$ seq 10 | sed -n '3,+2p'
3
4
5

$ cat <<\--- | sed -n '/MIDDLE/,+2p'        $ cat <<\--- | sed '/MIDDLE/,+2d'
111                                         111
222                                         222
MIDDLE                                      MIDDLE
333                                         333
444                                         444
555                                         555
---                                         ---
MIDDLE                                      111
333                                         222
444                                         555

address1 , ~N

~ 문자 는 위에 first ~ step 에서도 한번 나왔는데요. 배수를 뜻합니다.
그러니까 address1 부터 숫자 N 의 배수까지라는 의미가 됩니다.

# 6 을 지나는 4 의 배수는 8 이므로 결과적으로 6,8p 가 됩니다.
$ seq 20 | sed -n '6,~4p'
6
7
8

0 , /regex/

address range 는 결과가 프린트될때 최소 2 라인이 표시됩니다.
그래서 만약에 1번 라인이 address2 에 매칭 되었을때 1번 라인만 프린트할 수가 없는데요.
이때 사용할수 있는 방법이 address 0 입니다.

# address range 는 최소 2 라인이 표시된다.
$ seq 10 | sed -n '1,/[0-9]/p'
1
2

# address '0' 을 이용하면 1번 라인만 프린트할수 있다.
$ seq 10 | sed -n '0,/[0-9]/p'
1

Logical NOT

address range 에서는 ! 를 각각의 주소에 사용할 수 없습니다.
addr1 , addr2 ! 의 의미는 addr1 , addr2 를 제외한 나머지 부분이라고 생각하면 됩니다.

# 3번 라인을 제외하고 모두 프린트
$ seq 5 | sed -n '3!p'             # sed '3d' 와 같습니다.
1
2
4
5

# 10 ~ 20 라인을 제외하고 모든 라인에서 foo 를 bar 로 수정
$ sed '10,20! s/foo/bar/'

# 3 ~ 8 라인을 제외하고 모두 프린트
$ seq 10 | sed -n '3,8!p'
1
2
9
10

$ echo -e "AA\n11\nBB\n22\nCC" | sed -n '/[0-9]/,/[0-9]/!p'
AA
CC

$ echo -e "AA\n11\nBB\n22\nCC\n33\nDD" | sed -n '/[0-9]/,/[0-9]/!p'
AA
CC

$ echo -e "AA\n11\nBB\n22\nCC\n33\nDD\n44\nFF" | sed -n '/[0-9]/,/[0-9]/!p'
AA
CC
FF

예제 1

  1. <parent></parent> 로 둘러싸인 값만 프린트 합니다.
  2. <parent></parent> 로 둘러싸인 값을 제거하고 나머지 부분을 프린트 합니다.
  3. <parent></parent> 로 둘러싸인 값에서 "-SNAPSHOT" 을 삭제합니다.
# 데이터 내용
<modelVersion>4.0.0</modelVersion>
<version>5.1.3-SNAPSHOT</version>

<parent>
    <artifactId>xxx</artifactId>
    <groupId>group</groupId>
    <version>5.1.3-SNAPSHOT</version>
</parent>

<artifactId>yyy</artifactId>
<packaging>pom</packaging>
<name>artifact name</name>

-------------------------------------

# 1번 해답
$ sed -n '/<parent>/,/<\/parent>/p'

# 2번 해답
$ sed '/<parent>/,/<\/parent>/d'

$ sed -n '/<parent>/,/<\/parent>/!p'

# 3번 해답
$ sed '/<parent>/,/<\/parent>/ s/-SNAPSHOT//'

예제 2

공백 라인에 의해 레코드가 분리되어 있고 [name] 형식의 헤더를 갖습니다. 이때 [shovel] 헤더를 갖는 레코드에서 enabled = 0 항목을 enabled = 1 로 변경합니다.

# 데이터 내용
[purple]
auth = no
enabled = 0
username = 
password =
priority = 0
host = True

[shovel]
group = 
manual = False
enabled = 0
username = 

--------------------

$ sed '/\[shovel]/,/^$/ { /enabled/ s/0/1/ }' file