GROUP BY

array index 를 활용하면 group by 연산을 할 수 있습니다. 왜 그렇게 되는지 살펴보면 array a 의 index 값 $1 이 BBB 일 경우 a[BBB] 가 되는데 이때는 index 값으로 BBB 를 가지는 array 원소가 하나 생기는 것입니다. value 값을 대입하지 않았기 때문에 value 는 null 이 되구요. 다음에 $1 값으로 BBB 가 다시 나타나면 a[BBB] 가 되어 index 값이 중복되므로 결과적으로 하나만 남게 됩니다.

두 번째 예제에서처럼 a[$1]++ 을 하게 되면 이때는 a[BBB]++ 가되어 처음에 a[BBB] 원소가 생성됨과 동시에 value 가 1 이 됩니다. 다음에 $1 값으로 BBB 가 중복되어 나타나면 다시 a[BBB]++ 가 되므로 a[BBB] 의 value 가 +1 되어 2 가 됩니다.

$ cat file
AAA
EEE
BBB
CCC
DDD
DDD
BBB
BBB
...................................................

# select $1 from file group by $1 
$ awk '{ a[$1] } END { for (i in a) print i }' file
AAA
CCC
EEE
BBB
DDD

# select $1, count($1) from file group by $1 
$ awk '{ a[$1]++ } END { for (i in a) print i " : " a[i] }' file
AAA : 1
CCC : 1
EEE : 1
BBB : 3
DDD : 2

# select distinct($1) from file
$ awk '!a[$1]++' file
AAA
EEE
BBB
CCC
DDD

# select $1, count($1) as c from file group by $1 having c >= 2
$ awk '++a[$1] == 2'  file      # 중복라인을 가지고 있는 라인
DDD
BBB

낱말 출현 빈도 계산 후 정렬하기

# '.' 와 ',' 문자가 낱말에 포함되는 것을 방지하기 위해 RS 로 설정
$ awk -f - <<\EOF RS='\\.|,' song.txt
{                                            
    for (i=1; i<=NF; i++)                    
        words[$i]++                          
}                                            
END {                                        
    PROCINFO["sorted_in"] = "compare"        
    for (i in words)                         
        printf "%s %s\n",  i, words[i]       
 }                                           
# 이 비교 함수는 awk 명령 출력을 "| LC_ALL=C sort -k2,2r -k1,1" 한 것과 같습니다.
# ORDER BY count DESC, word ASC
function compare(i1, v1, i2, v2)
{                              
    if (v1 > v2) return -1               # 출현 빈도를 DESC 정렬하는 부분
    else if (v1 == v2)       
    {                                    # { ... } 부분이 낱말을 ASC 정렬하는 부분
        if ( i1 < i2 )       return -1   
        else if ( i1 == i2 ) return 0        
        else                 return 1
    }                                        
    else return 1
}
EOF                                    
                                      < song.txt 파일 내용 >
길이 4                                                                     
대한 4                               동해물과 백두산이 마르고 닳도록  
대한으로 4                           하느님이 보우하사 우리나라 만세.  
무궁화 4                             무궁화 삼천리 화려강산  
보전하세 4                           대한 사람, 대한으로 길이 보전하세.  
사람 4                                                                    
삼천리 4                             남산 위에 저 소나무, 철갑을 두른 듯  
화려강산 4                           바람서리 불변함은 우리 기상일세.  
우리 2                               무궁화 삼천리 화려강산  
이 2                                 대한 사람, 대한으로 길이 보전하세.  
가슴 1                                                                    
가을 1                               가을 하늘 공활한데 높고 구름 없이  
공활한데 1                           밝은 달은 우리 가슴 일편단심일세.  
괴로우나 1                           무궁화 삼천리 화려강산  
구름 1                               대한 사람, 대한으로 길이 보전하세.  
기상과 1                                                                  
기상일세 1                           이 기상과 이 맘으로 충성을 다하여  
나라 1                               괴로우나 즐거우나 나라 사랑하세.  
.....                                무궁화 삼천리 화려강산  
.....                                대한 사람, 대한으로 길이 보전하세.

현재 각 cpu core 에 바운드된 스레드 개수 출력

# ps 명령에서 psr 은 현재 스레드가 바운드된 cpu core 를 말하고
# 뒤에 '=' 는 ps 명령이 타이틀을 표시하지 않게 합니다.
# 출력 결과가 실제 코어 수 보다 많게 나오는 이유는 Hyper-Threading 때문입니다.

$ watch -td -n1 "$( cat <<\EOF
ps axH -o psr= |
awk '{a[$1]++} END { 
    for (i in a) { printf "cpu%-6d",i }; print "";
    for (i in a) { printf "%-9d", a[i] }; print "" }' 
EOF
)"
cpu0     cpu1     cpu2     cpu3     cpu4     cpu5     cpu6     cpu7     
289      232      269      198      260      214      279      215

group by 로 두 개 이상의 값을 사용

SUBSEP 을 사용하는 arr[$1,$3,$5] 형태를 이용하면 group by 로 두 개 이상의 값을 사용할 수 있습니다. 아래의 경우는 A, B 두 값을 group by 하기 위해 a[$1,$2] 를 사용하였습니다.

select 절에 count(C) 가 사용되었다면 입력 때는 a[$1,$2]["C"]++, 출력 때는 a[$1,$2]["C"] 을 사용하면 되는데요. count(distinct C) 가 사용되었기 때문에 distinct 를 구현하기 위해 입력 때는 a[$1,$2]["C"][$3], 출력 때는 length(a[$1,$2]["C"]) 가 사용되었습니다. 만약에 count(distinct C,D) 를 한다고 하면 입력 때 a[$1,$2]["C"][$3,$4] 로 하면 되겠죠

# awk 에서 데이터 파일을 사용할 때는 헤더 부분은 제외하세요
###################### 데이터 파일 내용 #######################
A   B   C   D   E
----------------------     #  x x 와 z o 는 2번씩 중복되고 x x 는 C 값이 같고 z o 는 다르다
x   x   e   2   10         #  x   x   e
y   y   g   1   8 
z   o   e   2   9          #  z   o   e
o   o   q   1   10
p   z   e   3   22
x   x   e   1   11         #  x   x   e
z   o   a   1   24         #  z   o   a
y   z   b   1   25

#################### SQL 문으로 나타낸 결과 ####################
select A, 
       B, 
       count(distinct C), 
       sum(D),
       sum(case when E>20 then E else 0 END) 
from test 
group by A,B

A   B  count(distinct C)   sum(D)   sum(case when E>20 then E else 0 END) 
-------------------------------------------------------
o   o    1       1       0
p   z    1       3       22
x   x    1       3       0
y   y    1       1       0
y   z    1       1       25
z   o    2       3       24

################################  AWK  ####################################

$ awk -f - <<\EOF OFS='\t' file
{ a[$1,$2]["C"][$3]; a[$1,$2]["D"] += $4; a[$1,$2]["E"] += ($5>20 ? $5:0) }
END { 
    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( i in a ) { 
        split(i, b, SUBSEP) 
        print b[1], b[2], length(a[i]["C"]), a[i]["D"], a[i]["E"] 
    }
}
EOF
o       o       1       1       0
p       z       1       3       22
x       x       1       3       0
y       y       1       1       0
y       z       1       1       25
z       o       2       3       24

ORDER BY

array index 로 a[$1,$2] 가 사용되었기 때문에 order by A, B 를 하기 위해서는 간단히 awk 에서 기본적으로 제공하는 @ind_str_asc 을 사용하면 되었는데요. 만약에 order by E 를 하려면 어떻게 해야 될까요? 이렇게 다차원 배열이 사용되는 환경에서는 value 에 적용되는 @val_num_asc 을 사용할 수 없습니다. 이때는 사용자 정의 비교 함수를 이용해 order by 절을 구현해야 합니다.

출력에 for ( i in a ) 문을 사용하므로 비교 함수에 전달되는 인수 값은 index 에 해당하는 i1, i2 값이 i 와 같게 되고 value 에 해당하는 v1, v2 값은 a[i] 와 같게됩니다.

select A, 
       B, 
       count(distinct C), 
       sum(D),
       sum(case when E>20 then E else 0 END) as E
from test 
group by A,B
order by E
.................................................

$ awk -f - <<\EOF OFS='\t' file
{ a[$1,$2]["C"][$3]; a[$1,$2]["D"] += $4; a[$1,$2]["E"] += ($5>20 ? $5:0) }
END { 
    PROCINFO["sorted_in"] = "compare"
    for ( i in a ) { 
        split(i, b, SUBSEP) 
        print b[1], b[2], length(a[i]["C"]), a[i]["D"], a[i]["E"] 
    }
}
function compare(i1, v1, i2, v2)
{ 
    if ( v1["E"] < v2["E"] )       return -1
    else if ( v1["E"] == v2["E"] ) return 0
    else                           return 1
}
EOF
y       y       1       1       0
o       o       1       1       0
x       x       1       3       0
p       z       1       3       22
z       o       2       3       24
y       z       1       1       25

HAVING

having 절을 구현하는 것은 비교적 간단합니다. 아래와 같이 if 문을 하나 추가하면 됩니다.

select A, 
       B, 
       count(distinct C), 
       sum(D) as D,
       sum(case when E>20 then E else 0 END) as E
from test 
group by A,B
having D > 1
order by E
.................................................

$ awk -f - <<\EOF OFS='\t' file
{ a[$1,$2]["C"][$3]; a[$1,$2]["D"] += $4; a[$1,$2]["E"] += ($5>20 ? $5:0) }
END { 
    PROCINFO["sorted_in"] = "compare"
    for ( i in a ) { 
        if ( a[i]["D"] > 1 ) {        # having 절을 위해 추가된 if 문
            split(i, b, SUBSEP) 
            print b[1], b[2], length(a[i]["C"]), a[i]["D"], a[i]["E"] 
        }
    }
}
function compare(i1, v1, i2, v2)
{ 
    if ( v1["E"] < v2["E"] )       return -1
    else if ( v1["E"] == v2["E"] ) return 0
    else                           return 1
}
EOF
x       x       1       3       0
p       z       1       3       22
z       o       2       3       24

UNION

UNION 은 order1 과 order2 의 항목들을 하나로 합칩니다. 이때 공통으로 존재하는 banana 와 strawberry 는 중복이 제거됩니다. 다음은 union 결과에 대해 order1 과 order2 에 존재 여부를 "O", "X" 로 표시합니다.

$ cat order1                       $ cat order2
apple                              melon
banana                             cherry
grape                              banana
strawberry                         strawberry
orange                             mango
                                   tomato

-------------------------------------------------

$ awk -f - <<\EOF order1 order2
BEGIN { 
    while (getline < ARGV[1] > 0) { u[$1]; o1[$1]=1 }    # u[$1] 는 union 을 위한 array
    while (getline < ARGV[2] > 0) { u[$1]; o2[$1]=1 }
} END {
    printf "%12s | %s | %s |\n", " ", ARGV[1], ARGV[2]
    print  "--------------------------------"
    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( i in u )
        printf "%12s |    %s   |    %s   |\n" , 
                  i  , ( o1[i] ? "O" : "X" ) , ( o2[i] ? "O" : "X" )
}
EOF
             | order1 | order2 |
--------------------------------
       apple |    O   |    X   |
      banana |    O   |    O   |
      cherry |    X   |    O   |
       grape |    O   |    X   |
       mango |    X   |    O   |
       melon |    X   |    O   |
      orange |    O   |    X   |
  strawberry |    O   |    O   |
      tomato |    X   |    O   |