Arrays
쉘 스크립트에서 array 는 프로그래밍 언어에서처럼 주요 기능이 아닙니다.
실제 스크립트를 작성하는데 있어서 array 가 잘 사용되지도 않습니다.
따라서 array 는 POSIX 에 정의되어있지 않으며 sh
에서는 사용할 수 없습니다.
하지만 interactive shell 에 사용되는 bash 의 경우는 select 문이나
사용자 정의 command completion 에서 array 가 사용되므로 참고삼아 보면 될 것 같습니다.
array 변수는 export 할 수 없습니다.
Array 만들기
array 는 index 로 숫자를 사용하는 indexed array 와 스트링을 사용할수 있는 associative array 가 있습니다.
indexed array 는 별다른 설정없이 사용할수 있으나 associative array 는 declare -A 변수명
으로 먼저 선언을 해줘야 합니다.
index 에는 매개변수 확장, 명령 치환, 산술 확장 을 모두 사용할 수 있습니다.
############### indexed array ################
AA=( 11 "hello array" 22 )
BB=( /usr/bin/x* /usr/bin/y* )
CC=( {001..100} )
idx=2 # index 값으로 사용되는 변수
AA=( [0]=11 [1]="hello array" [idx]=22 )
AA[0]=11
AA[1]="hello array"
AA[idx]=22 # indexed array 는 변수를 사용할때 '$' 를 붙이지 않아도 된다.
..........................
$ echo ${AA[2]} ${AA[idx]}
22 22
############# associative array ##############
declare -A BB # 먼저 declare -A 로 선언
idx=ef # index 값으로 사용되는 변수
BB=( [ab]=11 [cd]="hello array" [$idx]=22 )
BB[ab]=11
BB[cd]="hello array"
BB[$idx]=22 # associative array 는 변수를 사용할때 '$' 를 붙여야 한다.
key1=foo key2=bar key3=zoo value=100
BB[$key1,$key2,$key3]=$value
.....................................
$ echo ${BB[$key1,$key2,$key3]}
100
만약에 indexed array 의 index 로 문자를 사용하게 되면 [0]
에 할당됩니다.
따라서 indexed array 를 바로 associative array 로 사용할 수는 없고
먼저 unset 을 한후 associative array 로 만들어 주어야 합니다.
$ CC=(11 22 33) # indexed array
$ declare -p CC
declare -a CC=([0]="11" [1]="22" [2]="33")
$ CC[hello]=100 # index 로 문자를 사용하면 [0] 에 할당된다.
$ declare -p CC
declare -a CC=([0]="100" [1]="22" [2]="33")
$ unset -v CC # 먼저 unset 한후 associative array 로 지정
$ declare -A CC
$ CC[hello]=100
$ declare -p CC
declare -A CC=([hello]="100" )
Array 값 조회하기
$ AA=(apple banana orange)
$ declare -p AA
declare -a AA='([0]="apple" [1]="banana" [2]="orange")'
현재 shell 에 정의된 모든 array 변수명 보기
$ compgen -A arrayvar
Array 값의 사용
array 값을 사용할 때는 반드시 { }
를 사용해야 합니다.
만약에 index 가 [2]
인 원소를 사용하기 위해 $AA[2]
와 같이 한다면
먼저 $AA
가 변수 확장이 되어 foo 가 되고 이어 foo[2]
에서 globbing 이 일어나게 됩니다.
$ AA=(foo bar zoo)
# 만약 현재 디렉토리에 'foo2' 라는 파일이 있다면 globbing 이 일어나 결과는 'foo2' 가된다.
$ echo $AA[2]
foo[2]
$ echo ${AA[2]} # 반드시 '{ }' 를 사용해야 한다.
zoo
indexed array 에서는 기본적으로 index 에서 산술연산이 가능합니다.
associative array 경우는 index 값으로 스트링을 사용하므로 안됩니다.
$ AA=(11 22 33 44 55)
$ idx=2
$ echo ${AA[idx]}
33
$ echo ${AA[ idx + 1 ]}
44
$ echo "${AA[ idx == 2 ? 3 : 4 ]}"
44
# index 값으로 음수를 사용할수도 있습니다.
$ echo ${AA[-1]}
55
$ echo ${AA[-2]}
44
associative array 에서는 index 값에 공백의 포함 여부에 따라 각각 다른값으로 인식하므로 주의할 필요가 있습니다. ( index 에서 globbing 은 발생하지 않습니다.)
$ declare -A CC
$ CC[$(echo aaa)]=100
$ CC[ $(echo aaa)]=200
$ CC[ $(echo aaa) ]=300 # 공백으로 인해 각각 다른 값으로 인식
$ declare -p CC
declare -A CC=([aaa]="100" [" aaa "]="300" [" aaa"]="200" )
array 변수를 index 없이 일반 변수처럼 사용할 수도 있는데
이때는 기본적으로 index 가 [0]
인 원소가 됩니다.
이것은 associative array 도 마찬가지 입니다.
$ foo=100
$ echo $foo ${foo[0]} # 변수 $foo 의 값은 ${foo[0]} 와도 같다.
100 100
---------------------------------------------------
# indexed array # associative array
$ declare -A BB
$ AA=( 11 22 33 ) $ BB=( [ab]=11 [cd]=22 )
$ echo $AA $ echo $BB # 현재 index 가 '[0]' 인 원소는 없으므로
11 $
$ BB=100 # index 가 '[0]' 인 원소가 생성된다.
$ AA=100
$ echo $BB
$ echo $AA 100
100
$ declare -p BB # 100 은 '[0]' 에 할당된다.
$ echo ${AA[@]} declare -A BB=([0]="100" [ab]="11" [cd]="22" )
100 22 33
$ echo ${BB[@]}
100 11 22
@ , * 차이점
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[*]}
값을 변수에 대입할 경우 해당 변수도 ${arr[*]}
와 같이 출력됩니다.
$ IFS=XYZ
$ arr=(11 22 33 44 55)
$ echo "${arr[*]}" , ${arr[*]} $ echo "${arr[@]}" , ${arr[@]}
11X22X33X44X55 , 11 22 33 44 55 11 22 33 44 55 , 11 22 33 44 55
$ foo=${arr[*]} $ bar=${arr[@]}
# 변수 $foo 도 ${arr[*]} 와 같이 출력된다.
$ echo "$foo" , $foo $ echo "$bar" , $bar
11X22X33X44X55 , 11 22 33 44 55 11 22 33 44 55 , 11 22 33 44 55
$ IFS=YZ # IFS 변수값 변경
$ echo "${arr[*]}" , ${arr[*]}
11Y22Y33Y44Y55 , 11 22 33 44 55
$ echo "$foo" , $foo # IFS 값이 변경되면 기존의 $foo 변수값은
11X22X33X44X55 , 11X22X33X44X55 # 인수 구분자가 X 로 고정된다.
-----------------------------------------------------------------
# array 값을 특정 문자로 join 해서 출력하려면 다음과같이 하면 됩니다.
$ echo $( IFS=':'; echo "${arr[*]}" )
11:22:33:44:55
명령을 실행할때 ${arr[@]}
를 인수에 포함시키면 ?
$ AA=(11 22 33 44 55)
$ args.sh "aa ${AA[@]} bb"
$1 : aa 11
$2 : 22 # 인수가 분리된다.
$3 : 33
$4 : 44
$5 : 55 bb
------------
$ args.sh "aa ${AA[*]} bb"
$1 : aa 11 22 33 44 55 bb
IFS 값을 이용해 단어를 분리하여 원소를 설정하는 것은 quote 을 하지 않았을 때 적용됩니다.
$ AA=( aa:bb cc:dd ee:ff )
# ${AA[*]} 를 quote 하지 않으면 IFS=: 에의해 원소가 6 개로 분리된다.
# ${AA[*]} 는 array 이므로 원소단위로 분리가 됩니다.
$ ( IFS=:; BB=( ${AA[*]} ); echo "${BB[0]}" "${#BB[@]}" )
aa 6
# ${AA[@]} 를 quote 하지 않으면 *, @ 는 차이가 없다.
$ ( IFS=:; BB=( ${AA[@]} ); echo "${BB[0]}" "${#BB[@]}" )
aa 6
$ ( IFS=:; BB=( "${AA[*]}" ); echo "${BB[0]}" ) # quote 을 하여 원소가 1 개가됨
aa:bb:cc:dd:ee:ff
$ ( BB=( "${AA[@]}" ); echo "${BB[0]}" ) # quote 을 하여 원소가 3 개가됨
aa: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
ARR index : 0, value : 11
ARR index : 1, value : 22
ARR index : 2, value : 33
######## 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
ARR index : ef, value : 22
ARR index : cd, value : hello array
ARR index : ab, value : 11
Array 대입과 복사
array AA 를 BB 에 대입할경우 BB=${AA[@]}
와 같이 하면 array 가 아니라 AA 값 전체가 BB 변수에 대입됩니다. array 대입을 하려면 항상 =( )
를 사용해야 합니다.
$ AA=( 11 22 33 )
$ BB=${AA[@]}
$ echo ${#BB[@]} : ${BB[1]} # 정상적으로 array 대입이 되지 않는다.
1 :
$ echo "$BB" # array AA 의 전체 값이 $BB 에 대입된다.
11 22 33
$ BB=( "${AA[@]}" ) # array 대입은 항상 '=( )' 를 사용해야 한다
$ echo ${#BB[@]} : ${BB[1]}
3 : 22
array 전체를 복사할 때는 다음과 같은 방법도 있습니다.
$ declare -A AA BB
$ AA=( [ab]=11 [cd]="hello world" [ef]=22 )
$ declare -p AA
declare -A AA=([ab]="11" [cd]="hello world" [ef]="22" )
$ tmp=$(declare -p AA); eval "${tmp/declare -A AA=/declare -A BB=}"
$ declare -p BB
declare -A BB=([ab]="11" [cd]="hello world" [ef]="22" )
Array 원소 삭제하기
명령 | 의미 |
---|---|
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=()
와 unset
두 가지가 있는데 어떤 경우이건
실행이 되면 변수는 존재하지 않는 상태로 되고 매개변수 확장도 그에 따라 처리됩니다.
# array=() 의 경우 # unset 의 경우
$ AA=( 1 2 3 ) $ BB=( 1 2 3 )
$ [ -v AA ]; echo $? $ [ -v BB ]; echo $?
0 0
$ AA=() $ unset -v BB
$ [ -v AA ]; echo $? $ [ -v BB ]; echo $?
1 1
하지만 차이점이 있는데 array=()
는 전체 원소가 삭제되지만
변수 정보는 남아있는 상태가 되어 associative array 같은 속성이 유지가 됩니다.
반면에 unset 은 변수가 완전히 없어지는 것과 같습니다.
# array=() 의 경우 # unset 의 경우
$ declare -A CC=( [ab]=11 ) $ declare -A DD=( [ab]=11 )
$ CC=() $ unset -v DD
$ declare -p CC $ declare -p DD
declare -A CC=() bash: declare: DD: not found
$ CC+=( 22 ) $ DD+=( 22 )
bash: CC: 22: must use subscript $
when assigning associative array
# associative array 속성이 남아 있어서 오류가 된다.
index 정렬 하기
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
삭제된 원소의 index 값이 남는 이유는 다음과 같이 index 를 이용해 array 순회도중에
원소를 삭제할 수 있기 때문입니다.
이때는 또한 ${#AA[@]}
값을 직접 순회에 사용해서는 안됩니다.
( 그러면 첫번째와 같이 원소가 모두 표시되지 않습니다.)
$ AA=(11 22 33 44 55) $ AA=(11 22 33 44 55) num=${#AA[@]}
$ for (( i = 0; i < ${#AA[@]}; i++ )); do $ for (( i = 0; i < num; i++ )); do
(( i == 1 )) && unset -v "AA[i+1]" (( i == 1 )) && unset -v "AA[i+1]"
echo ${AA[i]} echo ${AA[i]}
done done
11 11
22 22
44 44
55
null 값을 가지고 있는 원소 삭제하기
unset 한 원소는 존재하지 않는 원소인 반면에 null 값을 가지고 있는 원소는 존재하고 있는 원소입니다.
다음과 같이 삭제할 수 있습니다.
$ AA=":arch linux:::ubuntu linux::::fedora linux::"
$ IFS=: read -a ARR <<< "$AA"
$ echo ${#ARR[@]}
10
$ echo ${ARR[0]}
$ echo ${ARR[1]}
arch linux
# ARR 에서 null 값을 가지고 있는 원소 삭제하기
$ IFS= # 먼저 IFS 값을 null 로 설정
$ ARR=( ${ARR[*]} ) # quotes 을 사용하지 않는다.
$ unset -v IFS
$ echo ${#ARR[@]}
3
$ echo "${ARR[0]},${ARR[1]},${ARR[2]}"
arch linux,ubuntu linux,fedora 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 원소 추가하기
array 에 원소를 추가할 땐 +=( )
를 사용할 수 있습니다.
$ AA=( "Arch Linux" "Ubuntu Linux" "Suse Linux" )
$ echo ${#AA[@]}
3
$ AA+=( "Fedora Linux" )
$ echo "${AA[@]}"
Arch Linux Ubuntu Linux Suse Linux Fedora Linux
$ echo ${#AA[@]}
4
#######################################
$ declare -A BB=( [aa]="Arch Linux" [bb]="Ubuntu Linux" [cc]="Suse Linux" )
$ echo ${#BB[@]}
3
$ BB+=( [dd]="Fedora Linux" )
$ echo ${#BB[@]}
4
#######################################
$ AA=( "Arch Linux" Ubuntu Fedora )
$ AA=( "${AA[@]}" AIX HP-UX )
$ echo "${AA[@]}"
Arch Linux Ubuntu Fedora AIX HP-UX
$ echo ${#AA[@]}
5
전체 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..." 와 같습니다. ('X' 는 IFS 변수의 첫번째 값)
# 그러므로 IFS 값을 '\n' 바꾸고 echo 한것을 명령치환 값으로 보내면
# '\n' 에의해 원소들이 분리되어 array 에 저장되게 됩니다.
$ IFS=$'\n'
$ AA=( $(echo "${AA[*]/Su*/}") )
$ unset -v IFS # array 입력이 완료되었으므로 IFS 값 복구
$ echo ${#AA[@]} # 삭제가 반영되어 원소개수가 3 개로 나온다
3
$ echo "${AA[1]},${AA[2]}" # index 도 정렬되었다.
Ubuntu Linux,Fedora Linux
맨앞을 가리키는 #
, 맨뒤를 가리키는 %
anchor 의 사용
# 각 원소에서 'Linux' 의 위치가 다르다.
$ AA=( "Linux Arch" "Ubuntu Linux" "Suse Linux" "Linux Fedora" )
# 'Linux' 가 맨앞에 위치한 경우만 변경됨
$ echo "${AA[@]/#Linux/XXX}"
XXX Arch Ubuntu Linux Suse Linux XXX Fedora
# 'Linux' 가 맨뒤에 위치한 경우만 변경됨
$ echo "${AA[@]/%Linux/XXX}"
Linux Arch Ubuntu XXX Suse XXX Linux Fedora
# 각 원소의 맨앞에 '::' 가 추가된다.
$ echo "${AA[@]/#/::}"
::Linux Arch ::Ubuntu Linux ::Suse Linux ::Linux Fedora
# 각 원소의 맨뒤에 '::' 가 추가된다.
$ echo "${AA[@]/%/::}"
Linux Arch:: Ubuntu Linux:: Suse Linux:: Linux Fedora::
# 각 원소의 맨앞 2 문자가 삭제된다.
$ echo "${AA[@]/#??/}"
nux Arch untu Linux se Linux nux Fedora
# 각 원소의 맨뒤 2 문자가 삭제된다.
$ echo "${AA[@]/%??/}"
Linux Ar Ubuntu Lin Suse Lin Linux Fedo
스트링에서 특정 문자를 구분자로 하여 필드 분리하기.
$ AA="Arch Linux:Ubuntu Linux:Suse Linux:Fedora Linux"
$ IFS=: read -a ARR <<< "$AA"
$ echo ${#ARR[@]}
4
$ echo "${ARR[1]}"
Ubuntu Linux
---------------------------------------------------------------------
# 입력되는 원소값에 glob 문자가 있을경우 globbing 이 발생할수 있으므로 noglob 옵션 설정
$ set -f; IFS=: # IFS 값을 ':' 로 설정
$ ARR=( $AA )
$ set +f; unset -v IFS # array 입력이 완료되었으므로 IFS 값 복구
$ echo ${#ARR[@]}
4
$ echo "${ARR[1]}"
Ubuntu Linux
array 를 사용할 수 없는 sh
에서는 다음과 같이 할 수 있습니다.
$ set -f; IFS=: # set -f 옵션을 이용해 globbing 을 방지
$ set -- $AA # IFS 값에 따라 원소들을 분리하여
$ set +f; unset -v IFS # positional parameters 에 할당
$ echo $#
4
$ echo "$2"
Ubuntu Linux
Array 원소 정렬
따로 array 정렬 기능을 제공하는 것은 아니지만 다음과 같은 방법을 사용하여 정렬할 수 있습니다.
$ arr=( 11 44 22 55 33 )
$ arr=( $( IFS=$'\n'; echo "${arr[*]}" | sort -n ) )
$ echo ${#arr[@]}; echo "${arr[@]}"
5
11 22 33 44 55
--------------------------------------------------
$ arr2=( "Fedora Linux" "Ubuntu Linux" "Suse Linux" "Arch Linux" )
$ IFS=$'\n'
$ arr2=( $( echo "${arr2[*]}" | sort ) )
$ unset -v IFS
$ echo ${#arr2[@]}; echo "${arr2[@]}"
4
Arch Linux Fedora Linux Suse Linux 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 와 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 array1 명령과 같게되어 unset 이 되지 않고 있습니다.
$ unset -v array[12]
$ echo ${array[12]}
300
$ unset -v 'array[12]' # globbing 을 방지하기 위해 quote.
$ echo ${array[12]} # 이제 정상적으로 unset 이됨
$
-----------------------------------------------------------------------
# 대입 연산에서는 기본적으로 단어분리, globbing 이 발생하지 않습니다.
AA="400 500"
array[12]=$AA array[12]=*abc*
Array 대입 연산시 오류가 발생할 경우
Array 대입 연산을 할 때 =( )
안에서 명령 치환을 사용할 수 있는데
이때 명령 치환에서 오류가 발생할 경우 명령 치환을 이용한 대입 연산에서와 같이
동일하게 처리됩니다.
#!/bin/bash
echo start ...
IFS=$'\n'
files=( $( cd xxx && find /usr/bin -type f ) ) # xxx 는 존재하지 않는 디렉토리
unset -v IFS
echo end ...
$ ./test.sh
start ...
./test.sh: line 5: cd: xxx: No such file or directory
end ... # 실행중 오류가 발생했지만 끝까지 실행되고
$ echo $? # 종료 상태 값도 0 이 된다.
0
--------------------------------------------------
#!/bin/bash
echo start ...
IFS=$'\n'
files=( $( cd xxx && find /usr/bin -type f ) ) || exit 3 # "|| exit 3" 을 추가
unset -v IFS
echo end ...
$ ./test.sh
start ...
./test.sh: line 5: cd: xxx: No such file or directory # exit 3 에의해 실행이 중단된다.
$ echo $?
3
--------------------------------------------------
#!/bin/bash -e
echo start ... # shebang 라인에 errexit 옵션 설정
IFS=$'\n'
files=( $( cd xxx && find /usr/bin -type f ) )
unset -v IFS
echo end ...
$ ./test.sh
start ...
./t.sh: line 5: cd: xxx: No such file or directory # errexit 옵션에 의해 실행이 중단된다.
$ echo $?
1
Quiz
다음 AA
array 가 Suse Linux
원소를 가지고 있는지는 어떻게 체크할 수 있을까요?
$ AA=( "Arch Linux" "Ubuntu Linux" "Suse Linux" "Fedora Linux" )
------------------------------------
# 첫번째는 grep 명령을 이용하는 방법입니다.
if printf '%s\n' "${AA[@]}" | grep -xq 'Suse Linux'; then
echo yes
fi
------------------------------------
# 두번째는 for 문을 이용하는 방법입니다.
for elem in "${AA[@]}"; do
if test "$elem" = "Suse Linux"; then
echo "yes"
fi
done
-------------------------------------
# 세번째는 매개변수확장 기능에서 substring removal 을 이용하여 해당 원소를 삭제한후
# 결과를 BB 에 대입하고 AA 와 BB 의 원소개수를 비교합니다.
IFS=$'\n'; BB=( `echo "${AA[*]#Suse Linux}"` ); unset -v IFS
if test ${#AA[@]} -ne ${#BB[@]}; then
echo "yes"
fi
2 .
다음 AA
array 에서 Suse Linux
원소를 삭제하려면 어떻게 할까요?
AA=( "Arch Linux" "Ubuntu Linux" "Suse Linux" "Fedora Linux" )
for (( i = 0; i < ${#AA[@]}; i++)); do
if [[ ${AA[i]} == "Suse Linux" ]]; then
unset -v "AA[i]"
fi
done
AA=( "${AA[@]}" ) # array 원소 삭제후 index 정렬
echo "number of array : " ${#AA[@]}
echo "${AA[0]},${AA[1]},${AA[2]}"
------------------------------------------
$ ./test.sh
number of array : 3 # 원소 개수도 맞고
Arch Linux,Ubuntu Linux,Fedora Linux # index 도 올바로 정렬되었다.
associative array 는 주로 index 값을 이용해 값을 읽고, 삭제하지만 다음은 value 값을 검색해서 매칭이 될경우 삭제하는 예입니다.
declare -A AA=(
[aa]="Arch Linux"
[bb]="Ubuntu Linux"
[cc]="Suse Linux"
[dd]="Fedora Linux"
)
for idx in "${!AA[@]}"; do
if [ "${AA[$idx]}" = "Suse Linux" ]; then
unset -v "AA[$idx]" # associative array 는 index 로 사용되는
fi # 변수에 '$' 문자를 사용해야 하고
done # 원소 삭제후 따로 index 정렬이 필요 없습니다.
echo "number of array : " ${#AA[@]}
echo "${AA[aa]},${AA[bb]},${AA[cc]},${AA[dd]}"
---------------------------------------------
$ ./test.sh
number of array : 3 # 원소 개수도 맞고
Arch Linux,Ubuntu Linux,,Fedora Linux # ${AA[cc]} 원소도 정상적으로 삭제되었다.
3 .
line='aaa bbb "foo bar" ccc'
와 같은 라인을 분리해서 array 로 만들려고 합니다.
이때 quotes 으로 둘러싸인 "foo bar"
가 하나의 원소가 되게 하려면 어떻게 할까요?
$ line='aaa bbb "foo bar" ddd'
$ arr=( $line )
$ echo ${#arr[@]} # 기본적으로 IFS=$' \t\n' 에의해 분리되므로
5 # array 의 원소가 5 개가 된다.
$ echo ${arr[2]}
"foo
$ echo ${arr[3]}
bar"
이때는 eval 을 사용하면 quotes 을 해석해서 분리할 수 있습니다.
$ line='aaa bbb "foo bar" ccc'
$ echo arr=\( $line \)
arr=( aaa bbb "foo bar" ddd )
$ eval arr=( $line )
$ echo ${#arr[@]}
4
$ echo ${arr[2]}
foo bar
4 .
변수가 array 인지 아닌지는 어떻게 체크할 수 있을까요?
declare -p 변수명
의 출력값을 이용하여 판단할 수 있습니다.
$ AA=100
$ declare -p AA
declare -- AA="100" # 일반 변수는 '--'
$ BB=( 1 2 3 )
$ declare -p BB
declare -a BB=([0]="1" [1]="2" [2]="3") # indexed array 는 '-a'
$ declare -A CC
$ CC=( [ab]=100 [cd]=200 [ef]=300 )
$ declare -p CC
declare -A CC=([ab]="100" [cd]="200" [ef]="300" ) # associative array 는 '-A'
$ declare -i DD=100
$ declare -p DD
declare -i DD="100" # integer 속성 변수는 '-i'
----------------------------------------------------------------------------
$ type_check() {
[[ `declare -p $1` =~ ^declare\ (..) ]] &&
case ${BASH_REMATCH[1]} in
--) echo normal variable ;;
-a) echo indexed array ;;
-A) echo associative array ;;
-i) echo integer variable ;;
*) echo other ... ;;
esac
}
$ type_check AA
normal variable
$ type_check BB
indexed array
그런데... bash 4.3 부터는 named reference 기능이 추가되었습니다. 따라서 named reference 일 경우 다음과 같이 원본 변수까지 loop 처리를 해주어야 합니다.
$ declare AA=( 1 2 3 )
$ declare -n AA2=AA
$ declare -n AA3=AA2
$ declare -p AA3
declare -n AA3="AA2"
$ declare -p AA2
declare -n AA2="AA"
$ declare -p AA
declare -a AA=([0]="1" [1]="2" [2]="3")
-------------------------------------------------
type_check() {
local var reg
var=$( declare -p "$1" ) && {
reg='^declare -n [^=]+="([^"]+)"$'
while [[ $var =~ $reg ]]; do
var=$( declare -p "${BASH_REMATCH[1]}" )
done
case ${var#declare -} in
-*) echo normal variable ;;
a*) echo indexed array ;;
A*) echo associative array ;;
i*) echo integer variable ;;
*) echo other ... ;;
esac
}
}
또는 매개변수 확장 기능을 이용할 수 있습니다.