Command Substitution

$( <COMMANDS> )
`<COMMANDS>`

명령 치환은 subshell 에서 실행되는데 명령 실행 결과 stdout 값이 pipe 를 통해 전달됩니다. 그러므로 명령 치환도 일종의 IPC (Inter Process Communication) 이라고 할 수 있습니다. 표현식은 두 가지 형태가 존재하는데 backtick 은 괄호형 보다 타입 하기가 편해서 비교적 간단한 명령을 작성할 때 많이 사용합니다. 그런데 표현식을 열고 닫는 문자가 같은 관계로 nesting 하여 사용할 수가 없고 escape sequence 가 다르게 처리됩니다. 그래서 복잡한 명령을 작성하거나 nesting 이 필요할 때는 괄호형을 사용하는 것이 좋습니다.

stdout 이 파이프로 연결된 것을 볼 수 있습니다.

$ AA=$( pgrep -d, -x ibus )
$ echo $AA
17516,17529,17530,17538,17541

$ AA=`pgrep -d, -x ibus`
$ echo $AA
17516,17529,17530,17538,17541

$ cat /proc/`pidof awk`/maps

$ cd /lib/modules/`uname -r`

backtick 을 사용할 때 한가지 주의할 점은 escape sequence 가 다르게 처리됩니다.

# 원본 명령
$ grep -lr --include='*.md' ' \\t\\n'
filename_expansion.md
word_splitting.md

# 괄호형은 원본 명령 그대로 사용할 수 있다.
$ echo $( grep -lr --include='*.md' ' \\t\\n' )
filename_expansion.md word_splitting.md
--------------------------------------------------

# backtick 은 escape sequence 가 다르게 처리되어 값이 출력되지 않는다.
$ echo `grep -lr --include='*.md' ' \\t\\n'`
$
$ echo `grep -lr --include='*.md' ' \\\\t\\\\n'`
filename_expansion.md word_splitting.md

Parent 프로세스의 변수값을 변경할 수 없습니다.

subshell 은 main 프로세스의 child 프로세스에 해당합니다. 각 프로세스는 독립적인 주소 공간을 가지므로 child 프로세스에서 설정, 변경한 값은 main 프로세스의 값에 영향을 주지 못합니다. 다음 예를 보시면 명령치환을 이용해 index 값을 40 으로 변경하였으나 기대했던 것과는 달리 main 프로세스의 값이 변경되지 않고 30 으로 나오는것을 볼 수 있습니다.

#!/bin/bash

index=30

change_index() {
  index=40
}

result=$(change_index; echo $index)

echo $result
echo $index

######### output ########

40
30

Quotes 이 중첩돼도 된다

명령치환의 결과로 나온 값은 일반 변수를 사용할 때와 같이 단어분리 및 globbing 대상이 됩니다. 그러므로 공백문자를 유지해야 하거나 globbing 이 발생하면 안될 경우 항상 double quotes 을 해야 합니다. 명령문 자체에서도 quotes 을 많이 사용하는데 명령치환 표현식에도 사용하게 되면 quotes 이 중첩되는 경우가 발생합니다. 이럴 경우를 위해서 shell 은 $( ... ) 표현식을 만나면 그 안의 내용은 별도로 분리해서 개별적으로 처리한다고 합니다. 그러므로 quotes 중첩문제를 피하려고 escape 하거나 할 필요 없이 그대로 명령문을 작성할 수 있습니다.

# 명령치환을 quote 하지 않은 경우
$ echo $( echo "
> I
> like
> winter     and     snow" )

I like winter and snow

# 명령치환을 quote 하여 공백과 라인개행이 유지되었다.
$ echo "$( echo "
> I
> like
> winter     and     snow" )"

I
like
winter     and     snow

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

# quotes 이 여러번 중첩되어도 상관없다.

$ echo "$(echo "$(echo "$(date)")")"       
Thu Jul 23 18:34:33 KST 2015

null 문자를 보낼 수 없다

변수값으로 NULL 문자를 저장할 수 없는것과 동일하게 명령치환 값으로 NULL 문자를 전달할 수 없습니다.

$ ls          # aaa, bbb, ccc  3개의 파일이 존재
aaa  bbb  ccc

# 파이프, redirection 은 null 값이 정상적으로 전달된다.
$ find * -print0 | od -a
0000000   a   a   a nul   b   b   b nul   c   c   c nul
0000014

# 명령치환은 null 값이 모두 제거됩니다.
$ echo -n "$(find * -print0)" | od -a
0000000   a   a   a   b   b   b   c   c   c
0000011

변수에 값을 대입할때 마지막 newline 은 제거된다.

$ AA=$'hello\n\n\n'
$ echo -n "$AA" | od -a
0000000   h   e   l   l   o  nl  nl  nl    # 정상적으로 newline 이 표시된다.

$ AA=$(echo -en "hello\n\n\n")
$ echo -n "$AA" | od -a
0000000   h   e   l   l   o                # 명령치환은 newline 이 모두 제거된다.

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

$ cat file         # 파일의 마지막에 4 개의 newline 이 존재.
111
222



$ cat file | od -a
0000000   1   1   1  nl   2   2   2  nl  nl  nl  nl
0000013

$ echo -n $(cat file) | od -a              # 파일의 마지막 newline 이 모두 제거 되었다.
0000000   1   1   1  nl   2   2   2
0000007

$( < filename )

이건 bash 에서 제공하는 기능인데 $( cat filename ) 의 간단 버전입니다.

$ cat file
11
44
55

$ echo $( < file )
11 44 55

$ echo $( cat file )
11 44 55