Variables

변수 이름은 알파벳 (대, 소문자), 숫자, _ 로 만들 수 있으며 이름의 첫 문자로 숫자가 올 수 없습니다. 변수이름을 대문자로 사용할때는 shell 환경변수와 중복되지 않도록 주의해야 합니다. 생성한 변수는 subshell 이나 source 한 스크립트 내에서는 별다른 설정 없이 사용할 수 있으나 child process 에서도 사용하려면 export 해야 합니다.

# '.' or '-' 문자는 변수 이름을 구성하는데 사용되지 않으므로 다음과 같이 사용할 수 있습니다.
# 결과적으로 FILE_PREFIX 변수값은 'myfile.split' or 'myfile-split' 이 됩니다.

ORIGINAL_FILE=myfile

FILE_PREFIX=$ORIGINAL_FILE.split        # '.' 사용
FILE_PREFIX=$ORIGINAL_FILE-split        # '-' 사용

.............................................

$ num=3
$ echo 1234567890 | cut -c $num-        # 3- 과 같다
34567890

현재 shell 에 정의된 모든 변수명 보기

$ compgen -A variable

# 현재 shell 에 특정 변수가 설정되어 있는지 간단히 확인하는 방법
# 가령 대문자 B 로 시작하는 변수를 조회하려면
$ $B[tab]

정의된 변수 삭제하기

$ unset -v 변수명

# unset 명령 실행시 옵션을 주지 않으면 첫번째로 변수를 unset 시도하고 실패할 경우
# 함수를 unset 시도합니다. 따라서 항상 -v 옵션을 사용하는 것이 좋습니다.
$ unset 변수명

변수는 동일한 라인에 여러개를 설정할 수 있습니다.

변수를 설정할때는 ; 없이 동일한 라인에 여러개를 설정할 수 있습니다. 이때는 앞에서부터 차례로 적용됩니다.

foo=100 bar=$foo zoo=$bar                    foo=100; bar=$foo; zoo=$bar

echo $foo $bar $zoo                          echo $foo $bar $zoo
------------------------                     ------------------------

100 100 100                                  100 100 100

하지만 local, declare, export 명령을 이용하는 경우는 적용되지 않습니다.

func() {
    local foo=X100 bar=Y$foo zoo=Z$bar       # local 명령에 의해 아직 foo 변수가
    echo $foo $bar $zoo                      # 설정이 완료된 상태가 아니기 때문에
}                                            # bar, zoo 변수는 설정되지 않는다.

$ func
X100 Y Z

&& , || 를 이용한 변수값 설정

Command line 에는 기본적으로 명령문 외에 대입 연산이 올 수도 있습니다.

  1. 명령문
  2. 대입연산
  3. 대입연산 + 명령문 ( 명령에 선행하는 대입연산 )

대입 연산은 명령에 선행해서 정의할 수 있어야 하므로 ; 없이 여러 개를 정의할 수 있습니다. | 파이프와 달리 && , || 연산자로 연결된 명령 라인은 현재 shell 에서 실행되므로 현재 shell 의 변수값을 설정, 변경하는데 사용할 수 있습니다.

# 명령이 성공하면 myvar 변수값을 100 으로 설정
$ command ... && myvar=100

# 명령이 실패하면 myvar 변수값을 100 으로 설정
$ command ... || myvar=100

# 명령이 성공하면 AA=100 BB=200 으로 설정, 실패하면 AA=300 BB=400 으로 설정
$ command ... && AA=100 BB=200 || AA=300 BB=400

$ echo $AA $BB
100 200
-----------------------------------------------------

# '|' 파이프로 연결된 명령 라인은 subshell 에서 실행되므로 
# 설정한 변수값을 이후의 명령에서 사용할 수 없습니다.
$ command ... | myvar=100

$ echo $myvar
$                # 값이 표시되지 않는다.

명령과 동일한 이름을 변수로 사용할 수 있습니다.

외부 명령, builtin 명령, 키워드, 함수, alias 로 사용되는 이름과 변수는 다른 namespace 를 사용하므로 동일한 이름을 변수로 사용할 수 있습니다.

$ for=100 while=200 select=300       # 키워드와 동일한 이름
$ echo $for $while $select
100 200 300
$ set=100 local=200 echo=300         # builtin 명령과 동일한 이름
$ echo $set $local $echo
100 200 300
$ ls=100 date=200 mkdir=300          # 외부명령과 동일한 이름
$ echo $ls $date $mkdir
100 200 300

Child 프로세스에서도 변수를 사용할 수 있으려면 export 해야 합니다.

설정한 변수값은 subshell 프로세스에서는 그대로 사용할 수 있지만 스크립트 파일을 실행할 때와 같이 exec 에 의해 생성되는 프로세스에서는 사용할 수 없습니다. 이때는 변수를 export 해야 사용할 수 있습니다.

$ AA=100                                        $ export AA=100

$ ( echo "variable AA : $AA" )                  $ ( echo "variable AA : $AA" )
variable AA : 100                               variable AA : 100

$ bash -c 'echo "variable AA : $AA"'            $ bash -c 'echo "variable AA : $AA"'
variable AA :                                   variable AA : 100

$ cat test.sh                                   $ cat test.sh
#!/bin/bash                                     #!/bin/bash

echo "variable AA : $AA"                        echo "variable AA : $AA"

$ ./test.sh                                     $ ./test.sh 
variable AA :                                   variable AA : 100

변수를 export 하고, 취소하고, 확인하는 방법

# 변수 export 하기
$ export 변수명
$ declare -x 변수명

# export 취소 하기
$ export -n 변수명

# 변수가 export 되어 있는지 확인하기
$ declare -p LANG
declare -x LANG="C.UTF-8"        # 출력값에 'x' 가 있으면 export 된 변수다.

$ declare -p AA
declare -- AA="100"              # 'x' 가 없으면 export 된 변수가 아니다.

$ export -p                      # 현재 export 된 모든 변수를 출력합니다.

Variable states

Shell 에서는 변수의 상태를 3 가지로 구분할 수 있습니다.

1 . 변수가 존재하지 않는 상태 또는 unset 상태

변수가 존재하지 않는 상태란 변수에 값을 ( null 값 포함 ) 한번도 대입한 적이 없는 상태를 말합니다. 그러므로 값을 할당하지 않고 declare AA or local AA 와 같이 선언만 한 경우도 해당됩니다. 이미 사용했던 변수도 unset 명령을 사용하면 이 상태로 됩니다.

2 . null 값인 상태

다음과 같은 경우 변수가 존재하고 null 값을 갖고있는 상태가 됩니다.

AA=  AA=""  AA=''
3 . null 이외의 값을 가지고 있는상태

다음과 같은 경우 변수가 존재하고 무엇이든 값을 가지고 있는 상태입니다.

AA=" "  AA="hello"  AA=123

값을 구분하기

3 번 무엇이든 값을 가지고 있는 상태와 그렇지 않은 상태 ( 1, 2 번 ) 은 다음과 같이 구분할 수 있습니다.

# $var 가 1, 2 번 상태일 때 참
if test -z "$var"; then ...  

# $var 가 3 번 상태일 때 참
if test -n "$var"; then ...

변수가 존재하지 않는 1 번 unset 상태와 2 번 null 값 상태는 다음 명령으로 구분할 수 있습니다.
unset 상태일 경우는 1 을 그외는 0 을 리턴합니다.

$ test -v asdfgh; echo $?    # 현재 asdfgh 변수는 존재하지 않는 상태 (1 번 상태)
1 

$ asdfgh=""                  # 변수는 존재하나 null 값인 상태 (2 번 상태)
$ test -v asdfgh; echo $?
0

$ asdfgh=123                 # 3 번 상태
$ test -v asdfgh; echo $?
0

$ unset -v asdfgh            # unset 했으므로 다시 1 번 상태가 됩니다.
$ test -v asdfgh; echo $?
1

1 번 상태와 2 번 상태를 따로 구분하는 것은 필요 없을 것 같지만 변수가 가지고 있는 값에 상관없이 변수의 존재 여부를 판단에 사용하는 경우가 있습니다. 예를 들어 foo 함수에서 bar 변수를 경우에 따라서 설정할 수도 있고 아닐 수도 있는데 값을 설정할 경우 empty 스트링도 될 수 있다면 이경우 -z or -n 을 이용해서는 정확히 bar 변수의 설정 여부를 알 수 없겠죠.

# LD_TRACE_LOADED_OBJECTS 변수에 특정 값을 대입하지 않아도 된다.
$ LD_TRACE_LOADED_OBJECTS= /bin/date
        linux-vdso.so.1 (0x00007ffff63fb000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f393dcbd000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f393dece000)

sh 에서는 -v 옵션을 사용할 수 없는데요. 하지만 매개변수 확장 기능을 활용하면 쉽게 체크할 수 있습니다. 또는 set -u ( nounset ) 옵션을 활용하여 테스트 할수도 있는데 이 옵션을 설정하면 1 번 존재하지 않는 변수 사용시 오류를 반환하고 스크립트가 종료됩니다.

테스트를 위해 subshell 를 사용함으로써 종료시 subshell 만 종료되게 할 수 있고 옵션 설정 또한 subshell 에 국한할 수 있습니다.

$ ( set -u; echo 111 $asdfgh; echo end... )     # 현재 $asdfgh 변수는 존재하지 않는 상태
bash: asdfgh: unbound variable

$ echo $?           # 오류를 반환하고 종료하므로 마지막 echo end... 는 실행되지 않는다.
1
............................................

$ asdfgh=""         # 2 번 상태는 정상적으로 실행
$ ( set -u; echo 111 $asdfgh; echo end... )
111
end...

따라서 다음과 같이 : 명령의 인수로 변수를 사용함으로써 존재 여부를 테스트할 수 있습니다.

sh$ ( set -u; : $asdfgh ) 2> /dev/null; echo $?  # 현재 $asdfgh 변수는 존재하지 않는 상태
2

sh$ asdfgh=""
sh$ ( set -u; : $asdfgh ) 2> /dev/null; echo $?
0

sh$ asdfgh="123"
sh$ ( set -u; : $asdfgh ) 2> /dev/null; echo $?
0

sh$ unset -v asdfgh
sh$ ( set -u; : $asdfgh ) 2> /dev/null; echo $?
2

대입 연산시 변수 이름에 변수를 사용

대입 연산은 shell keyword 와 같이 변수 확장 전에 처리되므로 기본적으로 변수 이름에는 변수를 사용할 수 없습니다. 하지만 eval, let, declare, local, export 와 함께 사용될 경우는 가능합니다.

이것은 sh 에서도 동일합니다. ( sh 에서는 declare, let 은 사용할 수 없습니다. )

# AA$i : 변수 이름에 변수를 사용하면 오류가 발생한다.
$ for (( i=1; i<=5; i++ )) do AA$i=$(($i * 100)); done
AA1=100: command not found
AA2=200: command not found
. . . .

# declare, local, export, eval, let 과 함께 사용할 경우는 가능.
$ for (( i=1; i<=5; i++ )) do declare AA$i=$(($i * 100)); done  # declare 사용
$ echo $AA1
100
$ echo $AA2
200

$ i=3
$ export BB$i=300     # export 사용
$ echo $BB3
300

$ i=4
$ eval CC$i=400       # eval 사용
$ echo $CC4
400

$ i=5
$ let DD$i=500        # let 사용
$ echo $DD5
500

$ f1() {
    i=6
    local EE$i=600    # local 사용
    echo $EE6
}

$ f1
600

변수값으로 NUL 문자를 저장할 수 없다.

NUL 문자( \0 )는 변수의 값으로 저장할 수 없습니다. 하지만 pipe 로 전달하거나 file 로 저장할 수는 있습니다.

$ var=$(echo -en "AA\x09\x0aBB")    # \x09 는 tab, \x0a 는 newline
$ echo -n "$var" | od -a
0000000   A   A  ht  nl   B   B     # tab, newline 같은 문자들은 정상적으로 저장된다.
0000006

$ var=$(echo -en "AA\x00\x00BB")    # \x00 대신 \0 로 해도됨
$ echo -n "$var" | od -a
0000000   A   A   B   B             # NUL 문자는 모두 제거되었다.
0000004

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

# enter 후에 마우스를 움직이면 NUL 문자값(00) 이 pipe 를 통해 전달되는 것을 볼 수 있습니다.
$ sudo cat /dev/input/mice | od -tx1
0000000 18 fe 09 18 fd 07 18 fc 09 18 fe 08 18 fe 06 18
0000020 ff 05 18 ff 04 08 00 03 08 00 03 08 00 02 08 00
0000040 02 08 01 01 08 01 01 08 01 01 08 01 00 08 01 00
0000060 28 01 ff 28 01 ff 28 00 fe 28 00 ff 28 00 fe 28
....

NUL 문자는 ascii table 상에 \x00 값을 가지고 있는 A (\x41), B (\x42) 와 같은 값입니다. 반면에 본문에 사용된 null 값은 empty 값을 의미합니다.

Subshell 과 스크립트 파일 에서는 현재 shell 의 변수값을 변경할 수 없다.

Subshell 과 스크립트 파일은 현재 shell 과는 다른 프로세스에서 실행되므로 현재 shell 의 변수값을 변경할 수 없습니다.

$ AA=100

$ echo $AA                    # 현재 shell 의 변수값
100

$ echo 111 | export AA=200    # pipe 로 연결된 명령들은 subshell 에서 실행된다.

$ echo $AA                    # 따라서 현재 shell 의 변수값을 변경할 수 없다.
100

$ ( export AA=200 )           # ( ... ) subshell

$ echo $AA                    # 변경할 수 없다.
100
......................................................

$ cat test.sh             # 스크립트 파일
#!/bin/bash

export AA=200

$ ./test.sh               # 스크립트 파일도 별도의 프로세스에서 실행되므로

$ echo $AA                # 현재 shell 의 변수값을 변경할 수 없습니다.
100

이후부터는 Bash 전용

사용자에게 변수 이름을 입력받고 해당 변수값을 출력하려면 어떻게 할까요?
이때는 다음과 같이 indirection 을 사용할 수 있습니다.

$ foobar=100

$ read varname
foobar                 # 사용자가 foobar 입력

$ echo $varname
foobar

$ echo ${$varname}     # ${$varname} -> ${foobar} -> 100  가 되지 않는다.
bash: ${$varname}: bad substitution

$ echo ${!varname}     # '!' 문자를 사용해야 합니다. 
100
---------------------------------------------------

$ hello=200
$ linux=hello

$ echo ${$linux}              # 오류
bash: ${$linux}: bad substitution

$ echo ${!linux}              # !linux -> $linux -> hello
200
--------------------------------------------------

$ arr=( 11 22 33 44 )
$ AA=arr[2]
$ echo "${!AA}"               # !AA -> $AA -> arr[2]
33

$ name="arr" index=2
$ ref="$name[$index]"
$ echo "${!ref}"              # !ref -> $ref -> arr[2]
33
--------------------------------------------------

#!/bin/bash

func() {
    echo "$1"
    for v in "${!2}"; do      # !2 -> $2 -> AA[@]
        echo "$v"
    done
    echo "$3"
}

AA=(22 "33 44" 55)
func 11 'AA[@]' 66

################ output ###############
11
22
33 44
55
66

Bash version 4.3+ 부터는 named reference 를 사용할 수 있습니다. 원본 변수의 reference 이므로 값을 변경하게 되면 원본 값이 변경됩니다. 변수 이름을 가지고 하는 것이기 때문에 recursion 에서처럼 동일한 이름이 반복될 경우에는 사용할 수 없습니다.

declare -n refname
local -n refname

#!/bin/bash

f2() {
    declare -n RVAR=$1    # declare 는 함수내에서 사용되면 local 과 같습니다.
    RVAR=200
}

f1() {
    local VAR=100
    echo f1 : local VAR = $VAR
    f2 VAR
    echo f1 : local VAR after f2 call = $VAR
}

f1

############# output ##############
f1 : local VAR = 100
f1 : local VAR after f2 call = 200

-------------------------------------------------------------
#!/bin/bash 

func() {
    local -n ARR=$1
    ARR[1]=100
}

AA=(11 22 33 44)
func AA

for v in "${AA[@]}"; do
    echo "$v"
done

############# output ##############
11
100     # 22 가 100 으로 변경됨
33
44

--------------------------------------------
#!/bin/bash 

declare -A weapons=(
    ['Straight Sword']=75
    ['Tainted Dagger']=54
    ['Imperial Sword']=90
    ['Edged Shuriken']=25
)

print_weapons() {                      # 여기서 ${!array[@]} 는 indirection 이 아니고
    local -n array=$1                  # array 에서 사용되는 특수 표현식으로
    for i in "${!array[@]}"; do        # 전체 원소의 index 값을 반환합니다.
        printf "%s\t%d\n" "$i" "${array[$i]}"
    done
}

print_weapons weapons

############# output ##############
Imperial Sword  90
Tainted Dagger  54
Edged Shuriken  25
Straight Sword  75

Quiz

/etc/os-release 또는 /etc/lsb-release 파일에 설정되어 있는 값을 쉘 스크립트 에서 사용하고 싶은데 어떻게 하면 될까요?

위의 파일 내용을 출력해 보면 쉘에서 변수를 설정할 때와 동일한 포맷을 사용하고 있습니다. 따라서 . 또는 source (bash) builtin 명령을 이용하면 쉽게 해당 변수값을 스크립트에서 사용할 수 있습니다.

$ bash <<\@
. /etc/os-release          # 또는 source /etc/os-release
echo $ID
echo $VERSION_CODENAME
@

ubuntu
kinetic

2 .

export 한 변수는 child process 가 실행될 때 자동으로 전달되는데요. 반대로 특정 명령 실행 시에는 export 한 변수가 전달되지 않게 하려면 어떻게 할까요?

다음과 같이 env 명령을 사용하면 됩니다.

$ export FOO=100 BAR=200

# export 한 변수는 자동으로 child process 에 전달된다.
$ sh -c 'echo $FOO $BAR'
100 200

$ env -u FOO sh -c 'echo $FOO $BAR'    # FOO 변수만 unset 하여 실행
200

$ env BAR=300 sh -c 'echo $FOO $BAR'   # BAR 변수값을 300 으로 설정해 실행
100 300

# '-i' 옵션은 모든 환경변수를 empty 로 만듭니다.
$ env -i sh -c 'echo $FOO $BAR'
$

# 만약에 명령 실행시 working directory 를 변경하고 싶으면 -C 옵션을 사용.
$ env -C Documents -u FOO sh -c 'pwd; echo $FOO $BAR'
/home/mug896/Documents
200

3 .

다음과 같이 대입 연산을 하게 되면 변수와 값의 구분은 어떻게 될까요 ?

$ AA=BB=CC

$ echo $AA      # 변수 AA 의 값은 BB=CC 가 된다.
BB=CC
$ echo $BB
$
$ echo $CC
$

4 .

변수를 나타내는 문자로 $ 가 사용되는 이유는 ?

변수를 나타내는 문자로 $ (dollar) 를 사용하는 이유는 옛날에는 지금과 달리 메모리 가격이 굉장히 비쌌다고 합니다. 따라서 변수를 사용하는 것은 곧 메모리를 사용하는 것이고 돈과 직결되므로 $ 문자가 적합하겠죠?