Bourne shell에서 Array 처리 방법

8 분 소요

모든 프로그래밍 언어와 쉘(bash, ksh 등)에서는 배열 변수를 지원한다. 그만큼 배열은 프로그래밍을 위한 가장 기초적인 요소이지만, 아쉽게도 본쉘은 배열을 지원하지 않는다. 예를 들어, arr[0]=25, echo ${arr[0]} 이런게 안된다는 뜻이다.

하지만 방법이 전혀 없는 것은 아니다. 위치 매개변수(Positional Parameter)에 답이 있다.

위치 매개변수

위치 매개변수는 특별한 것이 아니다. 대부분 여러분이 알고 있으며 흔하게 사용하고 있는 것이다. 주로 다음과 같이 사용자가 입력하는 인자를 받기 위해 사용한다.

#!/bin/sh

echo "cmd = $0"
echo "arg1 = $1"
echo "arg2 = $2"
echo "arg cnt = $#"
$ ./ex1.sh one two
cmd = ./ex1.sh
arg1 = one
arg2 = two
arg cnt = 2

대부분 위와 같은 용도로 사용하기 때문에 위치 매개변수는 값을 읽을 수만 있다고 오해하는 경우가 많다. 아주 단순하고 조작 범위가 제한적이지만, 위치 매개변수도 변경할 수 있는 몇가지 방법이 있다.

shift 명령

shift는 현재 위치 매개변수의 각 파라미터 위치를 하나씩 왼쪽으로 이동시키는 명령이다.

#!/bin/sh

echo "param cnt = $#"
echo "$1 $2 $3"

shift
echo "param cnt = $#"
echo "$1 $2"

shift
echo "param cnt = $#"
echo "$1"
$ ./ex2.sh A B C
param cnt = 3
A B C
param cnt = 2
B C
param cnt = 1
C

shift 명령 실행마다, 파라미터의 개수가 하나씩 줄어들고 파라미터의 위치도 왼쪽으로 하나씩 이동하는 것을 확인할 수 있다.

파라미터 개수를 나타내는 $#변수와 shift를 사용하면 반복문 안에서 파라미터를 하나씩 가져오는 것이 가능해진다.

#!/bin/sh

cnt=$#
i=1 
while [ "$i" -le "$cnt" ]
do
    echo "$i = $1"
    i=`expr $i + 1`
    shift
done
$ ./ex3.sh A B C
1 = A
2 = B
3 = C

예제를 보면 $2$3등을 사용하지 않고 오로지 $1만을 사용하여 모든 파라미터를 읽고 있다. 마치 배열에 접근하듯이, 개수가 정해지지 않은 변수 목록에서 하나씩 값을 읽을 수 있는 것이다.

잠깐 : shift를 실행할 때마다 가장 앞쪽의 변수값은 사라지게 된다. 그러므로 다시 사용할 필요가 있는 경우에는 문제가 된다. 이런 경우를 위한 해결 방법은 뒷 부분에서 설명한다.

위치 매개변수 재정의

지금까지 예제의 위치 매개변수는 쉘의 입력 인자로 받은 값만을 사용하였다. 하지만 위치 매개변수는 set -- 명령을 통해 새로 정의할 수 있다.

#!/bin/sh

echo "param cnt = $#"
echo $1
echo $2
echo $3

set -- one two

echo "param cnt = $#"
echo $1
echo $2
$ ./ex4.sh A B C
param cnt = 3
A
B
C
param cnt = 2
one
two

참고 : set 명령은 위치 파라미터 정의 용도 외에 쉘의 옵션을 설정하는 용도로도 쓰인다. 예를 들어 set -a b와 같은 명령은 -a를 파라미터가 아닌 set 옵션으로 인식한다. 이러한 경우에 -a도 파라미터로 인식하도록 하기 위한 옵션이 --이다.

함수와 위치 매개변수

위치 매개변수는 쉘 스크립트의 함수에 전달되는 인자를 전달 받을때도 사용된다. 다음은 두 수의 합을 구하는 함수의 예제이다.

#!/bin/sh

add()
{
    s=`expr $1 + $2`
    echo $s
}

a=$1
b=$2

sum=`add $a $b`

echo "$1 + $2 = $sum"
$ ./ex5.sh 3 5
3 + 5 = 8

함수에 인자를 전달하는 것은 쉘에 인자를 전달하는 것과 거의 동일하다고 생각할 수 있다.

가변 인자 처리 함수

이전 예제의 add()함수는 인자로 2개를 받도록 되어 있다. 만약, 인자 개수가 고정되어 있지 않고 2개 이상인 경우까지 처리하려면 어떻게 해야할까?

#!/bin/sh

add()
{
    cnt=$#
    i=1 
    s=0

    while [ "$i" -le "$cnt" ]
    do
        s=`expr $s + $1`
        i=`expr $i + 1`
        shift
    done

    echo $s
}

a=$1
b=$2
c=$3

sum=`add $a $b $c`

echo "$1 + $2 + $3 = $sum"
$ ./ex6.sh 3 5 7
3 + 5 + 7 = 15

반복 루프와 shift를 통한 처리는 앞에서 봤던 예제와 크게 다른점이 없다. 여기서 주목해야 하는 부분은 echo "$1 + $2 + $3 = $sum"이다. add() 함수 내부에서 shift를 사용했음에도 $1 $2 $3 값이 그대로 보존되고 있다. 이것은 함수가 시작될때 그 함수만의 위치 매개변수 공간이 별도로 생성되고 함수가 종료되면 같이 소멸되기 때문이다. 이렇게 함수 호출을 통한 방법으로 shift시 사라지는 변수값의 문제를 해결할 수 있다.

위치 매개변수 복사

shift로 인한 문제의 해결 방법 중 기존값을 복사해 두었다가 다시 복원하는 방법도 있다. 참고로 $@는 전체 파라미터를 의미한다.

#!/bin/sh

echo "$1 $2 $3"

str=$@

echo "$1"
shift
echo "$1"
shift
echo "$1"
shift

set -- $str

echo "$1 $2 $3"
$ ./ex7.sh A B C
A B C
A
B
C
A B C

eval 활용

이번에는 shift 명령을 사용하지 않고 탐색하는 방법을 알아보자.

eval 명령은 쉘이 스크립트를 한번 더 해석하도록 한다.

$ str1="str2"
$ str2="str3"
$ echo $str1
str2
$ eval echo \$$str1
str3

eval echo \$$str1 문장은 $str1이 먼저 해석되어 echo $str2로 1차 변경되고, 다시 한번 해석되어 최종적으로 echo str3이 된다. 참고로 \$$str1의 역슬래시 기호는 다음에 나오는 $기호가 변수를 나타내기 위한 기호가 아닌 일반 문자로 해석되도록 하는 역할을 한다.

이제 eval을 위치 매개변수에 활용해 보자.

#!/bin/sh

set -- jan feb mar apr may jun jul aug sep oct nov dec

cnt=$#

i=1
while [ "$i" -le "$cnt" ]
do
    eval echo \${$i}
    i=`expr $i + 1`
done
$ ./ex8.sh 
jan
feb
mar
apr
may
jun
jul
aug
sep
oct
nov
dec

위 예제의 eval echo \${$i}는 루프를 돌면서 echo ${1}, echo ${2}, echo ${3}, … echo ${12}으로 1차 변환되어 모든 위치매개변수를 탐색하고 있다.

여기서 주목할 것은 중괄호 기호({ })를 사용하고 있다는 점이다. 위치 매개변수 표기는 $0 ~ $9 만 가능한다. 예를 들어 $10$1과 숫자 0으로 구성된것으로 해석된다. 이러한 경우에 모두 위치를 나타내는 숫자로 인식하도록 하는 것이 중괄호이다. 즉, ${10}와 같이 표기하면 정상적으로 인식한다.

지금까지 설명한 방법으로 배열변수가 없는 본쉘에서의 가변 개수 배열 처리 방법을 알아보았다. 얼핏보면 그렇게 많이 불편하지 않은것 같지만, 예를 들어 하나의 루프 안에서 두가지 배열을 써야 하는 경우만 생각해도 난감할 것이다. 이렇듯 배열 변수를 완전히 대체하기는 어려울 테지만, 어쩔수 없이 본쉘을 써야만 하는 상황에서 최소한의 대안이 될 것이다.