Skip to main content

4. Variables - Part I

현존하는 거의 모든 프로그래밍 언어에는 값을 할당하고 그 내용을 읽고 조작할 수 있는 메모리 덩어리의 상징적인 이름인 변수라는 개념이 있습니다. 본 셸(Bourne shell)도 예외는 아니며, 이 섹션에서는 그 개념을 소개합니다. 환경에 의해 설정되는 변수를 살펴보는 변수 - 2부에서는 이에 대해 더 자세히 설명합니다.
첫 번째 Hello World 예제를 다시 살펴봅시다. 변수를 사용하여 이 작업을 수행할 수 있습니다(너무 간단한 예제라서 변수가 필요하지는 않지만!).
"=" 기호 주위에 공백이 없어야 한다는 점에 유의하세요: VAR=값은 작동하고, VAR = 값은 작동하지 않습니다. 첫 번째 경우 셸은 "=" 기호를 보고 명령을 변수 할당으로 처리합니다. 두 번째 경우 셸은 VAR이 명령의 이름이어야 한다고 가정하고 실행을 시도합니다.
첫 번째 인수가 "="이고 두 번째 인수가 "값"인 VAR 명령을 어떻게 실행하라고 지시할 수 있을까요?
var1.sh에 다음 코드를 입력합니다:

#!/bin/sh
MY_MESSAGE="Hello World"
echo $MY_MESSAGE

이렇게 하면 문자열 "Hello World"가 MY_MESSAGE 변수에 할당된 다음 변수 값이 에코됩니다.

Hello World 문자열 주위를 따옴표로 묶어야 한다는 점에 유의하세요. echo는 매개 변수를 얼마든지 사용할 수 있기 때문에 Hello World를 에코할 수 있지만, 변수는 하나의 값만 저장할 수 있으므로 공백이 있는 문자열은 셸이 모두 하나로 취급하도록 따옴표로 묶어야 합니다. 그렇지 않으면 셸은 MY_MESSAGE=Hello를 지정한 후 World 명령을 실행하려고 시도합니다.

셸은 변수의 유형을 신경 쓰지 않으며 문자열, 정수, 실수 등 원하는 모든 것을 저장할 수 있습니다.
Perl에 익숙한 사람이라면 이 점이 상당히 만족스러울 수 있지만, C나 파스칼, 심지어 Ada에 익숙하지 않은 사람이라면 이 점이 상당히 이상하게 느껴질 수 있습니다.
실제로는 모두 문자열로 저장되지만 숫자를 기대하는 루틴은 이를 숫자로 취급할 수 있습니다.
변수에 문자열을 할당하고 거기에 1을 더하려고 하면 실패합니다:

$ x="hello"
$ expr $x + 1
expr: non-numeric argument 
$

이는 외부 프로그램 expr이 숫자만 기대하기 때문입니다. 그러나 둘 사이에는 구문상의 차이가 없습니다:

MY_MESSAGE="Hello World"
MY_SHORT_MESSAGE=hi
MY_NUMBER=1
MY_PI=3.142
MY_OTHER_PI="3.142"
MY_MIXED=123abc

특수 문자는 셸에서 해석되지 않도록 적절하게 이스케이프 처리해야 합니다.
이에 대해서는 6장 "이스케이프 문자"에서 자세히 설명합니다.

다음 스크립트는 이름을 물어본 다음 직접 인사하는 방식으로 변수 이름을 대화형으로 설정할 수 있습니다:

#!/bin/sh
echo What is your name?
read MY_NAME
echo "Hello $MY_NAME - hope you're well."

마리오 바친스키가 친절하게도 제가 3행에서 큰따옴표를 놓쳐서 "you're"라는 단어의 작은따옴표가 일치하지 않아서 오류가 발생했다고 지적해 주었습니다. 셸 프로그래머를 미치게 만드는 이런 종류의 오류를 조심하세요!

이것은 표준 입력에서 제공된 변수로 한 줄을 읽는 셸 내장 명령 read를 사용하는 것입니다.
전체 이름을 입력하고 echo 명령 주위에 큰따옴표를 사용하지 않더라도 올바르게 출력된다는 점에 유의하세요. 어떻게 하나요? 앞서 MY_MESSAGE 변수를 설정하기 위해 변수 주위를 큰따옴표로 묶어야 했습니다.
이제 read 명령은 입력 주위에 자동으로 따옴표를 넣어 공백이 올바르게 처리되도록 합니다. 물론 출력도 따옴표로 묶어야 합니다(예: echo "$MY_MESSAGE").

변수의 범위

본 셸의 변수는 C와 같은 언어에서처럼 선언할 필요가 없습니다. 선언되지 않은 변수를 읽으려고 하면 결과는 빈 문자열입니다. 경고나
오류가 발생합니다. 이로 인해 몇 가지 미묘한 버그가 발생할 수 있습니다 - 만약 다음과 같이

MY_OBFUSCATED_VARIABLE=Hello

할당하고 

echo $MY_OSFUCATED_VARIABLE

그러면 아무것도 표시되지 않습니다(두 번째 OBFUSCATED의 철자가 틀렸기 때문입니다).

변수의 범위에 근본적인 영향을 미치는 export라는 명령이 있습니다. 변수에 어떤 일이 일어나는지 제대로 알기 위해서는 이 명령이 어떻게 사용되는지 이해해야 합니다.

작은 셸 스크립트인 myvar2.sh를 만듭니다:

#!/bin/sh
echo "MYVAR is: $MYVAR"
MYVAR="hi there"
echo "MYVAR is: $MYVAR"

이제 스크립트를 실행합니다:

$ ./myvar2.sh 
MYVAR is:
MYVAR is: hi there

MYVAR에 어떤 값도 설정되지 않았으므로 비어 있습니다. 그런 다음 값을 지정하면 예상한 결과가 나옵니다.
이제 실행합니다:

$ MYVAR=hello
$ ./myvar2.sh 
MYVAR is:
MYVAR is: hi there

아직 설정되지 않았습니다! 무슨 일이죠?
대화형 셸에서 myvar2.sh를 호출하면 스크립트를 실행하기 위한 새 셸이 생성됩니다. 이는 앞서 설명한 스크립트 시작 부분의 #!/bin/sh 줄 때문입니다.
셸 스크립트를 포함한 다른 프로그램에서 변수를 상속하려면 변수를 내보내야 합니다. 다음을 입력합니다:

$ export MYVAR
$ ./myvar2.sh 
MYVAR is: hello 
MYVAR is: hi there

이제 스크립트 3줄을 보세요. MYVAR의 값을 변경하고 있습니다. 하지만 이것이 대화형 셸에 다시 전달될 방법은 없습니다. MYVAR의 값을 읽어보세요:

$ echo $MYVAR 
hello
$

셸 스크립트가 종료되면 해당 환경은 파괴됩니다. 하지만 MYVAR은 대화형 셸 내에서 hello 값을 유지합니다.
스크립트에서 환경 변경 사항을 다시 받으려면 스크립트를 소싱해야 하는데, 이렇게 하면 스크립트를 실행하기 위해 다른 셸을 생성하는 대신 자체 대화형 셸 내에서 스크립트를 효과적으로 실행할 수 있습니다.
"."(점) 명령을 통해 스크립트를 소스화할 수 있습니다:

$ MYVAR=hello
$ echo $MYVAR 
hello
$ . ./myvar2.sh 
MYVAR is: hello 
MYVAR is: hi there 
$ echo $MYVAR
hi there

이제 변경 사항이 다시 셸에 적용되었습니다! 예를 들어 .profile 또는 .bash_profile 파일은 이렇게 작동합니다.
이 경우 MYVAR을 내보낼 필요가 없다는 점에 유의하세요.
위에서 echo MYVAR라고 했지, $MYVAR을 echo하라고 하지 않았다는 점을 지적해 주신 sway님께 감사드립니다. 셸 스크립트에서 저지르기 쉬운 실수의 또 다른 예입니다. 이 시점에서 변수에 대해 한 가지 더 언급할 가치가 있는 것은 다음 셸 스크립트를 고려하는 것입니다:

#!/bin/sh
echo "What is your name?"
read USER_NAME
echo "Hello $USER_NAME"
echo "I will create you a file called $USER_NAME_file" 
touch $USER_NAME_file

어떤 결과를 기대할 수 있는지 생각해 보세요. 예를 들어 USER_NAME에 "steve"를 입력하면 스크립트에서 steve_file을 만들어야 할까요?
사실, 아니죠. USER_NAME_file이라는 변수가 없으면 오류가 발생합니다. 셸은 변수가 어디에서 끝나고 나머지가 시작되는지 알지 못합니다. 이를 어떻게 정의할 수 있을까요? 답은 변수 자체를 중괄호({})로 묶는 것입니다:

#!/bin/sh
echo "What is your name?"
read USER_NAME
echo "Hello $USER_NAME"
echo "I will create you a file called ${USER_NAME}_file" 
touch "${USER_NAME}_file"

이제 셸은 우리가 USER_NAME 변수를 참조하고 있으며 이 변수에 "_file"이라는 접미사를 붙이기를 원한다는 것을 알고 있습니다. 문제의 원인을 추적하기 어려울 수 있기 때문에 많은 초보 셸 스크립트 프로그래머가 이런 오류를 범할 수 있습니다.

또한 "${USER_NAME}_file" 주위의 따옴표에 주목하세요. 사용자가 "Steve Parker"(공백에 유의)를 입력한 경우 따옴표가 없으면 touch에 전달되는 인수는 Steve와 Parker_file이 됩니다. 즉, 터치할 파일이 하나가 아니라 두 개인 Steve Parker_file을 터치하라는 뜻이 됩니다. 따옴표는 이를 방지합니다. 이 점을 강조해준 Chris에게 감사드립니다.