Sort

AWK 는 array 에 값이 입력될 때 기본적으로 정렬을 하지 않습니다. 다시 말해서 array 에 값이 입력된 순서대로 출력이 되지 않습니다. index 나 value 별로 정렬하여 출력하려면 PROCINFO["sorted_in"] 변수를 설정하는 방법이나 asort(), asorti() 함수를 이용해야 합니다.

다음은 테스트를 위한 코드인데요. index 값으로는 두 자리 정수 random 넘버를 사용하였고, value 값은 실제 array 에 입력된 순서가 됩니다. 출력 결과를 보면 index 도 정렬이 안 돼있고 array 에 입력된 순서에 따라 출력도 되지 않고 있습니다.

$ awk 'BEGIN{ srand()
    for ( i=0; i<10; i++ ) 
        arr[ int(rand() * 100 - 50) ] = i    # index 는 random, value 는 입력 순서가 된다.
    for ( i in arr ) 
        printf "index %3d : value %d\n", i, arr[i] 
}'
index  15 : value 9
index  17 : value 2
index   0 : value 6
index -25 : value 3
index  -9 : value 8
index -16 : value 1
index  40 : value 7
index  48 : value 4
index -43 : value 0
index  27 : value 5

array 에서 index 가 자동으로 정렬되는 경우가 한가지 있는데요. 바로 index 값이 양의 정수일 때입니다. 다음을 보면 index 값으로 양의 정수 random 넘버를 사용하였는데 index 가 순서대로 정렬되어 출력되는 것을 볼 수 있습니다. 따라서 array 에 값을 입력할 때 입력된 순서대로 출력하고 싶으면 index 값을 양의 정수로 하면 됩니다.

$ awk 'BEGIN{ srand()
    for ( i=1; i<=10; i++ ) 
        arr[ int(rand() * 100) ] = i
    for ( i in arr ) 
        printf "index %3d : value %d\n", i, arr[i] 
}'
index   1 : value 3
index   7 : value 7
index  13 : value 6
index  39 : value 1
index  45 : value 5
index  67 : value 4
index  77 : value 9
index  90 : value 10
index  97 : value 8
# index 값이 random 넘버이기 때문에 중복될 수가 있어서
# array 원소 개수가 10 개 미만으로 나올 수가 있습니다.

awk 는 PROCINFO["sorted_in"] 변수값 설정을 통해서 index 나 value 별로 정렬을 하여 출력할 수가 있습니다. 기본적으로 아래 표에서 보는 바와 같이 숫자 or 스트링 비교를 통한 오름, 내림차순 정렬을 지원하지만 사용자가 직접 비교 함수를 작성하여 적용할 수도 있습니다. 이 설정은 for ( idx in array ) 문으로 array 값을 출력할 때 적용되는 것으로 필요할 때마다 for 문 이전에 위치하여 여러 번 사용할 수 있습니다.

PROCINFO ["sorted_in"] Description
@unsorted index 를 정렬하지 않습니다. ( default 값입니다. )
( index 가 양의 정수일 경우 정렬됩니다. )
@ind_str_asc index 값을 스트링 비교를 해서 ascending order 정렬합니다.
@ind_str_desc index 값을 스트링 비교를 해서 descending order 정렬합니다.
@ind_num_asc index 값을 숫자 비교를 해서 ascending order 정렬합니다.
( 숫자가 아닌 경우 0 으로 취급됩니다. )
@ind_num_desc index 값을 숫자 비교를 해서 descending order 정렬합니다.
@val_str_asc value 값을 스트링 비교를 해서 ascending order 정렬합니다.
@val_str_desc value 값을 스트링 비교를 해서 descending order 정렬합니다.
@val_num_asc value 값을 숫자 비교를 해서 ascending order 정렬합니다.
@val_num_desc value 값을 숫자 비교를 해서 descending order 정렬합니다.
@val_type_asc value 값의 type 에 따라 ascending order 정렬합니다.
숫자로 된 값이 제일 먼저 나오고 다음으로 스트링, subarray
순서로 정렬합니다. 숫자->스트링->subarray
@val_type_desc value 값의 type 에 따라 descending order 정렬합니다.
subarray->스트링->숫자 순서로 정렬합니다.

다음은 @ind_num_asc 값 설정을 통하여 음수가 포함된 index 값을 정렬하여 출력하는 예입니다.

$ awk 'BEGIN{ srand()   
    for ( i=0; i<10; i++ ) 
        arr[ int(rand() * 100 - 50) ] = i

    PROCINFO["sorted_in"]="@ind_num_asc"
    for ( i in arr ) 
        printf "index %3d : value %d\n", i, arr[i] 
}'
index -43 : value 9
index -35 : value 6
index -27 : value 3
index -11 : value 5
index  -9 : value 0
index  -7 : value 4
index   2 : value 7
index  19 : value 1
index  30 : value 2
index  47 : value 8

다음은 두 개의 for (idx in array) 문에 대해서 각각 PROCINFO["sorted_in"] 값을 적용시켰을 때 출력값이 어떻게 변하는지 비교해 보세요

$ awk 'BEGIN { 

    a["ccc"] = "aaa3"
    a["aaa"] = "aaa1"
    a["bbb"] = "aaa2"

    b["bbb"] = "bbb2"
    b["aaa"] = "bbb1"
    b["ccc"] = "bbb3"

    for ( i in a ) print a[i]
    for ( j in b ) print b[j]
}'

aaa1        # 1,3,2 정렬 안됨
aaa3
aaa2
bbb1        # 1,3,2 정렬 안됨
bbb3
bbb2
----------------------------------

$ awk 'BEGIN { 

    a["ccc"] = "aaa3"
    a["aaa"] = "aaa1"
    a["bbb"] = "aaa2"

    b["bbb"] = "bbb2"
    b["aaa"] = "bbb1"
    b["ccc"] = "bbb3"

    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( i in a ) print a[i]
    for ( j in b ) print b[j]
}'

aaa1        # 1,2,3 asc 정렬
aaa2
aaa3
bbb1        # 1,2,3 asc 정렬
bbb2
bbb3
--------------------------------------------

$ awk 'BEGIN { 

    a["ccc"] = "aaa3"
    a["aaa"] = "aaa1"
    a["bbb"] = "aaa2"

    b["bbb"] = "bbb2"
    b["aaa"] = "bbb1"
    b["ccc"] = "bbb3"

    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( i in a ) print a[i]

    PROCINFO["sorted_in"] = "@ind_str_desc"
    for ( j in b ) print b[j]
}'

aaa1        # 1,2,3 asc 정렬
aaa2
aaa3
bbb3        # 3,2,1 desc 정렬
bbb2
bbb1

#####################  두 번째 예제  #########################

$ awk 'BEGIN { 

    a["ccc"]["ccc"] = "ccc3"
    a["ccc"]["aaa"] = "ccc1"
    a["ccc"]["bbb"] = "ccc2"

    a["aaa"]["bbb"] = "aaa2"
    a["aaa"]["aaa"] = "aaa1"
    a["aaa"]["ccc"] = "aaa3"

    a["bbb"]["aaa"] = "bbb1"
    a["bbb"]["ccc"] = "bbb3"
    a["bbb"]["bbb"] = "bbb2"

    for ( i in a )
        for ( j in a[i] )
            print a[i][j]
}'

aaa1        # aaa,ccc,bbb 정렬 안됨
aaa3        # 1,  3,  2   정렬 안됨
aaa2
ccc1
ccc3
ccc2
bbb1
bbb3
bbb2
----------------------------------

$ awk 'BEGIN { 

    a["ccc"]["ccc"] = "ccc3"
    a["ccc"]["aaa"] = "ccc1"
    a["ccc"]["bbb"] = "ccc2"

    a["aaa"]["bbb"] = "aaa2"
    a["aaa"]["aaa"] = "aaa1"
    a["aaa"]["ccc"] = "aaa3"

    a["bbb"]["aaa"] = "bbb1"
    a["bbb"]["ccc"] = "bbb3"
    a["bbb"]["bbb"] = "bbb2"

    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( i in a )
        for ( j in a[i] )
            print a[i][j]
}'

aaa1        # aaa,bbb,ccc  asc 정렬
aaa2        # 1,  2,  3    asc 정렬
aaa3
bbb1
bbb2
bbb3
ccc1
ccc2
ccc3
------------------------------------------

$ awk 'BEGIN { 

    a["ccc"]["ccc"] = "ccc3"
    a["ccc"]["aaa"] = "ccc1"
    a["ccc"]["bbb"] = "ccc2"

    a["aaa"]["bbb"] = "aaa2"
    a["aaa"]["aaa"] = "aaa1"
    a["aaa"]["ccc"] = "aaa3"

    a["bbb"]["aaa"] = "bbb1"
    a["bbb"]["ccc"] = "bbb3"
    a["bbb"]["bbb"] = "bbb2"

    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( i in a ) {
        PROCINFO["sorted_in"] = "@ind_str_desc"
        for ( j in a[i] )
            print a[i][j]
    }
}'

aaa3        # aaa,bbb,ccc  asc  정렬
aaa2        # 3,  2,  1    desc 정렬
aaa1
bbb3
bbb2
bbb1
ccc3
ccc2
ccc1

asort, asorti 함수

asort(), asorti() 함수를 이용하는 방법은 array 에서 index 나 value 값만 따로 분리하여 양의 정수를 index 로 가지는 정렬된 array 를 만들고자 할때 사용합니다. asort() 함수는 value 를 type_asc 정렬하고 ( @val_type_asc 와 같이 ) asorti() 함수는 index 를 type_asc 정렬합니다.

# asort() 함수를 이용한 value 정렬
# 기존 array 는 삭제되고 양의 정수를 index 로 갖는 정렬된 array 가 생성된다.
$ awk '
BEGIN {
    arr["ZZ"] = 30
    arr["YY"] = 10
    arr[70]   = "BB"
    arr[90]   = 20
    arr[80]   = "AA"

    asort(arr)

    for ( i in arr ) 
        printf "index %s : value %s\n", i, arr[i] 
}'
index 1 : value 10
index 2 : value 20
index 3 : value 30
index 4 : value AA
index 5 : value BB

# asorti() 함수를 이용한 index 정렬
# 기존 array 는 삭제되고 양의 정수를 index 로 갖는 정렬된 array 가 생성된다.
$ awk '
BEGIN {
    arr["ZZ"] = 30
    arr["YY"] = 10
    arr[70]   = "BB"
    arr[90]   = 20
    arr[80]   = "AA"

    asorti(arr)

    for ( i in arr ) 
        printf "index %s : value %s\n", i, arr[i] 
}'
index 1 : value 70
index 2 : value 80
index 3 : value 90
index 4 : value YY
index 5 : value ZZ

기존 array 가 삭제되면 안 될 경우 두 번째 인수를 사용할 수 있습니다. 이때는 기존의 array 가 변경되지 않은 상태로 남아있게 되고 정렬된 결과는 dest array 에 설정됩니다.

asort(source, dest)

asorti(source, dest)

이 함수에서도 세 번째 인수를 사용하여 사용자 정의 비교 함수를 적용할 수 있습니다.

asort(source, dest, "my_compare_func")

asorti(source, dest, "my_compare_func")

사용자 정의 비교 함수

위에서 알아본 방법들은 기본적으로 array 의 index 와 value 값만을 가지고 정렬을 하기 때문에 가령 index 와 value 두 개의 값을 가지고 정렬을 한다거나 다차원 배열을 사용하는 환경에서 ORDER BY 절을 구현하기가 어려운 경우가 있습니다. 이때는 사용자 정의 비교 함수를 사용해야 합니다.

사용자 정의 비교 함수를 작성하는 방법은 먼저 함수의 매개변수로 첫 번째 원소의 index 와 value, 두 번째 원소의 index 와 value, 4 개를 기본적으로 사용합니다. return 값으로 0 보다 작은 값이 사용될 경우 첫 번째 원소가 두 번째 원소보다 앞에 오게 되고 0 보다 큰 값이 사용되면 두 번째 원소가 첫 번째 원소보다 앞에 오게 됩니다. return 값이 0 이면 함께 오게 됩니다.

# i1, v1 는 비교되는 첫 번째 원소의 index 와 value 에 해당하고
# i2, v2 는 비교되는 두 번째 원소의 index 와 value 에 해당합니다.
function comp_func(i1, v1, i2, v2    ...)
{
    # comp_func(i1, v1, i2, v2) < 0
    #   Index i1 comes before index i2 during loop traversal.
    #
    # comp_func(i1, v1, i2, v2) == 0
    #   Indices i1 and i2 come together,
    #   but the relative order with respect to each other is undefined.
    #
    # comp_func(i1, v1, i2, v2) > 0
    #   Index i1 comes after index i2 during loop traversal.

    return ?
}

작성 예제 )

$ awk -f - <<\EOF
BEGIN {
    letters = "abcdefghijklmnopqrstuvwxyz" \
              "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    split(letters, arr, "")

    PROCINFO["sorted_in"] = "compare"
    for( i in arr ) { printf "%s ", arr[i] }
    print ""
}
function compare(i1, v1, i2, v2,    l, r)
{
    l = tolower(v1)
    r = tolower(v2)

    if (l < r)       return -1
    else if (l == r) return 0
    else             return 1
}
EOF

a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O .....

Quiz

아래 데이터를 보면 첫번째 컬럼 값이 중복되어 나타나는데요. 처음 하나만 출력하되 순서를 유지해서 출력하려면 어떻게 할까요? 단 PROCINFO["sorted_in"] 는 사용하지 않습니다.

$ cat data.txt
66115f5 HEAD@{1}: reset: moving to 66115f5
49436cf HEAD@{3}: reset: moving to 49436cf
66115f5 HEAD@{5}: commit: update
49436cf HEAD@{6}: reset: moving to 49436cf
aab4238 HEAD@{7}: reset: moving to aab4238
49436cf HEAD@{8}: commit: update
aab4238 HEAD@{9}: reset: moving to HEAD
aab4238 HEAD@{10}: reset: moving to HEAD
  • a[i++] 와같이 index 로 양의 정수를 사용하면 입력 순서를 유지할 수 있습니다.
  • !($1 in b)b[$1] 는 첫번째 컬럼 값의 중복 제거를 위한 것.
$ cat data.txt | awk '!($1 in b) {a[i++]=$0; b[$1]} END {for (i in a) print a[i]}' 
66115f5 HEAD@{1}: reset: moving to 66115f5
49436cf HEAD@{3}: reset: moving to 49436cf
aab4238 HEAD@{7}: reset: moving to aab4238