Quotes

Shell 에서 두 번째로 중요한 개념은 quotes 이라고 할 수 있습니다. shell 에서 quotes 은 숫자나 스트링 값을 구분하기 위한 용도로 사용하지 않습니다. 123, "123", '123' 은 모두 같고 abc, "abc", 'abc' 들은 차이가 없으며 모두 다 shell 에서는 스트링입니다. shell 에서 quotes 은 다음과 같은 용도로 사용됩니다.

  • 공백으로 분리되는 여러 개의 인수 들를 하나의 인수로 만들 때
    ( sed, awk 스크립트를 quotes 을 이용해 작성하는 이유가 하나의 인수로 만들기 위해서입니다. )

  • 라인개행이나 둘 이상의 공백문자를 유지하기위해

  • 단어분리, globbing 발생을 금지하기 위해

  • shell 키워드, 메타문자, alias 와 같이 shell 에서 특수기능을 하는 문자, 단어를 단순히 명령문의 스트링으로 만들기위해

  • 문자 그대로 스트링을 강조하기 위해

최종적으로 명령이 실행될 때는 사용된 quotes 이 제거된 후에 인수가 전달됩니다.

-------- args.sh --------
#!/bin/bash

echo arg1 : "$1"
echo arg2 : "$2"

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

$ ./args.sh 111 "111"      # quote 을 한것과 하지않은 것은 차이가 없다
arg1 : 111
arg2 : 111

$ ./args.sh hello world    # 두개의 인수를 나타낸다.
arg1 : hello 
arg2 : world

$ ./args.sh "hello world"  # quote 을 하면 한개의 인수가됨
arg1 : hello world
arg2 :

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

$ AA="hello world"

$ ./args.sh $AA            # 두개의 인수를 나타낸다.
arg1 : hello 
arg2 : world

$ ./args.sh "$AA"          # quote 을 하면 한개의 인수가됨
arg1 : hello world
arg2 :

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

# sed 스크립트를 quote 하지 않아 오류 발생
$ sed -n 1p; 2p; 3p file
ERR

# quote 을 해야 sed 스크립트가 하나의 인수로 전달됩니다.
$ sed -n '1p; 2p; 3p' file
OK

------------------------
#!/bin/bash
# 다음 세 명령은 모두 같고 차이가 없습니다.
# 명령문에서 사용된  quotes 은 실행될 때 shell 에 의해 자동으로 제거됩니다.

ls -al
ls "-al"
"ls" '-al'

특수 기능을 갖는 문자들

다음 세 문자는 shell 메타문자로 명령행 상에서 특수한 기능을 가집니다.

문자 기능
$ 매개변수 확장, 산술 확장, 명령 치환에 사용
` 명령 치환에 사용 (backtick)
! history 확장에 사용 (프롬프트상 에서만)
$ AA=hello

$ echo $AA world `date +%Y`   # $AA 변수가 확장이 되고 date 명령치환이 되었다.      
hello world 2015

# 특수문자를 escape 할 경우
$ echo \$AA world \`date +%Y\`      
$AA world `date +%Y`

특수기능을 갖는 문자나 단어를 escape 하는 방법

shell 에서는 escape 할때 \ 문자 외에 quote 을 사용할 수 있습니다. quote 을 하면 특수기능이 없어지고 단순히 명령문을 위한 스트링으로 사용됩니다.

# (  )  ; shell 메타문자를 quote 하여 기능을 상실. find 명령을 위한 스트링이 되었다.
# \(  \)  \; 한것과 같습니다.
$  find * -type f '(' -name "*.log"  -or  -name "*.bak" ')' -exec rm -f {} ';'

# background job 을 생성할때 사용되는 & 메타문자를 quote 하여 기능을 상실.
#  \& 한것과 같습니다.
$ echo hello '&'
hello &

# 메타문자인 \ 을 quote 하여 기능을 상실. 결과적으로 \n 가 되었다.
#  \\n 한것과 같습니다.
$ echo hello world | tr ' ' '\'n
hello
world

# grep 명령에 설정돼 있는 alias 가 escape 되었다.
# \grep 한것과 같습니다.
$ 'grep' 2015-07 data.txt
...

# shell 키워드인 time 이 escape 되어 외부명령인 /usr/bin/time 이 실행되었다.
# \time 한것과 같습니다.
$ 'time'
Usage: time [-apvV] [-f format] [-o file] [--append] [--verbose]
       [--portability] [--format=format] [--output=file] [--version]
       [--quiet] [--help] command [arg...]

No quotes

위에서 살펴본 바와 같이 no quotes 상태에서는 shell 키워드, 메타문자, alias, glob 문자, quotes, space, tab, newline 모두를 escape 하여 해당 기능을 disable 할 수 있습니다.

명령행 상에서 공백은 인수를 구분하는데 사용됩니다. 둘 이상의 공백은 의미가 없으므로 하나의 공백으로 대체됩니다. no quotes 에서는 공백도 escape 할 수 있습니다. 공백을 escape 하면 두 개의 인수가 하나가 됩니다.

----------- test.sh -----------
#!/bin/bash

echo arg1 : "$1"
echo arg2 : "$2"
-------------------------------

$ ./test.sh hello world           # 인수가 2개
arg1 : hello
arg2 : world

$ ./test.sh hello\ world          # 공백을 escape 하여 인수가 하나가 되었다.
arg1 : hello world
arg2 : 

$ echo hello             world    # 둘 이상의 공백은 하나로 줄어든다.
hello world                       

$ echo hello\ \ \ \ \ \ \world    # 공백이 유지된다.
hello      world

no quotes 상태에서 escape 문자 사용예.

# 모든 문자가 escape 되므로 t n 이 echo 명령에 전달된다.
$ echo -e foo\tbar\n123  
footbarn123

# 다음과 같이 하면 \t \n 이 echo 명령에 전달되어 escape 문자가 처리된다.
$ echo -e foo\\tbar\\n123
foo    bar
123

! history 확장 escape

$ date
Sat Jul 18 00:06:41 KST 2015

$ echo hello !!     
echo hello date     # 이전 명령 history 확장
hello date

$ echo hello \!!    # escape 하여 history 확장 기능 disable
hello !!

\ 문자 escape

# no quotes 상태에서는 모든문자가 escape 되므로 'tr : n' 와 같아진다.
$ echo 111:222:333 | tr : \n
111n222n333

$ echo 111:222:333 | tr : \\n     # tr : '\n' 와 같은 결과
111
222
333

" , ' quote 문자 escape

$ echo double quotes \" , single quotes \'
double quotes " , single quotes '

행의 마지막에 \ 를 붙이고 개행을 하면 \newline 과 같이 되어 newline 을 escape 한 결과를 같습니다. (\ 뒤에 다른 문자가 오면 안됨)

$ echo "I like \          
> winter and \
> snow"

I like winter and snow   # newline 이 escape 되어 기능을 상실해 한줄이됨.

Double quotes ( " " )

Double quotes 안에서는 $ ` ! 특수기능을 하는 문자들이 해석되어 실행되고 공백과 개행이 유지됩니다. 변수 사용 시에도 동일하게 적용되므로 quote 을 하지 않으면 공백과 개행이 유지되지 않습니다.

$ echo "I
> like
> winter       and         snow"
I                                   # 공백과 개행이 유지 된다.
like
winter       and         snow

#####  변수 사용시  #####

$ AA="this          is
two          lines"

$ echo $AA              # 공백과 개행이 유지되지 않는다.
this is two lines

$ echo "$AA"            # 공백과 개행이 유지된다.
this          is
two          lines

Double quotes 에서 escape 되는 문자들

double quotes 과 single quotes 의 차이점이 이것입니다. double quotes 에서는 위의 문자들이 특수한 기능을 가지고 사용되기 때문에 \ 문자로 escape 할 수가 있는 반면 single quotes 에서는 문자 그대로 출력됩니다.

$ echo '\$'     # single quotes
\$
$ echo "\$"     # double quotes 에서는 특수 기능을 하는 '$' 문자가 escape 된다. 
$

$ echo '\`'
\`
$ echo "\`"
`

$ echo '\\'
\\
$ echo "\\"
\

$ echo 'quotes\
test'
quotes\          # single quotes 은 문자 그대로 표시된다.
test

$ echo "quotes\
test"
quotestest       # double quotes 에서는 newline 이 escape 되어 한줄로 나온다.

double quotes 을 사용할때 한가지 주의해야될 사항은 ! 문자를 이용한 command history 확장 이 double quotes 에서도 일어난다는 것입니다. 이것은 command history 기능이 사용되는 프롬프트 상에서만 적용되는 사항으로 스크립트 파일을 작성할 때는 해당되지 않습니다. 자세한 내용은 해당 페이지를 참조하세요.

Array 와 관련된 특수기능

double quotes 은 array 와 관련해서 특수한 기능이 있는데 전체 원소를 나타내는 ${arr[@]} 를 quote 하면 그 의미는 "${arr[0]}" "${arr[1]}" "${arr[2]}" ... 와 같게 되고 ${arr[*]} 를 quote 하게 되면 그 의미는 "${arr[0]}X${arr[1]}X${arr[2]}X..." 와 같게 됩니다. 여기서 X 는 IFS 값의 첫번째 문자를 나타냅니다.
"$@", "$*" positional parameters 에서도 동일하게 적용됩니다.

변수값이 null 일때 quote 한것과 안한것의 차이

$ AA=""

# quote 을 하지 않으면 인수에 포함되지 않는다.
$ args.sh 1 2 $AA 3 
$0 : /home/mug896/bin/args.sh
$1 : 1
$2 : 2
$3 : 3

$ args.sh 1 2 "$AA" 3
$0 : /home/mug896/bin/args.sh
$1 : 1
$2 : 2
$3 :        # null 값 인수
$4 : 3

가령 다음과 같은 스크립트에서 $comp 변수값이 -c 일 경우는 tmpfile 이 컴파일된 오브젝트 파일이 되고 $comp 변수값이 null 일 경우는 링크가 완료된 실행파일을 만들고자 한다면 $comp 변수를 quote 하면 안되겠죠. 왜냐하면 quote 에의해 null 값이 하나의 인수로 전달되어 두 번째와 같이 오류가 발생하기 때문입니다.

gcc $comp -o tmpfile hello.c
........................................

gcc "" -o tmpfile hello.c
gcc: error: : No such file or directory

변수를 quote 하는 것은 오류 메시지 출력에도 영향을 줍니다. 변수를 quote 하면 좀 더 명확한 오류메시지가 출력됩니다. 따라서 명령문을 작성할 때 위와 같은 경우가 아니라면 변수를 quote 해서 사용하는 것이 좋습니다.

$ AA=""

$ echo hello 2>& $AA           # echo hello 2>& 
bash: $AA: ambiguous redirect

# quote 을 하면 좀 더 명확한 오류메시지가 출력된다.
$ echo hello 2>& "$AA"         # echo hello 2>& ""
bash: "$AA": Bad file descriptor
....................................

$ echo hello > $AA             # echo hello >
bash: $AA: ambiguous redirect

$ echo hello > "$AA"           # echo hello > ""
bash: : No such file or directory

Single quotes ( ' ' )

별다른 기능 없이 모든 문자를 있는 그대로 표시합니다. escape 도 되지 않습니다. 이 안에서 single quotes 을 사용하려면 뒤에 이어지는 $' ' 을 사용해야 합니다.

$ AA=hello

$ echo '$AA world 
> `date` 
> \$AA
> '
$AA world 
`date` 
\$AA

single quotes 사용시 ' 문자 입력 방법

$ echo 'foo'\''bar'
foo'bar

$ echo 'foo'"'"'bar'
foo'bar

Single quotes 사용이 필요한 경우

command string 이나 trap handler 를 작성할 때 double quotes 을 사용하면 작성 당시에 변수값이 확장되어 정의가 되므로 실행 시에 원하는 값이 표시되지 않을 수 있습니다.

1. command string 에서
$ AA=100;
$ sh -c "AA=200; echo $AA"  # double quotes 사용
100
$ sh -c 'AA=200; echo $AA'  # single quotes 사용
200

다음은 find 명령을 이용해 ~/.cache 내에 있는 각 디렉토리 별로 디스크 사용량을 조회하는 것인데요. -exec 옵션에는 실행할 외부 명령을 작성하고 \; 로 끝을 표시합니다. 이때 명령 스트링에서 사용된 {} 가 매칭 된 디렉토리 명으로 치환되어 실행됩니다.

$ find ~/.cache -maxdepth 1 -type d -exec du -hs {} \; | sort -hr
1.9G    /home/mug896/.cache 
601M    /home/mug896/.cache/google-chrome 
471M    /home/mug896/.cache/mozilla
110M    /home/mug896/.cache/apt-file 
...
...

위와 동일한 역할을 하는 명령을 single, double quotes 을 비교해보기 위해 sh -c 를 이용해 작성한 것입니다.

명령문에 sh -c 를 사용하는 것은 command line 개념을 참고하세요.

# double quotes 을 사용할 경우
# {} 가 find 명령에 전달되어 디렉토리 명으로 바뀌기 전에 $( du ... ) 가 처리되므로 오류 발생
$ find ~/.cache -maxdepth 1 -exec \
    sh -c "if test -d '{}'; then echo \"$( du -hs '{}' )\"; fi" \; | sort -hr
du: cannot access '{}': No such file or directory

# single quotes 을 사용할 경우는 정상적으로 실행됨
$ find ~/.cache -maxdepth 1 -exec \
    sh -c 'if test -d "{}"; then echo "$( du -hs "{}" )"; fi' \; | sort -hr
OK

# 다음은 xargs 명령을 이용한 것인데 -exec 의 경우와 동일하게 적용됩니다.
$ find ~/.cache -maxdepth 1 -print0 |
    xargs -0i sh -c 'if test -d "{}"; then echo "$( du -hs "{}" )"; fi' | sort -hr
OK
2. trap handler 에서

해당 페이지 참조

3. prompt 설정에서

해당 페이지 참조

$'  '

이건 ' ' 와 같은데 escape 문자를 사용할 수 있습니다.
escape 문자가 처리되고 난 결과는 $ 가 제외된 ' ' 상태가 됩니다.

sh 에서는 사용할 수 없습니다.

$ echo $'I like\n\'winter\'\tand\t\'snow\''   # \n, \t, \' escape 문자가 사용되었다.
I like
'winter'    and    'snow'

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

$ IFS=$'\n'
$ IFS=$' \t\n'

Quotes 을 서로 붙여 사용하기

두개의 quotes 을 공백을 두지 않고 서로 붙이면 하나가 됩니다. 이 원리는 변수를 포함하는 명령 스트링을 만들거나 함수에 전달할 인수를 하나로 만들때 유용하게 사용할 수 있습니다.

명령 스트링을 만들 때

' ' 을 사용해 명령문을 작성하였는데 그 안에 shell 변수를 사용할 일이 생기면 다음과 같이
' ' 을 분리한 후 double quotes 한 변수를 공백 없이 붙여 사용하면 됩니다. 변수를 quote 하지 않거나 single quotes 과 double quotes 사이에 공백이 있으면 안됩니다.

# 원본 명령
sed -r 's/foo/bar/g' 

# single quotes 을 분리한 후 shell 변수를 double quotes 하여 공백 없이 붙인다.
sed -r 's/'"$var1/$var2"'/g'

awk 는 스크립트 내에서 $ 문자를 사용하기 때문에 기본적으로 single quotes 을 이용해 작성하는데요. 다음의 경우를 보면 -exec 옵션에 사용된 sh -c 명령이 single quotes 에의해 작성되고 있는데 그 안에 있는 awk 명령에서도 single quotes 이 사용되고 있습니다. 이와 같은 경우 다음과 같이 일단 quotes 을 분리한후 \'"'" 를 사용해 연결하면 sh -c '...' 내에서 실행되는 awk 명령에서도 single quotes 을 사용할 수 있습니다.

$ find * -name 'Packages*' -type f -exec \
    sh -c 'echo $(md5sum -b "{}" | awk '\''{print $1}'\'') $(stat -c %s "{}") "{}"' \;

b49dd0f63bca9b3a139c5af3dd94c816 380 Packages
e805c26ff46c6e138e3cd198cff281ea 301 Packages.bz2
997a7252f202566a1e5fdc5b50c2ffdf 283 Packages.gz

명령의 인수를 만들 때

명령에 인수를 만들어 전달할 때도 두 quotes 을 서로 붙여 사용하면 하나의 인수가 됩니다.

$ ./args.sh 11 "hello "$'$world \u2665' 33   # $' ' quotes 을 사용

$0 : ./args.sh
$1 : 11
$2 : hello $world$3 : 33