Arrays

쉘 스크립트에서 array 는 프로그래밍 언어에서처럼 주요 기능이 아닙니다. 실제 스크립트를 작성하는데 있어서 array 가 잘 사용되지도 않습니다. 따라서 array 는 POSIX 에 정의되어있지 않으며 sh 에서는 사용할 수 없습니다. 하지만 interactive shell 에 사용되는 bash 의 경우는 사용자 정의 command completion 기능에 array 가 사용되므로 참고삼아 보시면 될 것 같습니다.

array 는 index 로 숫자를 사용하는 indexed array 와 스트링을 사용할수 있는 associative array 가 있습니다. indexed array 는 별다른 설정없이 사용할수 있으나 associative array 는 declare -A 변수명 으로 먼저 선언을 해줘야 합니다. 현재 사용중인 array 를 empty 로 만들때 AA=() 를 사용하는데 이것은 unset -v AA 와 동일한 효과를 갖습니다.

array 변수는 export 할 수 없습니다.

Array 만들기

index 를 나타내는 [ ] 에서는 산술연산을 할수있고 변수를 사용할 수 있습니다.

########## indexed array #########

AA=( 11 "hello array" 22 )

AA=( [0]=11 [1]="hello array" [2]=22 )

AA[0]=11
AA[1]="hello array"
AA[2]=22

######## associative array #######

declare -A AA        # 먼저 declare -A 로 선언 해야 한다

AA=( [ab]=11 [cd]="hello array" [ef]=22 )

AA[ab]=11
AA[cd]="hello array"
AA[ef]=22

Array 값 조회하기

$ AA=(apple banana orange)

$ declare -p AA
declare -a AA='([0]="apple" [1]="banana" [2]="orange")'

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

$ compgen -A arrayvar

Array 값의 사용

AA=(11 22 33) 와 같은 array 가 있을경우 $AA 값은 첫번째 원소인 11 과 같습니다. 여기서 AA[2] 값을 사용하기 위해 $AA[2] 와 같이 한다면 먼저 $AA 가 변수확장이 되어 11 이 되고 이어 11[2] 에서 globbing 이 일어나게 됩니다. 그러므로 array 값을 사용할 때는 반드시 { } 매개변수 확장을 이용해야 합니다.

$ AA=(11 22 33)

# 만약 현재 디렉토리에 '112' 라는 파일이 있다면 globbing 이 일어나 결과는 '112' 가 됩니다.
$ echo $AA[2]   
11[2]

$ echo ${AA[2]}
33

@ , * 차이점

Double quotes 을 하지 않을경우 ${array[@]}${array[*]} 는 차이가 없습니다. 왜냐하면 똑같이 IFS 값에의해 단어분리가 되기 때문입니다. 하지만 " " 로 quote 하였을경우 @* 는 의미가 달라집니다. "array[@]" 는 array 개개의 원소를 " " 로 quote 하여 나열하는것과 같고 "array[*]" 는 array 의 모든원소를 하나의 " " 안에 IFS 변수값을 구분자로 하여 넣는것과 같습니다.

Quote 여부 의미
${AA[@]}
${AA[*]}
quote 하지 않으면 둘은 차이가 없다
"${AA[@]}" "${AA[0]}" "${AA[1]}" "${AA[2]}" ...
"${AA[*]}" "${AA[0]}X${AA[1]}X${AA[2]}..."
여기서 'X' 는 IFS 변수값 중에 첫번째 문자
# 예를 위해 공백을 5 개씩 주었다.

$ AA=( "Arch     Linux" "Ubuntu     Linux" "Fedora     Linux" )  
$ echo ${#AA[@]}
3

# quote 하지 않아 IFS 값에 의해 단어분리가 일어나 공백이 유지되지 않고 
# for 문을 돌려도 6 개로 나온다

$ echo ${AA[@]}                           # 연이은 공백이 하나의 공백으로 바뀌였다.
Arch Linux Ubuntu Linux Fedora Linux      

$ for v in ${AA[@]}; do echo "$v"; done   # 원소개수가 6 개로 나온다
Arch
Linux
Ubuntu
Linux
Fedora
Linux

$ echo ${AA[*]}                           # 마찬가지다.
Arch Linux Ubuntu Linux Fedora Linux

$ for v in ${AA[*]}; do echo "$v"; done
Arch
Linux
Ubuntu
Linux
Fedora
Linux

################# quote 하였을 경우 #################

$ echo "${AA[@]}"                        
Arch     Linux Ubuntu     Linux Fedora     Linux   # 공백이 유지된다.

$ echo "${AA[*]}"
Arch     Linux Ubuntu     Linux Fedora     Linux

$ for v in "${AA[@]}"; do echo "$v"; done   # "array[@]" 는 원소개수가 유지된다.
Arch     Linux
Ubuntu     Linux
Fedora     Linux

$ for v in "${AA[*]}"; do echo "$v"; done   # "array[*]" 는 원소개수가 1 개로 나온다.
Arch     Linux Ubuntu     Linux Fedora     Linux

명령을 실행할때 ${arr[@]} 를 인수에 포함시키면 ?

$ AA=(1 2 3 4 5)
$ args.sh "aa ${AA[@]} bb"  # 인수가 분리된다.
$1 : aa 1
$2 : 2
$3 : 3
$4 : 4
$5 : 5 bb

---------

$ args.sh "aa ${AA[*]} bb"
$1 : aa 1 2 3 4 5 bb

특수 표현식

표현식 의미
${#array[@]}
${#array[*]}
array 전체 원소의 개수를 나타냄
${#array[N]}
${#array[string]}
indexed array 에서 N 번째 원소의 문자수 ( stringlength ) 를 나타냄
associative array 에서 index 가 string 인 원소의 문자수를 나타냄
${array[@]}
${array[*]}
array 전체 원소의 value 를 나타냄
${!array[@]}
${!array[*]}
array 전체 원소의 index 를 나타냄
${!name@}
${!name*}
name 으로 시작하는 이름을 갖는 모든 변수를 나타냄
예) echo "${!BASH@}"

Array iteration 하기

########## indexed array #########

ARR=(11 22 33)

for idx in ${!ARR[@]}; do
    echo ARR index : $idx, value : "${ARR[idx]}"  # ${ARR[$idx]} 로 해도됨
done

######## associative array ########

declare -A ARR

ARR=( [ab]=11 [cd]="hello array" [ef]=22 )

for idx in "${!ARR[@]}"; do
    echo ARR index : "$idx", value : "${ARR[idx]}" 
done

Array 복사하기

array AA 를 BB 로 복사할때 BB=${AA[@]} 와 같이 하면 array 복사가 아니라 AA 값 전체가 BB 변수에 할당됩니다. array 복사를 하려면 항상 ( ) 를 사용해야 합니다.

$ AA=( 11 22 33 )

$ BB=${AA[@]}           
$ echo "${BB[1]}"         # 정상적으로 array 복사가 되지 않는다.

$ echo "$BB"              # array AA 의 전체 값이 BB 에 할당된다.
11 22 33

$ BB=( "${AA[@]}" )       # array 복사는 항상 '( )' 를 사용해야 한다 
$ echo "${BB[1]}"
22

Array 원소 삭제하기

array 원소를 나타내는 데는 [ ] glob 문자가 사용되므로 globbing 이 발생하지 않게 항상 quote 해야합니다.

명령 의미
array=()
unset -v array
unset -v "array[@]"
array 전체를 삭제
unset -v "array[N]" indexed array 에서 N 번째 원소를 삭제
unset -v "array[string]" associative array 에서 index 가 string 인 원소를 삭제

array 원소를 삭제하면 자동으로 ${#array[@]} 값에도 반영이 되고 for 문을 이용해도 삭제된 원소는 나타나지 않습니다. 그러나 삭제된 원소의 index 는 그대로 남아있고 뒤에오는 원소들의 index 값들도 바뀌지 않습니다. 다시 array 를 재할당하면 index 가 정렬됩니다.

$ AA=(11 22 33 44 55)

$ unset -v "AA[2]"

$ echo ${#AA[@]}                           # 삭제후 원소 개수가 정상적으로 반영됨
4                                      

$ for v in "${AA[@]}"; do echo "$v"; done  
11                                         # for 문에서도 정상적으로 반영됨.
22
44         
55

$ echo ${AA[1]} :  ${AA[2]} : ${AA[3]}     # 그런데 삭제된 index 2 가 공백으로 남아있다!
22 : : 44                              

$ AA=( "${AA[@]}" )                        # 재할당 하면 index 가 정렬된다
$ echo ${AA[1]} :  ${AA[2]} : ${AA[3]}
22 : 44 : 55

null 값을 가지고 있는 원소 삭제하기 : unset 한 원소는 존재하지 않는 원소인 반면에 null 값을 가지고 있는 원소는 존재하고 있는 원소입니다. 다음과 같이 삭제할 수 있습니다.

$ AA=":arch linux:::ubuntu linux::::fedora linux::"

$ IFS=: read -ra ARR <<<  "$AA"

$ echo ${#ARR[@]}
10
$ echo ${ARR[0]}

$ echo ${ARR[1]}
arch linux

# ARR 에서 null 값을 가지고 있는 원소 삭제하기

$ set -f; IFS=''                    # 먼저 IFS 값을 null 로 설정
$ ARR=( ${ARR[@]} )                 # quotes 을 사용하지 않는다.
$ set +f; IFS=$' \n\t'

$ echo ${#ARR[@]}
3
$ echo ${ARR[0]}
arch linux
$ echo ${ARR[1]}
ubuntu linux

Array 원소 추출하기

array 의 특정 원소들을 추출하려고 할때는 ${array[@]:offset:length} 형식을 사용합니다.

$ AA=( Arch Ubuntu Fedora Suse Mint );

$ echo "${AA[@]:2}"
Fedora Suse Mint

$ echo "${AA[@]:0:2}"
Arch Ubuntu

$ echo "${AA[@]:1:3}"
Ubuntu Fedora Suse

Array 원소 추가하기

$ AA=( "Arch Linux" Ubuntu Fedora);

$ AA=( "${AA[@]}" AIX HP-UX);

$ echo "${AA[@]}"
Arch Linux Ubuntu Fedora AIX HP-UX

#######################################

$ BB=( 11 22 33 )
$ echo ${#BB[@]}
3

$ BB+=( 44 )
$ echo ${#BB[@]}
4

#######################################

$ declare -A AA=( [aa]=11 [bb]=22 [cc]=33 )
$ echo ${#AA[@]}
3

$ AA+=( [dd]=44 )
$ echo ${#AA[@]}
4

전체 array 원소에 패턴을 적용하기

패턴을 적용하여 매칭되는 부분을 바꾸거나 삭제할수 있습니다. 패턴에는 맨앞을 가리키는 #, 맨뒤를 가리키는 % anchor 를 사용할수 있습니다.

$ AA=( "Arch Linux" "Ubuntu Linux" "Suse Linux" "Fedora Linux" )

# 전체원소들의 'u' 문자가 'X' 로 바뀐것을 볼수있습니다.
# 그런데 Ubuntu Linux 와 Suse Linux 에서는 첫자만 바뀌고 나머지는 바뀌지 않았습니다.

$ echo "${AA[@]/u/X}"
Arch LinXx UbXntu Linux SXse Linux Fedora LinXx

# 원소 전체에 적용하려면 '//pattern' 을 사용합니다.

$ echo "${AA[@]//u/X}"
Arch LinXx UbXntX LinXx SXse LinXx Fedora LinXx

# Su* 패턴과 매칭되는 원소가 없어졌습니다.
# 이것은 원소가 삭제된것이 아니라 공백으로 치환된 것입니다.

$ echo "${AA[@]/Su*/}"
Arch Linux Ubuntu Linux Fedora Linux

$ AA=( "${AA[@]/Su*/}" )

$ echo ${#AA[@]}                    # 원소개수가 4 개로 그대로다.
4

$ for v in "${AA[@]}"; do echo "$v"; done
Arch Linux
Ubuntu Linux
                                   # index 2 는 공백으로 나온다.
Fedora Linux

패턴을 이용해 매칭되는 원소 삭제하기

$ AA=( "Arch Linux" "Ubuntu Linux" "Suse Linux" "Fedora Linux" )

# "${AA[*]}" 는 "elem1Xelem2Xelem3X..." 와 같습니다.
# 그러므로 IFS 값을 '\n' 바꾸고 echo 한것을 명령치환 값으로 보내면 
# '\n' 에의해 원소들이 분리되어 array 에 저장되게 됩니다.

$ set -f; IFS=$'\n'
$ AA=( $(echo "${AA[*]/Su*/}") )
$ set +f; IFS=$' \t\n'            # array 입력이 완료되었으므로 IFS 값 복구

$ echo ${#AA[@]}                  # 삭제가 반영되어 원소개수가 3 개로 나온다
3

$ echo "${AA[1]} ${AA[2]}"        # index 도 정렬되었다.
Ubuntu Linux Fedora Linux

스트링에서 특정 문자를 구분자로 하여 필드 분리하기.

$ AA="Arch Linux:Ubuntu Linux:Suse Linux:Fedora Linux"

$ IFS=: read -ra ARR <<< "$AA"
$ echo ${#ARR[@]}
4
$ echo "${ARR[1]}"
Ubuntu Linux

---------------------------------------------------------------------
# 입력되는 원소값에 glob 문자가 있을경우 globbing 이 발생할수 있으므로 noglob 옵션 설정

$ set -f; IFS=:             # IFS 값을 ':' 로 설정
$ ARR=( $AA )
$ set +f; IFS=$' \t\n'      # array 입력이 완료되었으므로 IFS 값 복구

$ echo ${#ARR[@]}
4
$ echo "${ARR[1]}"
Ubuntu Linux

--------------------------------------------------------------------
# array 를 사용할 수 없는 sh 에서는 다음과 같이 할 수 있습니다.

$ set -f; IFS=:                   # globbing 을 disable
$ set -- $AA                      # IFS 값에 따라 원소들을 분리하여 
$ set +f; IFS=$' \t\n'            # positional parameters 에 할당

$ echo $#
4
$ echo "$2"
Ubuntu Linux

파일 라인을 array 로 읽어들이기

$ cat datafile 
100  Emma    Thomas
200  Alex    Jason
300  Madison Randy

$ mapfile -t arr < datafile 

$ echo "${arr[0]}"
100  Emma    Thomas

$ echo "${arr[1]}"
200  Alex    Jason

Array index 에서 산술연산을 할 수 있다.

산술연산 특수표현식 에서 처럼 [ ] 에서도 동일하게 산술연산을 할 수 있습니다.

$ AA=(1 2 3 4 5)
$ idx=2
$ echo "${AA[ idx == 2 ? 3 : 4 ]}"
4

Array index 와 globbing

array 에서 index 에 사용되는 [ ] 문자는 globbing 에서 사용하는 glob 문자와 겹칩니다. 그러므로 사용에 주의해야 될 점이 있습니다. 다음과 같은 경우 unset 명령을 실행할때 [ ] 에서 globbing 이 일어나 정상적으로 unset 이 되지 않고 있습니다.

$ array=( [10]=100 [11]=200 [12]=300 )
$ echo ${array[12]}
300

$ touch array1               # 현재 디렉토리에 임의로 array1 파일생성

# unset 을 실행하였으나 globbing 에의해 array[12] 이 array1 파일과 매칭이되어 
# 실제적으로 unset -v array1 명령과 같게되어 unset 이 되지 않고 있습니다.
$ unset -v array[12]         
$ echo ${array[12]}         
300                       

$ unset -v 'array[12]'       # globbing 을 disable 하기위해 quote.
$ echo ${array[12]}          # 이제 정상적으로 unset 이됨

대입연산은 처리되는 시점이 키워드와 같으므로 globbing 에 신경 쓰지 않아도 됩니다.

$ touch array2

# 대입연산은 globbing 이전에 처리되므로 정상적으로 대입된다.
$ array[12]=100

$ echo ${array[12]}
100