Move between spaces

소문자로 된 명령은 copy 입니다.
목적지 기존 데이터는 삭제되고 source 내용이 dest 로 copy 됩니다.
대문자로 된 명령은 append 입니다.
먼저 newline 이 dest 에 append 되고 이어 source 내용이 dest 에 append 됩니다.

기본적으로 'x' 명령을 제외하고 source 내용은 변하지 않습니다.

hold (copy)

1. 현재 hold space 에 있는 내용은 전부 삭제됩니다.
2. pattern space 에 있는 내용이 hold space 로 copy 됩니다.

Hold (append)

1. 먼저 hold space 에 newline 을 append 합니다.
2. pattern space 에 있는 내용이 copy 되어 hold space 로 append 됩니다.

go to pattern space (copy)

1. 현재 pattern space 에 있는 내용은 전부 삭제됩니다.
2. hold space 에 있는 내용이 pattern space 로 copy 됩니다.

Go to pattern space (append)

1. 먼저 pattern space 에 newline 을 append 합니다.
2. hold space 에 있는 내용이 copy 되어 pattern space 로 append 됩니다.

eXchange

1. pattern space 에 있는 내용과 hold space 에 있는 내용을 서로 맞바꿉니다.


그림으로 보는 예제

예제 1

다음 예제는 어떤 명령의 실행 결과인데요. md5sum 을 구해서 파일 사이즈와 함께 표시하는데 항상 출력이 다음과 같은 순서로 표시가 됩니다. 이것을 sed 를 이용해 파일명 -> gz -> bz2 순서로 변경하는 것입니다.

$ command ...
e805c26ff46c6e138e3cd198cff281ea 301 Packages.bz2
15bfecb2b041d5387aacdd32879e4e56 324 Packages
b97a7252f202566a1e5fdc5b50c2ffdf 283 Packages.gz
---------------------------------------------------

# 'n' 명령은 자동 출력 모드에서 pattern space 의 내용을 출력하므로 '-n' 옵션을 사용
$ command ... | sed -n 'h; n; N; G; p'
15bfecb2b041d5387aacdd32879e4e56 324 Packages
b97a7252f202566a1e5fdc5b50c2ffdf 283 Packages.gz
e805c26ff46c6e138e3cd198cff281ea 301 Packages.bz2

예제 2

다음은 docker hub 에서 특정 이미지의 tag list 를 조회해 sort 한 결과인데 이미지에 따라서 latest 가 다음과 같이 임의의 위치에 나타날 수 있습니다. 어떤 경우에서든 latest 를 항상 마지막에 표시하려면 어떻게 할까요?

$ docker-taglist alpine                         # latest 가 없는경우
latest             2.6                2.6               2.6
2.6                2.7                2.7               2.7
2.7                3                  3                 3
3                  3.1                3.1               3.1
3.1                3.2                3.2               3.2
3.2                3.3                3.3               3.3
3.3                latest             3.4               3.4
3.4                3.4                3.5               3.5
3.5                3.5                3.6               3.6
3.6                3.6                latest
--------------------------------------------------------------

# 입력 라인이 'latest' 가 아닐 경우 출력한 후 :X 레이블로 branch 합니다.
/^latest$/!{p; bX};

# 입력 라인이 'latest' 일 경우는 hold space 에 저장합니다.
h;

# 마지막 라인일 경우 hold space 의 내용을 pattern space 로 가져오는데
# 이때 값이 없는 경우가 있을 수 있으므로 /^$/! 로 체크하여 프린트합니다.
${g; /^$/!p}' 

$ cat taglist | sed -n '/^latest$/!{p; bX}; h; :X ${g; /^$/!p}'

예제 3

/usr/include/elf.h 파일 내용을 보면 여러개의 typedef struct 이 있는데요. 그중에서 Elf64_Ehdr 부분만 추출합니다.

# "typedef struct" 을 포함하는 라인을 만나면 hold space 로 이동하고 식의 끝으로 branch 합니다.
# 이때 기존에 hold space 에 내용이 있었다면 삭제됩니다.
/typedef struct/{h;b}
# 이후부터는 라인을 hold space 에 계속 append 합니다. 
H
# "Elf64_Ehdr;" 가 포함된 라인을 만나면 hold space 에 있는 내용을 pattern space 로 옮긴 후
# 프린트하고 종료합니다. 
/Elf64_Ehdr;/{g;p;Q}/ 

$ sed -n '/typedef struct/{h;b}; H; /Elf64_Ehdr;/{g;p;Q}' /usr/include/elf.h      
typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf64_Half    e_type;                 /* Object file type */
  Elf64_Half    e_machine;              /* Architecture */
  Elf64_Word    e_version;              /* Object file version */
  Elf64_Addr    e_entry;                /* Entry point virtual address */
  Elf64_Off     e_phoff;                /* Program header table file offset */
  Elf64_Off     e_shoff;                /* Section header table file offset */
  Elf64_Word    e_flags;                /* Processor-specific flags */
  Elf64_Half    e_ehsize;               /* ELF header size in bytes */
  Elf64_Half    e_phentsize;            /* Program header table entry size */
  Elf64_Half    e_phnum;                /* Program header table entry count */
  Elf64_Half    e_shentsize;            /* Section header table entry size */
  Elf64_Half    e_shnum;                /* Section header table entry count */
  Elf64_Half    e_shstrndx;             /* Section header string table index */
} Elf64_Ehdr;

다음은 -z 옵션을 이용해 전체 파일을 읽어들인 후 regex 을 활용하는 방법입니다.

$ sed -z -rn 's/.*(typedef struct\n.+} Elf64_Ehdr;\n).*/\1/p' /usr/include/elf.h

예제 4

다음과 같이 AAA 뒤에 숫자 데이터가 이어지는데요. 이것을 한개의 라인으로 만드는것 입니다.

# 데이터 내용
AAA
1
34
AAA
5
32
61
17
AAA
71
4
15
...................

$ sed -En '
    /AAA/{
        :X 
        x          # `x` 명령으로 hold space 내용과 pattern space 내용을 교환합니다.
        s/\n/ /g   # newline 을 space 로 변경하여 한 줄로 만듭니다.
        p          # 내용을 프린트하고
        b          # 명령 사이클의 END 로 분기합니다.
    } 
    H              # AAA 와 매칭이 되지 않은 경우는 `H` 명령으로 hold space 에 append 합니다.
    $bX            # 마지막 라인일 경우 :X 로 분기하여 hold space 에 있는 내용을 프린트합니다.
' file

AAA 1 34
AAA 5 32 61 17
AAA 71 4 15

예제 5

필드가 space 로 분리되어 있는 레코드에서 /config/service 필드를 추출합니다.
그런데 여기서 문제는 필드의 위치나 순서가 정해져 있지가 않습니다.

# 데이터 내용
build 345 /config:launcher.mxres /nickname:prod /service:session
auto 4986 /nickname:deal /service:engine /config:launcher5.mxres
build 912 /config:launcher_binary.mxres /service:scanner /nickname:input 
.........................................................................

$ sed -E '
    h                            # 먼저 원본 라인을 hold 하여 복사

    s#.*(/config:[^ ]+).*#\1#    # /config 값을 추출한 후 `H` 명령으로 hold space 에 append.
    H;g                          # 현재 pattern space 는 /config 값만 남고 삭제된 상태이므로
                                 # `g` 명령을 이용해 hold space 값을 pattern space 로 복사

    s#.*(/service:[^ ]+).*#\1#   # 위와 같은 방법으로 /service 값 추출
    H;g                          # `g` 명령을 이용해 hold space 값을 pattern space 로 복사

    s#.*\n(.*)\n(.*)#\1 \2#      # 현재 pattern space 에는 원본 라인과 /config, /service
                                 # 3개의 라인이 존재하므로 /config, /service 값만 추출
' file

/config:launcher.mxres /service:session
/config:launcher5.mxres /service:engine
/config:launcher_binary.mxres /service:scanner

Quiz

앞서 regex 메뉴에서 lazy 매칭을 하는 방법에 대해서 알아보았는데요. 단일 문자가 아니라 특정 스트링을 lazy 매칭해서 삭제하려면 어떻게 할까요?

# .* 는 greedy 하므로 마지막 foo 단어까지 삭제가 된다.
$ echo "111 foo 222 foo 333" | sed -E 's/.*foo//'
 333

$ echo "111 foo 222 foo 333" | sed -E 'h; s/(foo).*$/\1/; G; s/^([^\n]+)\n\1//'
 222 foo 333

1. h; 먼저 입력값을 hold space 에 저장해 놓습니다.
2. s/(foo).*$/\1/ 식으로 greedy 매칭을 뒤에서 부터하면 pattern space 에는
   111 foo 만 남게됩니다.
3. G 명령으로 hold space 에 저장해 놓은 원본값을 pattern space 에 append 합니다.
   111 foo
   111 foo 222 foo 333
4. s/^([^\n]+)\n\1//  (여기서 \1 은 ([^\n]+) 매칭된 값과 동일한 값이 됩니다.)
   식을 이용해 pattern space 에서 앞부분을 삭제하면 222 foo 333 만 남게됩니다.

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

# perl 에서는 .*? 를 이용해 lazy 매칭을 할 수 있습니다.
$ echo "111 foo 222 foo 333" | perl -pe 's/.*?(foo)//'
 222 foo 333

다음은 FOO 에서부터 BAR 까지 lazy 매칭하여 XXX 로 치환하는 것입니다.

$ echo sss FOO uuu BAR nnn FOO ccc BAR rrr |
    sed 's/BAR/\a/g; s/FOO[^\a]*\a/XXX/; s/\a/BAR/g'
sss XXX nnn FOO ccc BAR rrr

1. 먼저 BAR 를 \a 문자로 모두 치환합니다.
2. 그다음 FOO 에서부터 \a 문자 까지를 XXX 값으로 치환합니다.
3. 마지막으로 남은 \a 문자를 다시 BAR 로 모두 치환합니다.

# 만약에 FOO 나 BAR 단어에 regex 이 포함될 경우는 다음과 같이하면 됩니다.
$ echo "sss FXO uuu BXR nnn FYO ccc BYR rrr" |
    sed -E 's/(B[A-Z]R)/\a\1/g; s/F[A-Z]O[^\a]*\aB[A-Z]R/XXX/; s/\a//g'
sss XXX nnn FYO ccc BYR rrr

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

$ echo "sss FOO uuu BAR nnn FOO ccc BAR rrr" | perl -pe 's/FOO.*?BAR/XXX/'
sss XXX nnn FOO ccc BAR rrr

이번에는 BAR 쪽 뿐만 아니라 FOO 쪽도 lazy 매칭을 적용하는 것입니다.

# FOO 쪽은 lazy 매칭이 적용되지 않아 FOO 두개가 모두 삭제된다.
$ echo sss FOO uuu FOO w BAR nnn FOO ccc BAR rrr | perl -pe 's/FOO.*?BAR/XXX/'
sss XXX nnn FOO ccc BAR rrr

$ echo sss FOO uuu FOO w BAR nnn FOO ccc BAR rrr |
    sed 's/FOO/\a/g; s/BAR/\v/g; s/\a[^\a\v]*\v/XXX/; s/\a/FOO/g; s/\v/BAR/g'
sss FOO uuu XXX nnn FOO ccc BAR rrr