들어가며
쉘 스크립트에서 변수에 값을 할당하고, 변수에 할당된 값을 어떻게 참조하는지 살펴보겠습니다.
변수에 값 할당
(1) 일반적인 형태
VAR1=1004
VAR2=hello
VAR3="hello"
VAR4='hello'
# 큰따옴표/따옴표를 사용하는 습관을 들이자
VAR5=hello world # NO
VAR6="hello world" # OK
일반적으로 변수에 값을 할당하는 문법은 변수명=값 으로 통일되어 간단합니다. 단, 주의할 점은 다음과 같습니다.
- 쉘 스크립트는 기본적으로 값의 타입이 없고(untyped) 모두 문자열로 인식됩니다. 위 예시에서 var1에 할당된 1004도, var2에 할당된 hello도 모두 문자열입니다.
- 반드시 할당 연산자 좌/우에 공백이 없어야 합니다. 대부분의 언어에서 가독성을 확보하기 위해 할당 연산자 좌/우에 공백을 넣기 마련인데, 쉘 스크립트에서는 그렇게 하지 않습니다.
- 공백이 들어간 문자열이든 그렇지 않든, 문자열 값을 큰따옴표/따옴표로 감싸주는 습관을 들이는 것이 이상적입니다. 큰따옴표/따옴표를 사용하여 문자열을 감싸주든, 아무것도 사용하지 않든 똑같은 문자열입니다(var2~var4 참고). 그러나 큰따옴표/따옴표를 사용하지 않고 2개 이상의 단어가 들어가게 될 경우, 값이 정상적으로 할당되지 않는 문제가 발생합니다.
(2) 명령어의 결과를 할당하는 형태
RESULT=$(ls -la)
# legacy syntax
RESULT=`ls -la`
특정 명령어의 결과를 변수에 할당해야 할 일이 꽤 빈번히 일어납니다. 그 경우, 위 result1에 할당한 방식처럼 $() 으로 명령어를 감싸주시면 되겠습니다. 참고로 result2에 할당한 방식처럼 백틱(`)을 활용하는 경우가 빈번한데, 해당 문법은 legacy syntax로 문제를 일으킬 수 있으니 지양해주세요. [참고 문서]
RESULT="파일 개수: $(ls -l | grep ^- -c)"
# legacy syntax
RESULT="파일 개수: `ls -l | grep ^- -c`"
추가적으로, 특정 문자열 내에 특정 명령어의 결과를 포함시키고 싶다면 위와 같이 하면 됩니다. 마찬가지로 백틱을 활용한 legacy syntax는 지양해주세요.
변수에 할당된 값 참조
NAME="Mong-Gu"
echo "${NAME}"
일반적으로 변수에 할당된 값을 참조하는 문법은"${변수명}" 입니다. 위 예시에서는 echo 명령어에서 특정 변수들에 담긴 값들을 참조하고 있는데요. 사실 변수에 할당된 값을 참조하는 문법은 미세하지만 차이가 있긴 한데, 저는 되도록 "${변수명}"을 사용하도록 권장드리고 싶습니다. 구체적인 이유는 하나하나 살펴보겠습니다.
(1) $변수명 대신 ${변수명} 을 권장
참고 문서
# 특정 상품의 제조사와 일련번호를 참조하여 "제조사_일련번호" 형태의 문자열로 만드는 예시
MANUFACTURER="SAMSUNG"
SERIAL_NUMBER="F12A159G"
# 좋지 못한 코드
ITEM="$MANUFACTURER_$SERIAL_NUMBER"
echo "${ITEM}" # F12A159G
# 좋은 코드
ITEM=${MANUFACTURER}_${SERIAL_NUMBER}
echo "${ITEM}" # SAMSUNG_F12A159G
사실 변수에 할당된 값을 참조할 때, { }로 변수명을 감싸주지 않고 $변수명 으로도 참조가 가능합니다. 변수를 하나만 참조할 때는 큰 문제가 안 되지만, 한 번에 두 개 이상의 변수를 참조해야 할 때 문제가 발생할 수 있습니다. 위 코드를 통해 어떠한 문제가 발생할 수 있는지 알아보겠습니다.
좋지 못한 코드에서는 { }로 변수명을 감싸주지 않은 채 값을 참조했습니다. 하지만 echo를 통해 ITEM에 담긴 값을 확인해보면 의도한 바와 다른 값이 저장되어 있습니다. 쉘 스크립트에서는 { }로 변수명을 감싸주지 않으면 다음 $가 나올 때까지가 변수명이라고 판단하여 변수 MANUFACTURER에 담긴 값을 참조한 게 아니라 MANUFACTURER_에 담긴 값을 참조하게 됩니다. 하지만 MANUFACTURER_라는 변수는 초기화된 적이 없어서 아무런 값을 참조하지 않게 되며, 그 결과 ITEM에는 SERIAL_NUMBER만 할당된 것을 확인할 수 있습니다.
반면, 좋은 코드에서는 { }로 변수명을 감싸준 채 값을 참조했습니다. 이때는 의도한 대로 ITEM에 값이 담겼습니다. { }로 변수명을 감싸주면 참조할 변수를 정확히 명시할 수 있습니다. ${변수명} 문법을 사용하면 가독성이 높아질 뿐만 아니라 참조하고자 하는 값이 담긴 변수를 정확히 지정할 수 있다는 장점이 있습니다.
누군가에게는 하나의 변수만을 참조할 때, $변수명 이 더 깔끔해 보일 수 있습니다. 하지만 저는 동일한 역할의 문법을 두 가지로 혼용하여 사용하고 싶지 않아서 ${변수명} 을 일관되게 사용하는 중입니다.
(2) ${변수명} 대신 "${변수명}" 을 권장합니다.
참고 문서
str1="hello"
str2="world"
str3="hello world"
str4="hello world"
# 좋지 못한 코드
echo ${str1} ${str2} # hello world
echo ${str1} ${str2} # hello world
echo ${str3} # hello world
echo ${str4} # hello world
echo '${str4}' # ${str4}
# 좋은 코드
echo "${str1} ${str2}" # hello world
echo "${str1} ${str2}" # hello world
echo "${str3}" # hello world
echo "${str4}" # hello world
사실 변수에 할당된 값을 참조할 때, 참조된 변수가 포함된 문구를 " "로 감싸주지 않고도 가능합니다. 해당 문구 내에 공백이 없다면 " "로 감싸주지 않아도 문제가 발생하지 않지만, 공백이 존재한다면 문제가 발생합니다. 위의 간단한 예시 코드를 통해 어떠한 문제가 발생하는지 살펴보겠습니다.
좋지 못한 코드 중 첫 번째 라인은 의도한 대로 hello와 world 사이에 공백이 하나 들어가게 됩니다. 하지만 두 번째 라인에서는 hello와 world 사이에 공백을 5개 삽입하고 싶었는데, 공백이 하나만 들어가게 됩니다. 심지어 공백을 포함한 문자열이 이미 할당되어 있고, 해당 값을 참조하려고 해도 동일한 문제가 발생합니다 (str3, str4 참고).
참조된 변수가 포함된 문구를 " "로 감싸주지 않으면 안에 있는 공백의 연속은 모두 하나의 공백으로 치환됩니다. 추가적으로, ' '로 감싸면 아예 값을 참조할 수 없게 되고 문자 그대로를 사용되게 됩니다.
좋은 코드에서는 변수에 할당된 값을 참조할 때 모두 " "로 감싸줬습니다. 그 결과, 의도한 대로 본래의 값 그대로를 사용할 수 있게 되었습니다.