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