Branch

Branch 명령과 레이블을 이용하면 sed 에서도 if ~ else 문을 구현할 수 있습니다.

# 입력 라인이 '000' 일 경우 'AAA' 로 출력하고 그렇지 않을경우 'BBB' 로 출력
$ echo -e '111\n000\n222' | sed -n 's/000/AAA/; tX; s/.*/BBB/; :X p'
BBB                                         # t 명령과 X 레이블 사용
AAA
BBB
$ echo -e '111\n000\n222' | sed -n '/000/{ s//AAA/; bX }; s/.*/BBB/; :X p'
BBB                                         # b 명령과 X 레이블 사용
AAA
BBB

b label , t label , T label

Unconditional branch

1. 이 명령은 무조건 해당 label 로 분기합니다.
2. label 을 적지 않을 경우 명령 사이클의 END 로 분기하고 다음 사이클을 시작합니다.

Conditional branch (test)

1. t 명령은 test flag 가 1 일 경우 분기, T 명령은 test flag 가 0 일 경우 분기합니다.
2. flag 값이 1 이 되는 때는 s/../../ 명령이 성공했을 때입니다.
3. flag 값이 0 이 되는 때는 t 명령으로 분기할 때, 라인을 읽어들일 때입니다.
4. default flag 값은 0 입니다.

그러므로 s/../../ 명령이 성공했을 경우 t 명령에서 분기하게 되고
실패했을 경우는 T 명령에서 분기하게 됩니다.

5. test 에 성공했으나 label 을 적지 않았을 경우 END 로 분기하고 다음 사이클을 시작합니다.

조건 분기 명령을 다룰 때 잘못 생각하기 쉬운 부분이 s/../../ 명령이 실패하면 flag 값이 0 이 될거라는 생각입니다. 예를 들어 다음과 같은 시리즈의 명령이 있을때 T 명령은 테스트에 실패하여 분기하지 않고 t 명령은 분기합니다.

# 앞에 (성공) 이 있으므로 현재 flag 값은 1 이다.

s/../../(성공); s/../../(실패); T XX1; t XX2

이제 t 명령으로 레이블 XX2 로 분기했으므로 이제는 flag 값이 1 에서 0 으로 되었습니다. 그러므로 다음과 같은 명령이 이어지면 T 명령에서는 YY1 로 분기하지만 t 명령은 분기하지 않습니다.

# 't' 명령으로 분기했으므로 이제 flag 값은 0 이다.

T YY1; t YY2

이와 같은 동작 방식은 s 명령을 여러개 사용하게 될때 문제가 될 수 있는데요. 가령 앞선 s 명령이 성공이 돼버리면 뒤에 이어지는 s 명령에서는 성공, 실패를 구분해서 분기할 수가 없게 됩니다. 이럴때는 다음과 같이 s 명령 사용전에 flag 값을 0 으로 reset 해놓는 것이 필요합니다.

# 1. ( ) 괄호로 둘러싸인 입력 스트링에서 먼저 ( ) 를 삭제하고
# 2. 그다음 "@" 문자를 ":" 로 치환하는데 성공하면 결과를 출력하고
# 3. 실패할 경우는 출력을 하지 않습니다.

# s/@/:/g 가 성공하면 tX 에의해 :X 레이블로 분기해 p 출력이 되게하고
# 실패할 경우는 b 명령에 의해 END 로 분기하여 출력이 되지 않게한다.
$ echo '(foo@bar@zoo)' | sed -En -e 's/\(|\)//g; s/@/:/g; tX; b; :X p'
foo:bar:zoo
# 또는
$ echo '(foo@bar@zoo)' | sed -En -e 's/\(|\)//g; s/@/:/g; T; p'
foo:bar:zoo

# 하지만 s/@/:/g 명령이 실패하였는데도 앞선 s 명령이 성공하여 값이 출력이 된다.
$ echo '(foo/bar/zoo)' | sed -En -e 's/\(|\)//g; s/@/:/g; tX; b; :X p'
foo/bar/zoo

# s/@/:/g 명령 사용전에 tR :R 를 이용해 flag 값을 0 으로 리셋한다.
# s/@/:/g 명령이 실패하여 기대하였던 것과 같이 값이 출력되지 않는다.
$ echo '(foo/bar/zoo)' | sed -En -e 's/\(|\)//g; tR :R s/@/:/g; tX; b; :X p'
$

substitute 명령의 g 플래그 와 branch 명령 비교

substitute 명령의 g 플래그

$ echo "1234567890" | sed -E 's#([0-9]{2})([0-9]{2})#\1\\x\2#g'
12\x3456\x7890

$ echo "1234567890" | sed -E 's#([0-9]{2})([0-9]{2})#\\x\1\\x\2#g'
\x12\x34\x56\x7890

$ echo "1234567890" | sed -E 's#([0-9]{2})#\\x\1#g'
\x12\x34\x56\x78\x90

# *, + 수량 한정자를 사용하면 마지막 매칭값만 출력된다.
$ echo "1234567890" | sed -E 's#([0-9]{2})*#\\x\1#g'
\x90

branch 명령을 사용

$ echo '1234567890' | sed -E ':X s/^([0-9]*)([0-9]{2})/\1\\x\2/; tX'
\x12\x34\x56\x78\x90

$ echo '1234567890' | sed -E ':X s/^([0-9]*)([0-9]{2})/\1\\x\2/; l; tX'    # 'l' 명령 사용
12345678\\x90$
123456\\x78\\x90$
1234\\x56\\x78\\x90$
12\\x34\\x56\\x78\\x90$
\\x12\\x34\\x56\\x78\\x90$
\\x12\\x34\\x56\\x78\\x90$
\x12\x34\x56\x78\x90

예제 1

다음은 [ ] 괄호안에 포함되지 않는 단어들을 추출하는 예입니다. 단순히 괄호 안의 내용을 s 명령을 이용해 모두 삭제하면 될것 같지만 문제는 괄호가 중첩되어 있는 경우가 있다는 것입니다. 따라서 이와 같은 경우는 두번째 명령과 같이 branch loop 기능을 활용해야 합니다.

$ cat file1
[--natsettings<1-N> [<mtu>],[<socksnd>], [<sockrcv>],[<tcpsnd>], [<tcprcv>]] hello 
[--natpf<1-N> [<rulename>],tcp|udp,[<hostip>], <hostport>,[<guestip>],<guestport>] 
[--natpf<1-N> delete <rulename>] world [--nataliasmode default|proxyonly, [sameports]]

$ sed -E 's/\[[^][]*\]//g' file1          # 괄호가 중첩되어 있어서 올바로 삭제되지 않는다.
[--natsettings<1-N> ,, ,, ] hello 
[--natpf<1-N> ,tcp|udp,, <hostport>,,<guestport>] 
 world [--nataliasmode default|proxyonly, ]

$ sed -E ':X s/\[[^][]*\]//g; tX' file1   # branch loop 를 사용해야 한다.
 hello   

 world

예제 2

이번 예제는 파일에서 --string 형식의 스트링을 추출하는 것입니다. 먼저 파일 내용을 하나의 라인으로 만들기 위해 echo $(< file2) 를 사용했는데 이때 globbing 이 발생할 수 있으므로 set -f 옵션을 설정했습니다. 위 예제에 비해 좀 복잡해 보일 수 있는데 backreference 의 순서를 \2\n\1 와 같이 활용하는 것을 주의깊게 보세요.

$ cat file2
Usage: VBoxManage sharedfolder add <uuid|vmname> --name <name> 
--hostpath <hostpath> [--readonly] [--automount] VBoxManage 
sharedfolder remove <uuid|vmname> --name <name> [--transient]

# 매칭이 되는 \2 는 계속 앞쪽으로 붙여넣기가 되고 \3 은 삭제가 된다. \1 의 (.*) 는 greedy 매칭
$ { set -f; echo $(< file2) ;} | 
    sed -En ':X s/(.*)(--[a-z-]+)(.*)/\2\n\1/; /--[a-z-]+\n$/{p;q}; tX'
--transient
--name
--hostpath
--readonly
--automount
--name

# `l` 명령을 이용하여 디버깅을 해보면 다음과 같습니다.
$ { set -f; echo `< file2` ;} |
    sed -En ':X s/(.*)(--[a-z-]+)(.*)/\2\n\1/; l; /--[a-z-]+\n$/{p;q}; tX'
--transient\nUsage: VBoxManage sharedfolder add <uuid|vmname> --name \
<name> --hostpath <hostpath> [--readonly] [--automount] VBoxManage sh\
aredfolder remove <uuid|vmname> --name <name> [$
--name\n--transient\nUsage: VBoxManage sharedfolder add <uuid|vmname>\
 --name <name> --hostpath <hostpath> [--readonly] [--automount] VBoxM\
anage sharedfolder remove <uuid|vmname> $
--automount\n--name\n--transient\nUsage: VBoxManage sharedfolder add \
<uuid|vmname> --name <name> --hostpath <hostpath> [--readonly] [$
--readonly\n--automount\n--name\n--transient\nUsage: VBoxManage share\
dfolder add <uuid|vmname> --name <name> --hostpath <hostpath> [$
--hostpath\n--readonly\n--automount\n--name\n--transient\nUsage: VBox\
Manage sharedfolder add <uuid|vmname> --name <name> $
--name\n--hostpath\n--readonly\n--automount\n--name\n--transient\nUsa\
ge: VBoxManage sharedfolder add <uuid|vmname> $
--transient\n--name\n--hostpath\n--readonly\n--automount\n--name\n$
--transient
--name
--hostpath
--readonly
--automount
--name

다음은 위와 비슷한 예제인데 {{string}} 형식의 스트링을 추출하는 것입니다.

$ cat file3
Usage: VBoxManage sharedfolder add <uuid|vmname> {{name}}
<name> {{hostpath}} <hostpath> {{readonly}} {{automount}} 
VBoxManage sharedfolder remove <uuid|vmname> {{name}} <name> {{transient}}

$ { set -f; echo `< file3` ;} |
    sed -En ':X s/(.*)(\{\{[a-z]+}})(.*)/\2\n\1/; /\{\{[a-z]+}}\n$/{p;q}; tX'
{{transient}}
{{name}}
{{hostpath}}
{{readonly}}
{{automount}}
{{name}}

예제 3

다음과 같은 xml 데이터가 이어질 경우 <ns1:queryDeviceInfoBtRequest.... 에서부터 </ns1:queryDeviceInfoBtRequest> 까지 데이터를 출력하는데 그중에서 equipmentId 값이 089471386301077634 일 경우만 프린트하는 것입니다.

<ns1:queryDeviceInfoBtRequest xmlns:ns1="http://integration.sprint.com/test/">
<ns12:equipmentId>543211386301077634</ns12:equipmentId>
<ns3:messageHeaderVersion>2</ns3:messageHeaderVersion>
<ns3:serviceName>queryDeviceInfoBt</ns3:serviceName>
</ns1:queryDeviceInfoBtRequest>

<ns1:queryDeviceInfoBtRequest xmlns:ns1="http://integration.sprint.com/integration/">
<ns12:equipmentId>089471386301077634</ns12:equipmentId>
<ns3:messageHeaderVersion>2</ns3:messageHeaderVersion>
<ns3:serviceName>queryDeviceInfoBt</ns3:serviceName>
</ns1:queryDeviceInfoBtRequest>
---------------------------------------------------------

$ sed -En '
/<ns1:queryDeviceInfoBtRequest/!b         # 첫라인과 매칭이 안되는 라인이면 끝으로 분기하고
h                                         # 매칭이 되면 hold 합니다.
:X
n                                         # pattern space 에 다음 라인을 읽어들여서
/<\/ns1:queryDeviceInfoBtRequest>/ {      # 마지막 라인 매칭을 테스트 합니다.
    H                                     # 매칭이 되면 hold space 에 append 한후에(H)
    g                                     # 그동안 hold space 에 모아두었던 데이터를
    /equipmentId>089471386301077634</p    # pattern space 로 옮긴후(g) id 매칭을 테스트해서
    b                                     # 매칭이 되면 출력하고 그렇지 않으면 끝으로 분기합니다.
}
H                             # 마지막 라인과 매칭이 안되면 계속 hold space 에 append 하고
bX                            # 다음 라인을 읽어들이기 위해 :X 레이블로 분기합니다.
' xmlfile

예제 4

"header" { ... } 형태로 구성되는 레코드인데요. 이때 "crosshair" 헤더에 "sprites/crosshairs" 항목이 있을 경우 해당 레코드를 삭제합니다. hold space 를 사용하지 않고 pattern space 만 이용하는 이 방법은 pattern space 에 모아두는 데이터가 커질수록 regex 매칭 영역이 계속해서 증가하므로 처리 속도가 크게 떨어질 수 있습니다.

"crosshair"
{
    "file"      "vgui/replay/thumbnails/"
    "x"         "0"
    "y"         "0"
    "width"     "64"
    "height"    "64"
}

"weapon"
{
    "file"      "sprites/bucket_bat_red"
    "x"         "0"
    "y"         "0"
    "width"     "200"
    "height"    "128"
}

"crosshair"
{
    "file"      "sprites/crosshairs" <---
    "x"         "32"
    "y"         "32"
    "width"     "32"
    "height"    "32"
}

"autoaim"
{
   "file"      "sprites/crosshairs"
   "x"         "0"
   "y"         "48"
   "width"     "24"
   "height"    "24"
}
-------------------------------------------

$ sed -f - <<\EOF file
    /^"crosshair"$/ {
        :X
        N         # "}" 라인이 입력될 때까지 pattern space 에 append 합니다.
        /}$/!bX   # "}" 와 매칭이 안될 경우 다음 라인을 읽어들이기 위해 :X 로 분기합니다.

        # `}` 와 매칭이 되었다는 것은 현재 pattern space 에 "crosshair" {...} 레코드
        # 전체가 입력되었다는 것을 의미하므로 "sprites/crosshairs" 항목을 조회하여
        # 매칭될 경우 `d` 명령으로 pattern space 를 삭제하고 새로운 명령 사이클을 시작합니다.

        /sprites\/crosshairs/d
    }
EOF

예제 5

중복되는 단어 삭제 하기

$ echo "I am am a a boy am am a am boy am" |
  sed -E ':X s#(\b\w+\b)(.*) \1#\1\2#g; tX'
I am a boy

$ echo "I am am a a boy am am a am boy am" |
  sed -E ':X s#(\b\w+\b)(.*) \1#\1\2#g; l; tX'     # 'l' 명령 사용
I am am a a boy am am a am boy$
I am am a a boy am am a boy$
I am am a a boy am a boy$
I am am a a boy a boy$
I am a a boy boy$
I am a boy$
I am a boy$
I am a boy

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

아래에서 1번과 2번은 같은 스트링이 됩니다.
가령 1번이 'am' 과 매칭이 되었다면 2번도 'am' 이 되어 결과적으로 
's#(am)(.*) am#\1\2#g' 와 같은 식이 됩니다.
식에 따라 '\1\2' 만 취하게 되면 마지막 'am' 은 삭제가 되겠죠.
마지막 'am' 이후의 스트링은 hidden 이므로 남게됩니다.

sed -E ':X s#(\b\w+\b)(.*) \1#\1\2#g; tX'
             ^^^^^^^^^     ^^
                12

예제 6

천 단위 콤마 넣기

$ echo "12345678" | sed -E ':X s#\B[0-9]{3}\b#,&#; tX'
12,345,678

$ echo "12345678" | sed -E ':X s#\B[0-9]{3}\b#,&#; l; tX'
12345,678$
12,345,678$
12,345,678$
12,345,678

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

식에서 맨앞에 '\B' 를 포함해야 합니다. 
그렇지 않으면 '12,345,678' 상태에서 '[0-9]{3}' 이 계속 '345' 와 매칭이 되어
종료되지 않고 아래와 같이 됩니다. 
'345' 는 앞, 뒤에 ',' 가 있어서 ',\b345\b,' 와 같은 상태인데 '\B' 를 붙이면
',\B345\b,' 가 되므로 매칭이 되지 않겠죠.

12,345,678
12,,345,678
12,,,345,678
12,,,,345,678
. . .

Quiz

입력값 중에서 foo 가 존재하면 barxxx 로 변경하고 존재하지 않으면 barzzz 로 변경하려면 어떻게 할까요?

s/foo/&/ 형식을 이용하면 입력값은 변경되지 않고 test flag 만 설정할 수 있습니다.

$ echo "aaa bbb foo ccc bar ddd" | 
    sed -En -e 's/foo/&/; tX; s/bar/zzz/; p; b' -e ':X s/bar/xxx/; p'
aaa bbb foo ccc xxx ddd

$ echo "aaa bbb zoo ccc bar ddd" |
    sed -En -e 's/foo/&/; tX; s/bar/zzz/; p; b' -e ':X s/bar/xxx/; p'
aaa bbb zoo ccc zzz ddd

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

# 다음과 같이 해도 됩니다.
$ echo "aaa bbb foo ccc bar ddd" |
    sed -En -e '/foo/{ s/bar/xxx/; p; b}; s/bar/zzz/; p'
aaa bbb foo ccc xxx ddd

$ echo "aaa bbb zoo ccc bar ddd" |
    sed -En -e '/foo/{ s/bar/xxx/; p; b}; s/bar/zzz/; p'
aaa bbb zoo ccc zzz ddd

2 .

매칭되는 스트링을 다른 스트링으로 치환하는 것은 s 명령으로 쉽게 할수가 있는데요. 스트링의 length 를 그대로 유지하면서 다른 문자로 치환하려면 어떻게 할까요?

다음은 "VBoxManage natnetwork" 스트링의 length 를 유지하면서 space 로 치환합니다.

$ cat file
VBoxManage natnetwork       add --netname <name>
VBoxManage natnetwork       remove --netname <name>
VBoxManage natnetwork       modify --netname <name>

$ cat file | sed -En -e 's/VBoxManage natnetwork/\a&\a/;' \
    -e ':X s/\a.(.*)\a/ \a\1\a/; tX; s/\a\a//; p'
                            add --netname <name>
                            remove --netname <name>
                            modify --netname <name>

$ cat file | sed -En -e 's/VBoxManage natnetwork/\a&\a/;' \
    -e ':X s/\a.(.*)\a/ \a\1\a/; tX; s/\a\a//; s/[ ]{6}/foobar/; p'
foobar                      add --netname <name>
foobar                      remove --netname <name>
foobar                      modify --netname <name>

# perl 의경우는 e (execute) 플래그를 이용하면 치환값에 함수를 사용할 수 있습니다.
$ cat file | perl -pe 's/(VBoxManage natnetwork)/" " x length($1)/e'

$ cat file | perl -pe 's/(VBoxManage natnetwork)/foobar . " " x (length($1) - 6)/e'

다음은 [Ss]h 로 시작하는 단어를 length 를 유지하면서 모두 . 으로 치환합니다.

$ echo "She sells sea shells by the sea shore" | 
    sed -E ':X s/([Ss]h\.*)[^. ]/\1./; tX; s/[Ss]h/../g'

... sells sea ...... by the sea .....

3 .

옵션 스트링에 [ ] 괄호를 이용해서 선택 값을 나타낼 수 있는데요. 다음과 같이 확장이 되게 스크립트를 작성하는 것입니다. 괄호가 없는 경우는 그대로 출력하면 됩니다.

--[no-]leading-underscore        -fenable-[tree|rtl|ipa]-bar=        -fbounds-check

--no-leading-underscore          -fenable-ipa-bar=                   -fbounds-check
--leading-underscore             -fenable-rtl-bar=  
                                 -fenable-tree-bar=
# 첫 번째의 경우는 비교적 간단합니다.
$ echo '--[no-]leading-underscore' |  sed -En '           
    h; s/(.*)\[([^]]*)](.*)/\1\2\3/; p;      # [ ] 괄호 안에 있는 스트링을 추출해서 출력
    g; s/\[[^]]*]//; p;                      # [ ] 괄호 전체를 삭제해서 출력
'
--no-leading-underscore
--leading-underscore

# 두 번째가 좀 까다로운데 [tree|rtl|ipa] 값을 [(\|?(\w+))+] 을 이용해 추출하면
# \3 의 값은 마지막 ipa 가 됩니다. 그 다음 반복에서는 또 마지막 rtl 이 되고 ...
# 마지막 라인에서는 \|?\w+] 를 이용해 |ipa] 부분을 삭제해서 [tree|rtl] 로 만듭니다.
# 반복을 계속하다가 최종 [] 스트링만 남게되면 종료합니다. 
$ echo '-fenable-[tree|rtl|ipa]-bar=' |  sed -En '                          
    :X h; tR1; :R1 s/(.*)\[(\|?(\w+))+](.*)/\1\3\4/; p; T;
    g; s/\|?\w+]/]/; /\[]/!bX   
'
-fenable-ipa-bar=
-fenable-rtl-bar=
-fenable-tree-bar=

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

# 위의 두 작업을 하나로 합치면 다음과같이 됩니다.
$ echo  '-fenable-[tree|rtl|ipa]-bar=' |  sed -En '
    :X h; tR1; :R1 s/(.*)\[(\|?(\w+-?))+](.*)/\1\3\4/; p; T; 
    g; s/\|?\w+-?]/]/; tR2 :R2 s/-\[]([[:alnum:]])/-\1/p; t; /\[]/!bX'

4 .

텍스트 데이터 중에는 ( ), < >, { } 같은 괄호가 쌍으로 nesting 되어 있는 경우가 많습니다. 다음 스트링에서 괄호만 모두 제거하려면 어떻게 할까요?

첫번째 방법:

$ echo 'foo (aa, ((bb, (cc)), (dd))) bar (xx, ((yy), zz)) zoo' |
sed -E ':X s/\([^()]*\)//g; l; tX'

foo (aa, ((bb, ), )) bar (xx, (, zz)) zoo$
foo (aa, (, )) bar (xx, ) zoo$
foo (aa, ) bar  zoo$
foo  bar  zoo$
foo  bar  zoo$
foo  bar  zoo

다음과 같이하면 괄호를 왼쪽에서부터 차례로 제거해 나갈수 있습니다.

$ echo 'foo (aa, ((bb, (cc)), (dd))) bar (xx, ((yy), zz)) zoo' |
sed -E ':X s/([^)]*)(\([^)]*\))/\1/; l; tX'

foo (aa, ((bb, ), (dd))) bar (xx, ((yy), zz)) zoo$
foo (aa, (, (dd))) bar (xx, ((yy), zz)) zoo$
foo (aa, (, )) bar (xx, ((yy), zz)) zoo$
foo (aa, ) bar (xx, ((yy), zz)) zoo$
foo  bar (xx, ((yy), zz)) zoo$
foo  bar (xx, (, zz)) zoo$
foo  bar (xx, ) zoo$
foo  bar  zoo$
foo  bar  zoo$
foo  bar  zoo

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

# 처음 (cc) 괄호가 삭제되는 과정을 살펴보면 다음과 같습니다.

1. 먼저 '[^)]*' regexp 에의해 'foo (aa, ((bb, (cc' 까지 매칭이 됩니다.

2. 그다음 '\([^)]*\)' regexp 에의해 '(...)' 형태가 매칭이 되어야 하는데
   이때 위의 1번을 만족하면서 2번이 매칭이될수 있는 형태는 다음과 같이 됩니다.

     foo (aa, ((bb,   (cc)
     ^^^^^^^^^^^^^^^  ^^^^
          123. 's/.../\1/' 식에 의해 1번만 취하게 되면 2번 이후는 hidden 이므로 결과적으로
   2번이 삭제되게 됩니다.

다음은 __attr__( 괄호 ) 형태의 스트링만 제거하는 것입니다.

$ echo '((foo)) __attr__((aa,(cc)),(xx)) ((bar)) __attr__((dd,((xx)),aa)) ((zoo))' |
sed -E ':X s/__attr__\s*\([^()]*\)//g; s/(__attr__[^)]*)(\([^)]*\))/\1/g; l; tX'

((foo)) __attr__((aa,),(xx)) ((bar)) __attr__((dd,(),aa)) ((zoo))$
((foo)) __attr__(,(xx)) ((bar)) __attr__((dd,,aa)) ((zoo))$
((foo)) __attr__(,) ((bar)) __attr__() ((zoo))$
((foo))  ((bar))  ((zoo))$
((foo))  ((bar))  ((zoo))$
((foo))  ((bar))  ((zoo))

5 .

다음은 괄호 안에 있는 , 콤마를 모두 제거하는 것입니다.

$ echo "John Doe, model (dell, 24, inch), 847483992, (100, foo, infinity) loop" |
sed -E ':X s/(\([^)]*),/\1/g; l; tX'

John Doe, model (dell, 24 inch), 847483992, (100, foo infinity) loop$
John Doe, model (dell 24 inch), 847483992, (100 foo infinity) loop$
John Doe, model (dell 24 inch), 847483992, (100 foo infinity) loop$
John Doe, model (dell 24 inch), 847483992, (100 foo infinity) loop

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

1. 먼저 '\([^)]*' 식에 의해 '(dell, 24, inch' 까지 매칭이 됩니다.

2. 그런데 위의식 바로뒤에 ',' 콤마가 붙었으므로 1번식 과 ',' 를 모두 만족하는 값은
   '(dell, 24,' 까지가 됩니다.

3. 이후 '\1' 에의해 ',' 콤마를 제외한 부분만 취하게 되면 괄호안에 있는 콤마가 삭제됩니다.