shell에서 작은따옴표('), 큰따옴표(")의 차이

Reading time ~4 minutes

작은따옴표(‘), 큰따옴표(“)?

$ str=`world`
$ echo "hello $str"
hello world
$ echo 'hello $str'
hello $str
  • 작은따옴표로 감싸진 문자열은 변화 없이 그대로 출력
  • 큰따옴표 안에 넣으면 변수가 실제 값으로 치환된 후 출력
$ echo "\""
"  
$ echo '"'
"  
echo "\\"
\
$ echo '\'
\

“나 \같은 특정 문자를 출력하려면 큰따옴표 같은 경우는 \를 사용하면 출력 가능합니다.

$ echo "pwd : `pwd`"
pwd : /home/storycompiler
$ echo 'pwd : `pwd`'
pwd : `pwd`

좀 더 복잡한 경우

#!/usr/bin/env bash

VERSION=$1
EC2_URL=$2

ssh ec2-user@${EC2_URL} "kill -9 `ps -ef | grep java | awk '{print $2}'`"

젠킨스에서 배포 시 기존 자바 프로세스를 죽이는 스크립트를 이렇게 짰는데 의도대로 기존 프로세슷 잡아 종료시키지 못했습니다… 무엇이 문제였을까요??

아래를 보도록 하죠.

$ echo "kill -9 `ps -ef | grep java | awk '{print $2}'`"
kill -9 8456

$ echo "kill -9 \`ps -ef | grep java | awk '{print $2}'\`"
kill -9 `ps -ef | grep java | awk '{print }'`

$ echo "kill -9 \`ps -ef | grep java | awk '{print \$2}'\`"
kill -9 `ps -ef | grep java | awk '{print $2}'`

(참고로 8456은 grep java의 PID)

첫 번째 경우

한가지 기억해 둬야 할 것이 있습니다. 해당 스크립트가 실행되는 장소는 젠킨스 서버입니다.
즉, 변수가 실제 값으로 치환되는 시점이 젠킨스 서버에서 이루어집니다.

그 소리는

`ps -ef | grep java | awk '{print $2}'`

찾아지는 java 프로세스의 PID는 배포를 할 서버가 아닌 젠킨스 서버 내의 자바 프로세스입니다.

제가 작성했던 쉘 스크립트에서는 아래와 같이 실행됩니다.

$ ssh ec2-user@${EC2_URL} "kill -9 `ps -ef | grep java | awk '{print $2}'`"
$ ssh ec2-user@${EC2_URL} kill -9 8456

(첫 줄은 작성한 명령어, 두 번째 줄은 실제 실행될 때의 형태)

8456이라는 PID가 배포할 서버에서 종료하고자 했던 java 프로세스라면 아무 문제 없었겠죠?
하지만 앞서 말씀드린 것처럼 해당 PID는 젠킨스 서버서 실행시켰던 grep java에 대한 PID입니다.

여기서 발생할 수 있는 문제가 몇 가지 있는데 가장 큰 문제는 저 명령어가 실행될 서버에 PID가 8456이라는 프로세스가 있을 때입니다. SIGKILL에 의해 묻지도 따지지도 않고 종료가 될 테고…. 그 이후 어떻게 될지는 어떤 프로세스였는지에 따라 최악의 상황으로도 이어질 수 있습니다.

실제 저 같은 경우 처음에 위와 같이 쉡 스크립트를 작성 한 다음에 스스로 흡족해한 다음 자꾸 의도치 않게 서버가 죽어버리거나 아무 일도 안 일어나는 현상을 겪고 멘붕을 했습니다.

로빈
(전 java 프로세스를 죽이고 싶었습니다.)

두번째 경우

$ echo "kill -9 \`ps -ef | grep java | awk '{print $2}'\`"
kill -9 `ps -ef | grep java | awk '{print }'`  

$ ssh ec2-user@${EC2_URL} "kill -9 \`ps -ef | grep java | awk '{print $2}'\`"
$ ssh ec2-user@${EC2_URL} kill -9 `ps -ef | grep java | awk '{print }'`

위에 상황보다는 조금 나아 보이나요?
그럴 수도 있지만 전혀 아닙니다.

$ kill -9 `ps -ef | grep java | awk '{print }'`
-bash: kill: (501) - No such process
-bash: kill: (8553) - No such process
-bash: kill: (8551) - No such process

[프로세스 완료됨]

맥의 터미널에서 해당 명령어를 실행시켰을 때의 결과입니다. 왜 이런 결과가 나왔을까요?

$ ps -ef | grep java | awk '{print }'
501  8497  8442   0  6:40PM ttys000    0:00.00 grep java

501 : UID
8497 : PID
8442 : PPID

kill 명령어 동작 방식에 의해 순차적으로 아래 형태로 실행됩니다.

$ kill -9 501
$ kill -9 8497
$ kill -9 8442

게다가 현재 상황에선 PPID(8442)의 주인은 쉘이기 때문에 바로 프로세스가 종료됩니다. 당연하게도 해당 UID, PID, PPID 값들은 모두 젠킨스 서버 내의 값들입니다.

이런 상황도 의도하지 않은 상황이겠지만 여기서 한가지 더 놓치면 안 되는 점이 있습니다.

#!/usr/bin/env bash

VERSION=$1
EC2_URL=$2

ssh ec2-user@${EC2_URL} "kill -9 \`ps -ef | grep java | awk '{print $2}\'`"  

위에 제가 작성했다는 쉘 스크립트입니다. $2를 사용하고 있죠.

즉, ps -ef | grep java | awk '{print }'처럼 print 뒤에 빈값이 오는 게 아니라 ps -ef | grep java | awk '{print 무언가}' 두 번째 파라미터 인수로 들어오는 무언가가 해당 위치에 들어가기 때문에 경우에 따라서는 더욱 최악의 상황이 될 수도 있습니다.

세 번째 경우

자 마지막 상황을 보죠. 마지막인 이유가 있겠죠?

$ echo "kill -9 \`ps -ef | grep java | awk '{print \$2}'\`"
kill -9 `ps -ef | grep java | awk '{print $2}'`

오! 뭔가 저희가 원하는 형태가 보입니다.

$ ssh ec2-user@${EC2_URL} "kill -9 `ps -ef | grep java | awk '{print $2}'`"  
$ ssh ec2-user@${EC2_URL} kill -9 `ps -ef | grep java | awk '{print $2}'`

첫 번째와 같이 작성하면 실제 실행은 아래와 되어 젠킨스 서버 내에서 ssh를 통해 배포 서버에서

$ kill -9 `ps -ef | grep java | awk '{print $2}'`

를 실행시킵니다.

알고 나면 쉽고 당연하지만 모르는 상황이면 원이 파악부터 쉽지 않은 사항이니 당장 활용은 안 하신다 해도 머릿속에 넣어놓고 다니시길 바랍니다!!! 저 같이 쉘 스크립트도 및 젠킨스도 익숙지 않은 상황이라면 무엇이 잘못된 건지 알지도 못한 상황에서 며칠을 고생합니다…

참고로 저는

$ ssh ec2-user@${EC2_URL} 'kill -9 `pgrep java`'

이와 같이 변경했습니다.

개구리

(참고로 위 명령어도 필요에 따라 작음 따옴표, 큰따옴표를 이용하셔야 합니다!!!)


결론

  • 작은따옴표로 감싸진 문자열은 변화 없이 그대로 출력
  • 큰따옴표 안에 넣으면 변수가 실제 값으로 치환된 후 출력

작은따옴표(‘)를 쓰거나 큰따옴표(“)를 쓰실 때는 변수를 치환할 필요가 있는지, ssh 등 특정 상황에서는 어느 곳에서 변수를 치환해야 할지까지 고려해서 사용을 하셔야 합니다.

출처: 스프링연구소(spring-lab)
출처: 아프니까 개발자다