Quotes
Shell 에서 두 번째로 중요한 개념은 quotes 이라고 할 수 있습니다.
shell 에서 quotes 은 숫자나 스트링 값을 구분하기 위한 용도로 사용하지 않습니다.
123
, "123"
, '123'
은 모두 같고 abc
, "abc"
, 'abc'
들은 차이가 없으며 모두 다 shell 에서는 스트링입니다.
shell 에서 quotes 은 다음과 같은 용도로 사용됩니다.
공백으로 분리되는 여러 개의 스트링을 하나의 인수로 만들 때
( sed, awk 스크립트를 quotes 을 이용해 작성하는 이유가 하나의 인수로 만들기 위해서입니다. )라인 개행이나 둘 이상의 공백을 유지하기 위해
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 111 222 # 두개의 인수를 나타낸다.
arg1 : 111
arg2 : 222
$ ./args.sh "111 222" # quote 을 하면 한개의 인수가 됨
arg1 : 111 222
arg2 :
------------------------
$ AA="111 222"
$ ./args.sh $AA # 두개의 인수가 된다.
arg1 : 111
arg2 : 222
$ ./args.sh "$AA" # quote 을 하면 한개의 인수가 됨
arg1 : 111 222
arg2 :
------------------------
# sed 스크립트를 quote 하지 않아 오류 발생
$ sed -n 1p; 2p; 3p file
ERROR
# quote 을 해야 sed 스크립트가 하나의 인수로 전달됩니다.
$ sed -n '1p; 2p; 3p' file
OK
------------------------
#!/bin/bash
ls -al # 다음 세 명령은 모두 같고 차이가 없습니다.
ls "-al" # 명령문에서 사용된 quotes 은 shell 에의해 해석된 후에
"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 할때 \
문자 외에 quotes 을 사용할 수 있습니다.
quote 을 하면 특수 기능이 없어지고 단순히 명령문을 위한 스트링이 됩니다.
# shell 에서 사용되는 ( ) ; 메타문자를 quote 하여 기능을 상실. find 명령을 위한 스트링이 됩니다.
# \( \) \; 한것과 같습니다.
$ find * -type f '(' -name "*.log" -or -name "*.bak" ')' -exec rm -f {} ';'
# background 프로세스를 생성할때 사용하는 & 메타문자를 quote 하여 기능을 상실.
# \& 한것과 같습니다.
$ echo hello '&'
hello &
# escape 문자인 \ 를 quote 하여 기능을 상실. 결과적으로 \n 가 됩니다.
# \\n 한것과 같습니다.
$ echo hello world | tr ' ' '\'n
hello
world
# t 문자에 설정돼 있는 alias 가 escape 되어 기능하지 않게됩니다.
# \t 한것과 같습니다.
$ alias t='type -a'
$ 't' time
...
# 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 상태에서는 기본적으로 모든 문자가 escape 됩니다. 따라서 shell 키워드, 메타문자, alias, glob 문자, quotes, whitespace 문자를 escape 하여 해당 기능을 disable 할 수 있습니다.
whitespace 문자는 space, tab, newline 을 말합니다.
$ echo \a\b\c\d\ \!\@\$\%\^\&\*\(\)\{\}\[\]\<\>\/\\ ... # no quotes
abcd !@$%^&*(){}[]<>/\ ...
$ echo "\a\b\c\d" ... # double quotes
\a\b\c\d ...
$ echo '\a\b\c\d' ... # single quotes
\a\b\c\d
명령행 상에서 공백은 인수를 구분하는데 사용됩니다. 둘 이상의 공백은 의미가 없으므로 하나의 공백으로 대체됩니다. no quotes 에서는 공백도 escape 할 수 있습니다. 공백을 escape 하면 두 개의 인수가 하나가 됩니다.
----------- args.sh -----------
#!/bin/bash
echo arg1 : "$1"
echo arg2 : "$2"
-------------------------------
$ ./args.sh hello world # 인수가 2개
arg1 : hello
arg2 : world
$ ./args.sh hello\ world # 공백을 escape 하여 인수가 하나가 됩니다.
arg1 : hello world
arg2 :
$ echo hello world # 둘 이상의 whitespace 문자는 single space 로 줄어든다.
hello world # tab 문자일 경우 space 로 변경됨
$ 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 한 결과를 같습니다.
이것을 backslash-newline
이라고 하고 \
뒤에 다른 문자가 오면 안 됩니다.
$ echo "I like \
> winter and \
> snow"
I like winter and snow # newline 이 escape 되어 기능을 상실해 한줄이됨.
대입연산 에서도 escape 은 처리됩니다.
$ regex=\w*foo\w*
$ echo "$regex"
w*foow*
$ regex='\w*foo\w*'
$ echo "$regex"
\w*foo\w*
Double quotes ( " " )
Double quotes 안에서는 $
`
!
특수기능을 하는 문자들이 해석되어 실행되고 공백과 개행이 유지됩니다.
변수 사용 시에도 동일하게 적용되므로 quote 을 하지 않으면 공백과 개행이 유지되지 않습니다.
$ echo "I
> like
> winter and snow"
I
like
winter and snow # 공백과 개행이 유지 된다.
##### 변수 사용시 #####
$ AA="this is
two lines"
$ echo $AA # whitespace 문자들이 single space 로 변경되고
this is two lines # 공백과 개행이 유지되지 않는다.
$ echo "$AA" # quote 을 하여 공백과 개행이 유지된다.
this is
two lines
Double quotes 에서 escape 되는 문자들
double quotes 에서는 위의 문자들이 특수한 기능을 가지고 사용되기 때문에 \
문자로 escape 할 수가 있습니다.
single quotes 과 비교해 보면 다음과 같습니다.
$ echo '\$ \` \" \\' # single quotes
\$ \` \" \\
$ echo "\$ \` \" \\" # double quotes
$ ` " \
$ echo "\( \{ \[ \A \@ \'" # 그외 문자들은 그대로 '\' 가 출력된다.
\( \{ \[ \A \@ \'
# single quotes 은 문자 그대로 출력된다.
$ echo 'quotes\
> test'
quotes\
test
# double quotes 에서는 newline 이 escape 되어 한줄로 나온다.
$ echo "quotes\
> test"
quotestest
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=""
# args.sh 은 명령 라인에서 전달한 인수들을 출력해 주는 스크립트.
$ args.sh xx yy $AA zz
$0 : /home/mug896/bin/args.sh
$1 : xx
$2 : yy # quote 을 하지 않으면 인수에 포함되지 않는다.
$3 : zz
$ args.sh xx yy "$AA" zz
$0 : /home/mug896/bin/args.sh
$1 : xx
$2 : yy
$3 : # quote 을 하면 null 값 인수가 생긴다.
$4 : zz
가령 다음과 같은 스크립트에서 $comp
변수값이 -c
일 경우는
tmpfile 이 컴파일된 오브젝트 파일이 되고
$comp
변수값이 null 일 경우는 링크가 완료된 실행파일을 만들고자 한다면
$comp
변수를 double quote 하면 안되겠죠.
왜냐하면 quotes 에의해 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
bash: $AA: ambiguous redirect
# quote 을 하면 좀 더 명확한 오류메시지가 출력된다.
$ echo hello 2>& "$AA"
bash: "$AA": Bad file descriptor
....................................
$ echo hello > $AA
bash: $AA: ambiguous redirect
$ echo hello > "$AA"
bash: : No such file or directory
Single quotes ( ' ' )
별다른 기능 없이 모든 문자를 있는 그대로 표시합니다. escape 도 되지 않습니다.
이 안에서 single quotes 을 사용하려면 뒤에 이어지는 $' '
를 사용해야 합니다.
$ AA=hello
# 변수값도 확장이 안되고 개행도 있는 그대로 유지된다.
$ echo '$AA world
> `date`
> \$AA
> '
$AA world
`date`
\$AA
single quotes 사용시 '
문자를 입력하려면 다음과 같은 방법을 사용할 수 있습니다.
# single quotes 을 분리한후 no quotes 상태에서 ' 를 escape
$ echo 'foo'\''bar'
foo'bar
# 또는 ' 문자를 double quotes 으로 감싸면 됩니다.
$ 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
. . . .
. . . .
# 위 명령은 실제 다음과 같이 간단히 할 수 있습니다.
$ du -h -d1 ~/.cache | sort -hr
위와 동일한 역할을 하는 명령을 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
# 정상적으로 실행되려면 $ 문자를 \$ 로 escape 해서 명령 치환을 방지해야 합니다.
$ find ~/.cache -maxdepth 1 -exec \
sh -c "if test -d \"{}\"; then echo \"\$( du -hs \"{}\" )\"; fi" \; | sort -hr
OK
# 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 -e "aaa\nbbb" # single, double quotes 에서는 echo -e 옵션을
aaa # 사용해 출력할때 '\n' escape 문자가 처리된다.
bbb
$ foo="aaa\nbbb" # 따라서 대입 연산에서 사용될 경우는
$ echo "$foo" # '\n' 가 문자 그대로 저장되어 출력된다.
aaa\nbbb
$ foo=$'aaa\nbbb' # $' ' quotes 을 사용해 대입하면
$ echo "$foo" # '\n' escape 문자가 처리되어 저장된다.
aaa
bbb
# echo -e 옵션을 사용하지 않아도 \n, \t, \' escape 문자가 처리되어 출력된다.
$ echo $'I like\n\'winter\'\tand\t\'snow\''
I like
'winter' and 'snow'
------------------------------------------
$ IFS=$'\n' # IFS 변수값을 newline 으로 설정.
$ IFS=$' \t\n' # IFS 변수값을 space, tab, newline 으로 설정.
$" ... "
이것은 double qoutes 과 기능은 동일한데 메시지 localization 을 할때 사용됩니다.
printf %q
printf 명령의 %q
지정자는 명령에 사용되는 인수나 또는 명령문 전체를
다른 명령으로 올바르게 전달할 수 있게 escape 해줍니다.
# 공백문자, glob 문자, quotes 등을 모두 escape 해줍니다.
$ arg=$( printf "%q " "$( echo -e "foo? *bar[3]" )" )
$ echo $arg
foo\?\ \*bar\[3\]
# 입력되는 스트링에 \t, \n 같은 문자가 포함되면 $arg 값은 $' ' 형태가 됩니다.
$ arg=$( printf "%q " "$( echo -e "foo bar\n*zoo[3]" )" )
$ echo $arg
$'foo bar\n*zoo[3]' # $' ' 형태가 된다.
$ echo echo $arg
echo $'foo bar\n*zoo[3]'
$ eval echo $arg
foo bar
*zoo[3]
Quotes 을 분리해 작성할 경우
Quotes 을 분리해 작성하게 되면 중간의 공백이 하나의 space 로 변경됩니다.
$ echo "ERROR: backup disk drive ( hdd usb )" "must specified as the first"
argument"ERROR: backup disk drive ( hdd usb ) must specified as the first argument
$ echo "ERROR: backup disk drive ( hdd usb )" \
"must specified as the first argument"
ERROR: backup disk drive ( hdd usb ) must specified as the first argument
Quotes 을 서로 붙여 사용하기
두개의 quotes 을 공백을 두지 않고 서로 붙이면 하나의 인수가 됩니다. 이 원리는 변수를 포함하는 명령 스트링을 만들거나 명령에 전달할 인수를 하나로 만들때 유용하게 사용할 수 있습니다.
명령 스트링을 만들 때
' '
을 사용해 명령문을 작성하였는데 그 안에 변수를 사용할 일이 생기면 다음과 같이
' '
를 분리한 후 double quote 한 변수를 공백없이 붙여 사용하면 됩니다.
변수를 quote 하지 않으면 만약에 변수값에 공백이 포함될 경우 인수가 두개로 분리될 수 있습니다.
# sed 명령에 's/foo/bar/g' 는 하나의 인수로 전달된다.
sed -E 's/foo/bar/g'
# single quotes 을 분리한 후 변수를 double quotes 하여 공백 없이 붙인다.
sed -E '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 "{}" | 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
Perl 언어 에서의 quote 연산자
이와 같이 quotes 을 사용하는 방법이 복잡해 보일 수 있기 때문에 perl 언어에서는 별도로 quote 관련 연산자를 두고 있습니다. ( 그렇다고 이렇게 quotes 을 활용하는 기술이 없어지진 않겠죠. 이것도 하나의 활용 방법이기 때문에 )
$ perl -l - <<\@
$foo = 100;
$bar = qq(it is "worth" $foo); # "qq" 는 double quote 에 해당
print $bar; # $bar = "it is \"worth\" $foo" 와 동일
@
it is "worth" 100
$ perl -l - <<\@
$foo = 100;
$bar = q(it is 'worth' $foo); # "q" 는 single quote 에 해당
print $bar; # $bar = 'it is \'worth\' $foo' 와 동일
@
it is 'worth' $foo
$ perl -l - <<\@
$res = qx(date --date="2 year ago"); # "qx" 는 backtick 에 해당
print $res; # res=`date --date="2 year age"` 와 같은 형태
@
Sun Nov 15 14:27:26 KST 2020
Quiz
디렉토리에서 작업을 하다 보니 ?
문자가 포함된 파일이 생성되었습니다.
rm
명령을 이용해 삭제하려고 해도 삭제가 되지 않는데요.
어떻게 하면 삭제할 수 있을까요?
ls
명령은 파일명에 nongraphic 문자가 존재할 경우 ?
로 표시하는데요.
다음과 같이 삭제할 수 있습니다.
$ ls
?? bar foo zoo
$ rm '??'
rm: cannot remove '??': No such file or directory
$ ls -b // 먼저 -b 옵션을 이용해 nongraphic 문자를 8 진수로 출력
\312\004 bar foo zoo
$ rm $'\312\004' // $' ' quotes 을 이용해 삭제합니다.
$ ls
bar foo zoo
2 .
gcc 를 이용해 컴파일을 할때 -D
옵션을 이용하면 명령 라인에서 매크로 값을 설정할 수 있습니다.
매크로 값으로 스트링을 전달하려면 어떻게 할까요?
명령 라인에서 사용된 quotes 은 shell 에의해 해석된 후에 최종적으로 명령이 실행될 때는
제거됩니다.
따라서 다음 gcc 명령의 경우 MESSAGE 값은 "hello"
가 아니라 quotes 이 제거된 hello
가 되므로 오류가 발생하게 됩니다.
$ cat test.c
#include <stdio.h>
int main(void){
char *str = "DEBUG: *** " MESSAGE " ***";
puts(str);
}
# char *str = "DEBUG: *** " hello " ***"; 가 되므로 오류가 된다.
$ gcc -D MESSAGE="hello" test.c
...: error: ‘hello’ undeclared (first use in this function); did you mean ‘ftello’?
다음과 같이해야 매크로 값으로 double quotes 을 함께 전달할 수 있습니다.
# char *str = "DEBUG: *** " "hello" " ***"; 가 되므로 OK
$ gcc -D MESSAGE='"hello"' test.c
$ gcc -D MESSAGE=\"hello\" test.c
# char *str = "DEBUG: *** " "hello quotes" " ***"; 가 되므로 OK
$ gcc -D MESSAGE='"hello quotes"' test.c
$ gcc -D MESSAGE="\"hello quotes\"" test.c
$ ./a.out
DEBUG: *** hello quotes ***