Arrays
사실상 awk 의 메인 기능이라고 할 수 있습니다. awk array 는 associative array 인데 인덱스로 숫자, 스트링 상관없이 섞어 사용할 수가 있습니다. index 나 value 별로 sort 할 수도 있고 Multidimensional array 도 사용할 수 있으므로 array 를 활용하면 대부분의 문제를 해결할 수 있습니다.
array 원소 추출하기
$ awk '
BEGIN {
arr["apple"] = 1.23
arr[35] = "mango"
arr[0.05] = 100
arr["fruit"] = "banana"
arr[-7] = "guava"
for (idx in arr) {
printf "index: %-10s value: %s\n" ,idx ,arr[idx]
}
}'
index: 35 value: mango
index: 0.05 value: 100
index: apple value: 1.23
index: fruit value: banana
index: -7 value: guava
array 원소 체크하기
$ awk -f - <<\EOF
BEGIN {
fruit["apple"] = 4
fruit["mango"] = 12
fruit["guava"] = 8
fruit["banana"] = 16
if ("apple" in fruit)
print "We have apple."
else
print "We do not have apple."
delete fruit["apple"]
print "fruit[\"apple\"] has been deleted."
if ("apple" in fruit)
print "We have apple."
else
print "We do not have apple."
}
EOF
We have apple.
fruit["apple"] has been deleted.
We do not have apple.
logical NOT 은 다음과 같이 합니다.
if ( ! ("apple" in fruit)) ...
array 삭제하기
delete 문은 개별 array 원소를 삭제할 때, 또는 해당 array 전체를 삭제할 때 사용할 수 있습니다.
$ awk '
BEGIN {
fruit["apple"] = 4
fruit["mango"] = 12
fruit["guava"] = 8
fruit["banana"] = 16
print "fruit array length: " length(fruit)
delete fruit["apple"] # array 원소 삭제
print "fruit array length: " length(fruit)
delete fruit # array 전체 삭제
print "fruit array length: " length(fruit)
}'
fruit array length: 4
fruit array length: 3
fruit array length: 0
for 반복문 실행중에 array 원소 또는 array 전체를 삭제하면 length 값은 변하지만 반복 횟수에는 영향을 미치지 않습니다. 다음번 for 반복문 실행시에 반영이 됩니다.
$ awk 'BEGIN {
a[11]=100;
a[22]=200;
a[33]=300;
a[44]=400;
a[55]=500;
for (i in a ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
delete a[33]
delete a[44]
}
print "------------------------------"
for (i in a ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
}
}'
length: 5 index: 11 value: 100 # index 33, 44 원소가 삭제되어 length 값이 변경되었지만
length: 3 index: 22 value: 200 # 반복 횟수는 기존 원소 개수대로 5 번이 된다.
length: 3 index: 33 value:
length: 3 index: 44 value:
length: 3 index: 55 value: 500
------------------------------
length: 3 index: 11 value: 100 # 다음번 for 반복문 실행시에 반영된다.
length: 3 index: 22 value: 200
length: 3 index: 55 value: 500
# for 반복문 실행중에 삭제한 원소가 출력되지 않게 하려면
# 다음과 같이 value 값이 empty 인지 체크하면 됩니다.
$ awk 'BEGIN {
a[11]=100;
a[22]=200;
a[33]=300;
a[44]=400;
a[55]=500;
for (i in a ) {
if ( a[i] != "" ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
delete a[33] # 반복문 실행중에 원소를 삭제
delete a[44]
}
}
}'
length: 5 index: 11 value: 100
length: 3 index: 22 value: 200
length: 5 index: 55 value: 500
마찬가지로 for 반복문 실행중에 추가한 원소는 출력에 포함되지 않습니다. 다음번 for 반복문 실행시에 반영됩니다.
$ awk 'BEGIN {
a[11]=100;
a[22]=200;
a[33]=300;
for (i in a ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
a[44]=400 # 반복문 실행중에 원소를 추가
a[55]=500
}
print "------------------------------"
for (i in a ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
}
}'
length: 3 index: 11 value: 100 # for 반복문 실행중에 추가한 원소는
length: 5 index: 22 value: 200 # 출력에 포함되지 않는다.
length: 5 index: 33 value: 300
------------------------------
length: 5 index: 11 value: 100 # 다음번 for 반복문 실행시에 반영된다.
length: 5 index: 22 value: 200
length: 5 index: 33 value: 300
length: 5 index: 44 value: 400
length: 5 index: 55 value: 500
새로운 레코드 입력시 마다 기존에 사용되던 array 변수를 reset 해 사용하려면 delete 합니다.
BEGIN { token[0][0]; stack[0] }
{
idx = 0
tokenize()
. . .
delete token # 다음 레코드 입력시에는 array 가 초기화 된다.
delete stack
}
delete 문을 적절히 사용하면 추가적인 메모리 사용을 제한할 수 있습니다. 가령 array A 를 생성하는데 1G 메모리가 사용되고, array B 를 생성하는데 1G, array C 를 생성하는데 1G 가 사용된다면 awk 실행시 총 3G 메모리가 필요하게 됩니다. 하지만 array B 를 생성하기 전에 A 를 삭제하고, array C 를 생성하기 전에 B 를 삭제하게 되면 awk 실행 중에 사용되는 전체 메모리를 1G 로 제한할 수 있습니다.
한번 메모리가 특정 크기로 사용되고 나면 delete 한다고 해서 반환되지는 않습니다. 그러니까 array A, B 를 생성하여 메모리 사용량이 2G 가 되었다면 이후 A, B 를 delete 한다고 해서 메모리가 반환되지는 않지만 array C 를 생성할 때는 추가적인 메모리가 사용되지 않는다는 점입니다.
보통 JOIN 연산을 할때 데이터를 array 를 이용해 메모리로 읽어 들이는데 데이터 사이즈가 크고 여러번 array 가 생성되어 사용된다면 먼저 delete 문을 이용해 사용이 끝난 array 를 삭제해주는 것이 좋겠습니다.
Multidimensional arrays
awk 에서 다차원 배열은 index 로 다음과 같이 여러 가지 형태를 모두 사용할 수 있습니다.
그리고 대입 연산은 scalar context 이므로 array 를 값으로 사용할 수는 없습니다.
arr[1][2] = 3
arr["animal"][1] = "dog"
arr[1]["orange"] = 4
arr["planet"]["mars"] = 4
arr[1][3][1, "name"] = "barney"
array 명칭과 관련해서는 아래와 같은 배열의 경우
fruit
을 main array
라고 하고 fruit[1]
, fruit[2]
을 subarray
라고 합니다.
fruit[1][1]
fruit[1][2]
...
fruit[2][1]
fruit[2][2]
...
scalar 와 array 의 구분
# foo 는 array, foo[1] 는 scalar
foo[1] = 1
# foo, foo[1] 는 array, foo[1][2] 는 scalar
foo[1][2] = 2
# foo[1] 은 scalar 가 되므로 foo[1][2] 에서 array 로 사용하여 오류 발생
$ awk 'BEGIN {
foo[1] = 3
foo[1][2] = 4;
}'
awk: cmd. line:3: fatal: attempt to use scalar 'foo["1"]' as an array
array length
length()
함수는 scalar 변수와 array 변수에서 각각 다르게 적용됩니다.
scalar 변수에서는 string length 가 되고 array 변수에서는 array length 가 됩니다.
array 의 경우 SQL DISTINCT 절을 구현할 때 활용됩니다.
$ awk 'BEGIN {
arr[1][1][1] = 1
arr[1][1][2] = 2
arr[1][1][3] = 3
arr[1][2][1] = 4
print length(arr)
print length(arr[1])
print length(arr[1][1])
}'
1 # arr[1]
2 # arr[1][1], arr[1][2]
3 # arr[1][1][1], arr[1][1][2], arr[1][1][3]
한가지 주의할 점은 다음과 같이 직접 설정하지 않은 array 원소가 코드 중에 사용될 경우에도 array 원소가 생성된다는 것입니다.
$ awk 'BEGIN {
a["AAA"] = 111
if ( a["BBB"] ) print "yes" # 또는 print a["BBB"]
print length(a)
for ( i in a ) print i
}'
2 # a["BBB"] 값을 체크만 했을뿐인데 array 원소가 추가됨
AAA
BBB
하지만 for 반복문 실행중에 생성된 원소는 반복 횟수에 영향을 미치지 않습니다. 다음번 for 반복문 실행시에 반영이 됩니다.
$ awk 'BEGIN {
a[11]=100;
a[22]=200;
a[33]=300;
for (i in a ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
printf a[44] # 여기서 새로 array 원소가 생성됨
printf a[55]
}
print "------------------------------"
for (i in a ) {
printf "length: %s index: %s value: %s\n", length(a), i, a[i];
}
}'
length: 3 index: 11 value: 100 # for 반복문 실행중에 원소가 생성되어 length 값이
length: 5 index: 22 value: 200 # 변경되었지만 반복 횟수에는 영향을 미치지 않는다.
length: 5 index: 33 value: 300
------------------------------
length: 5 index: 11 value: 100 # 다음번 for 반복문이 실행될때 반영된다.
length: 5 index: 22 value: 200
length: 5 index: 33 value: 300
length: 5 index: 44 value:
length: 5 index: 55 value:
원소 출력
$ awk 'BEGIN{
arr["fruit"][1] = "apple"
arr["fruit"][2] = "orange"
arr["fruit"][3] = "banana"
arr["animal"][1] = "dog"
arr["animal"][2] = "cat"
arr["animal"][3] = "mouse"
for (i in arr)
for(j in arr[i])
printf "%s:%s = %s\n", i, j, arr[i][j]
}'
fruit:1 = apple
fruit:2 = orange
fruit:3 = banana
animal:1 = dog
animal:2 = cat
animal:3 = mouse
---------------------------------------------------
$ awk 'BEGIN {
fruit["apple"]["study"] = 4
fruit["apple"]["kitchen"] = 5
fruit["mango"] = 12
fruit["guava"] = 8
fruit["banana"] = 16
for (j in fruit) {
if (isarray (fruit[j])) { # isarray 함수를 이용한 타입 체크
for (k in fruit[j])
printf "%s in %s = %d numbers\n", j, k, fruit[j][k]
} else {
printf "%s: %d numbers\n", j, fruit[j]
}
}
}'
guava: 8 numbers
mango: 12 numbers
apple in kitchen = 5 numbers
apple in study = 4 numbers
banana: 16 numbers
Recursion 을 이용한 다차원 배열 값 프린트
$ awk '
BEGIN {
walk_array(PROCINFO, "PROCINFO")
}
function walk_array(arr, name, i)
{
for (i in arr) {
if (isarray(arr[i]))
walk_array(arr[i], name "[" i "]")
else
printf("%s[%s] = %s\n", name, i, arr[i])
}
}'
.....
.....
PROCINFO[gid] = 1000
PROCINFO[mpfr_version] = GNU MPFR 3.1.5
PROCINFO[group2] = 24
PROCINFO[egid] = 1000
PROCINFO[group3] = 27
PROCINFO[identifiers][OFS] = scalar
PROCINFO[identifiers][rand] = builtin
PROCINFO[identifiers][ARGC] = scalar
PROCINFO[identifiers][dcgettext] = builtin
PROCINFO[identifiers][gsub] = builtin
PROCINFO[identifiers][PREC] = scalar
.....
.....
Comma 와 SUBSEP 을 이용하는 방법
인덱스에 ,
를 사용해서 arr[idx1, idx2]
와 같이 다차원 배열을 구성할 수 있습니다.
그런데 만약에 idx1 값에 a,b
와 같이 ,
가 포함되어 있다면
idx1 와 idx2 값을 정상적으로 분리할 수 없겠죠.
따라서 인덱스에 사용된 ,
는 저장될 때 SUBSEP 변수값으로 변경되어 저장됩니다.
SUBSEP 값은 non-printing 문자인 \034
를 사용하므로 idx1 값에 ,
가 포함되어도
SUBSEP 을 이용해 idx1, idx2 값을 분리할 수 있습니다.
인덱스에 [idx1, idx2]
가 사용되었다면 원소 체크에도 (idx1, idx2) in array
형태가 사용됩니다.
인덱스에 사용된 공백은 값에 포함되지 않으므로 [idx1,idx2]
와 [ idx1 , idx2 ]
는 모두 같은 값이 됩니다.
$ awk 'BEGIN {
arr[0, 0] = 45
arr[2,3] = "hello"
arr[1 , 1] = "hi"
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
if ((i, j) in arr) # (i, j) in array 형태를 이용해 원소 체크
printf ("arr[%d, %d] = %s\n", i, j, arr[i, j])
}'
arr[0, 0] = 45
arr[1, 1] = hi
arr[2, 3] = hello
split 함수를 이용한 인덱스 분리
$ awk 'BEGIN{
arr["fruit","apple",15]
arr["fruit","orange",35]
arr["fruit","banana",55]
for(idx in arr) {
split(idx, a, SUBSEP) # SUBSEP 변수를 이용
print a[1], a[2], a[3]
}
}'
fruit orange 35
fruit apple 15
fruit banana 55
index 를 숫자 비교를 해서 정렬할때는 ,
뒷 부분은 스트링 비교를 하게 됩니다.
$ awk 'BEGIN { a[9,9]=10; a[10,10]=20; a[11,11]=30
PROCINFO["sorted_in"] = "@ind_num_asc"
for (i in a) print a[i]
}'
10
20
30
$ awk 'BEGIN { a[1,9]=10; a[1,10]=20; a[1,11]=30
PROCINFO["sorted_in"] = "@ind_num_asc"
for (i in a) print a[i]
}'
20
30
10
Subarray 사용시 주의할 점
split 함수는 실행 결과로 두 번째 인수가 array 가 되는데요.
다음과 같이 두 번째 인수에 바로 subarray arr[1]
, arr[2]
를 사용하게 되면
아직 array 가 구성되지 않은 상태라 오류가 발생합니다.
$ awk 'BEGIN {
split("aa:bb:cc", arr[1], ":"); print arr[1][2]
split("11:22:33", arr[2], ":"); print arr[2][2]
}'
awk: cmd. line:2: fatal: split: second argument is not an array
따라서 다음과 같이 arr[1][0]
, arr[2][0]
를 먼저 선언하여 arr[1]
, arr[2]
를
array 로 만들어 줘야 합니다.
$ awk 'BEGIN {
arr[1][0]
split("aa:bb:cc", arr[1], ":"); print arr[1][2]
arr[2][0]
split("11:22:33", arr[2], ":"); print arr[2][2]
}'
bb
22
-------------------------------------------------------
$ awk 'BEGIN {
arr[1][0]
split("aa:bb:cc", arr[1], ":")
arr[2][0]
split("11:22:33", arr[2], ":")
for (i in arr)
for (j in arr[i])
print "arr[" i "][" j "] = " arr[i][j]
}'
arr[1][1] = aa
arr[1][2] = bb
arr[1][3] = cc
arr[2][1] = 11
arr[2][2] = 22
arr[2][3] = 33
array index 는 스트링으로 비교됩니다.
array index 값이 숫자일 경우는 먼저 스트링으로 변환된 후 비교됩니다.
따라서 다음과 같은 경우 모두 array 원소 개수는 1 개가 되고 값은 200 이 됩니다.
$ awk 'BEGIN {
arr[100] = 100
arr["100"] = 200
for ( i in arr ) printf "index : %s, value : %s\n", i , arr[i]
}'
index : 100, value : 200
$ awk 'BEGIN {
arr[-100] = 100
arr["-100"] = 200
for ( i in arr ) printf "index : %s, value : %s\n", i , arr[i]
}'
index : -100, value : 200
$ awk 'BEGIN {
arr[0.123] = 100
arr["0.123"] = 200
for ( i in arr ) printf "index : %s, value : %s\n", i , arr[i]
}'
index : 0.123, value : 200
숫자 17.0
이 스트링으로 변환되면 17
이 되므로
arr[17]
와 arr[17.0]
는 같은 원소가 됩니다.
# printf "%s", 17.0 값은 17
$ awk 'BEGIN {
arr[17] = 100
arr[17.0] = 200
arr["17.0"] = 300
for ( i in arr ) printf "index : %s, value : %s\n", i , arr[i]
}'
index : 17.0, value : 300
index : 17, value : 200
소수가 스트링으로 변환될 때는 CONVFMT 값의 적용을 받습니다.
CONVFMT 의 기본값은 %.6g
이므로 다음과 같은 경우 숫자 12.3456789 가 "12.3457" 스트링이 됩니다.
스트링 "12.3457" 과 "12.3456789" 는 다른 값이므로 결과적으로 2 개의 원소가 됩니다.
$ awk 'BEGIN {
arr[12.3456789] = 100
arr["12.3456789"] = 200
for ( i in arr ) printf "index : %s, value : %s\n", i , arr[i]
}'
index : 12.3456789, value : 200
index : 12.3457, value : 100
# %.8f 로 출력하면 index 값이 같게 나온다. (하나는 숫자, 하나는 스트링)
$ awk 'BEGIN {
arr[12.3456789] = 100
arr["12.3456789"] = 200
for ( i in arr ) printf "index : %.8f, value : %s\n", i , arr[i]
}'
index : 12.34567890, value : 200
index : 12.34567890, value : 100
# printf "%s", 12.3456789 값은 12.3457
# printf "%s", 12.34567 값도 12.3457
# 따라서 index 값이 같게 취급되어 첫번째 원소에 다시 200 이 대입된다.
$ awk 'BEGIN {
arr[12.3456789] = 100
arr[12.34567] = 200
for ( i in arr ) printf "index : %.8f, value : %s\n", i , arr[i]
}'
index : 12.34567890, value : 200
$ awk 'BEGIN {
arr1[12.3456789]
arr2[12.34567]
for ( i in arr1 )
for ( j in arr2 )
if ( i == j ) { print i, j; printf "%.8f %.8f\n", i, j }
}'
12.3457 12.3457
12.34567890 12.34567000
Octal 과 hexadecimal 은 10 진수로 처리됩니다.
# printf "%s", 021 값은 17
# printf "%s", 0x11 값은 17
$ awk 'BEGIN {
arr[17] = 100
arr["17"] = 200
arr[021] = 300 # Octal
arr[0x11] = 400 # Hexadecimal
for ( i in arr ) printf "index : %s, value : %s\n", i , arr[i]
}'
index : 17, value : 400 # 최종적으로 마지막 값인 400 이 된다.
Quiz
stack 은 context 유지에 사용되는 중요한 자료구조 인데요.
array 를 이용해 stack 자료구조를 만들려면 어떻게 할까요?
$ awk -f - <<\EOF
BEGIN {
stack[0] # 여기서 원소가 하나 생성돼 length 가 1 이 된다.
print "stack length: " slen(stack) # 따라서 stack length 를 구할때는 -1 을 합니다.
push(stack, 10)
print "stack length: " slen(stack)
push(stack, 20)
print "stack length: " slen(stack)
push(stack, 30)
print "stack length: " slen(stack)
print pop(stack)
print "stack length: " slen(stack)
print pop(stack)
print "stack length: " slen(stack)
print pop(stack)
print "stack length: " slen(stack)
}
function push(arr, val) {
arr[ length(arr) ] = val # push 되는 원소는 stack[1] 부터 들어가게 됩니다.
}
function pop (arr, len, ret) {
len = length(arr)
if ( len > 1 ) {
ret = arr[ len - 1 ]
delete arr[ len - 1 ]
}
return ret
}
function peek (arr, len, ret) {
len = length(arr)
if ( len > 1 )
ret = arr[ len - 1 ]
return ret
}
function slen(arr) {
return length(arr) - 1
}
EOF
####### 실행 결과 #######
stack length: 0
stack length: 1
stack length: 2
stack length: 3
30
stack length: 2
20
stack length: 1
10
stack length: 0