Builtin Variables

awk 에서 builin 변수는 스크립트를 작성할 때 자주 활용됩니다. 특히 RECORD 와 FIELD 관련 변수에 대해서는 잘 알고 있어야겠습니다. 이 변수들은 함수를 정의할 때 매개변수로 사용할 수 없습니다. 따라서 함수 내에서 변경해 사용하려면 직접 백업과 복구 과정을 거쳐야 합니다.

[ RECORD 와 관련된 변수 ]


NR

awk 명령의 인수로 여러 개의 입력 파일이 사용될 경우 계속해서 누적되어 증가하는 total number of input records 를 말합니다. 메인 입력 스트림에서 읽어들이는 경우에만 적용됩니다.

FNR

현재 입력 스트림으로 사용 중인 파일의 record number 를 말합니다. NR 와 같이 계속 누적되는 것이 아니고 파일별 record number 이므로 입력 스트림이 다음 파일로 변경될 때마다 0 부터 다시 시작합니다. 마찬가지로 메인 입력 스트림에서 읽어들이는 경우에만 적용됩니다.

$ cat file1                  $ cat file2
aaaaa                        ddddd
bbbbb                        eeeee
ccccc                        fffff

$ awk '{ printf "NR: %d, FNR: %d\n", NR, FNR}' file1 file2
NR: 1, FNR: 1
NR: 2, FNR: 2
NR: 3, FNR: 3
NR: 4, FNR: 1     <---- FNR 은 1 부터 다시시작
NR: 5, FNR: 2
NR: 6, FNR: 3

RS

input Record Separator 입니다. awk 는 데이터를 읽어 들일 때 항상 레코드 단위로 읽어 들입니다. 따라서 명령 사이클에 의해 메인 입력 스트림으로부터 읽어 들이던 redirection 을 이용해 직접 읽어들이던 상관없이 항상 RS 가 적용됩니다. 기본값은 newline 이고 문자가 두 개 이상이면 regex 로 해석됩니다. 실행 중 변경하게 되면 기본적으로 다음 레코드를 읽어들일 때부터 적용됩니다.

ORS

Output Record Separator 입니다. print 문을 이용해 레코드를 출력할 때 자동으로 끝에 붙는 값입니다 ( '1' 을 사용하여 출력하는 경우도 포함). 기본값은 newline 이고 output 에 사용되므로 regex 으로 해석되지 않습니다. 변경 즉시 이어지는 print 문부터 바로 적용됩니다. 레코드 끝에 자동으로 붙는 값이라 다음과 같은 특징이 있습니다.

# '111.222' 를 '111X222' 로 만들려고 하지만 자동으로 끝에 X 가 붙는다.
$ echo -n "111.222" | awk '1' RS=. ORS=X
111X222X

# newline 이 레코드 값이 되어 실제 레코드 개수는 3 개가 된다.
$ echo "111.222." | awk '1' RS=. ORS=X
111X222X
X

$ echo -n "111.222." | awk '1' RS=. ORS=X
111X222X
.........................................

$ seq 30 | awk 'ORS = NR % 5 ? FS : RS'
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30

RT

Record Terminator 입니다. 기본적으로 RS 값과 같지만 RS 값으로 regex 이 사용될 경우 실제 매칭 된 값이 RT 에 설정되게 됩니다.

예제: 숫자를 모두 -1 하기 )

$ cat file
2,6*3,4-7
1,2*7,6-2
5,4**3,9-8

$ awk 'BEGIN { RS="[^0-9]+"; ORS="" } 
    { if (NF) print ($0-1) RT }' file 
1,5*2,3-6
0,1*6,5-1
4,3**2,8-7

[ FIELD 와 관련된 변수 ]


NF

읽어들인 레코드의 Number of Fields 값입니다. getline var 와 같이 레코드를 읽어들여 변수에 저장하는 경우를 제외하고 레코드를 읽어들이는 모든 경우에 $1, $2, $3 ... 필드 변수와 함께 설정됩니다.

실행 중에 새로운 필드값을 추가할 경우 NF 값도 변경됩니다.

$ echo 11 22 33 | awk '{ print NF; print; $5=55; print NF; print }'
3
11 22 33
5
11 22 33  55
----------------------------------------------------

$ awk '{ for (i=5; i<=NF; i++) sum += (2 ^ (i-5)) * 4096 * $i } 
       END { print sum / 1024 }' /proc/buddyinfo
4171104

$ free
              total        used        free      shared  buff/cache   available
Mem:       16301392     4491600     4171356      748976     7638436    10723484
Swap:       2097148      513984     1583164

FS

input Field Separator 입니다. 기본값은 space 이지만 awk 에서 특별히 처리하므로 실질적으로 FS="[ \t\n]+" 와 같습니다 (해당 페이지 참조). 문자가 두 개 이상이면 regex 로 해석됩니다. 실행 중 변경하게 되면 기본적으로 다음 레코드를 읽어들일 때부터 적용됩니다.

OFS

Output Field Separator 입니다. 기본값은 space 이고 output 에 사용되므로 regex 으로 해석되지 않습니다. 변경 즉시 이어지는 print 문부터 바로 적용됩니다.

FIELDWIDTHS

해당 페이지 참조

FPAT

해당 페이지 참조

[ 포멧과 관련된 변수 ]


CONVFMT

해당 페이지 참조

OFMT

해당 페이지 참조

PREC

global working precision of arbitrary precision floating-point numbers 을 설정합니다. 기본값은 53 bits 인데 더 높은 수준의 정밀도가 필요할 경우 -M 옵션과 함께 PREC 변수를 설정해 사용할 수 있습니다.

PREC 변수는 -v 옵션으로만 설정할 수 있습니다.

$ awk 'BEGIN { printf("%0.25f\n", 0.1 ) }'
0.1000000000000000055511151

$ awk -M -vPREC=80 'BEGIN { printf("%0.25f\n", 0.1 ) }'
0.1000000000000000000000000
...............................................................

$ awk 'BEGIN { OFMT="%.20f"; print 0.123456789 }' 
0.12345678899999999734

$ awk -M -vPREC=65 'BEGIN { OFMT="%.20f"; print 0.123456789 }' 
0.12345678900000000000
...............................................................

$ awk 'BEGIN { OFMT="%.20f"; print 2.15 - 1.1 }' 
1.04999999999999982236

$ awk -M -vPREC=70 'BEGIN { OFMT="%.20f"; print 2.15 - 1.1 }' 
1.05000000000000000000
...............................................................

$ awk 'BEGIN { printf("%0.25f\n", 0.7 * 1.05 ) }'
0.7349999999999999866773237

$ awk -M -vPREC=70 'BEGIN { printf("%0.25f\n", 0.7 * 1.05 ) }'
0.7350000000000000000003049

$ awk -M -vPREC=80 'BEGIN { printf("%0.25f\n", 0.7 * 1.05 ) }'
0.7349999999999999999999997

$ awk -M -vPREC=90 'BEGIN { printf("%0.25f\n", 0.7 * 1.05 ) }'
0.7350000000000000000000000

bc 명령과 e^12345 계산 속도 비교

$ time BC_LINE_LENGTH=0 bc -l <<< 'e(12345)'
..........833186320363613720444367.94022392548520604058

real    0m53.836s
user    0m53.832s
sys     0m0.004s

$ time awk -M -vPREC=20000 'BEGIN { printf( "%0.20f\n", exp(12345) ) }' 
..........833186320363613720444367.94022392548520604058

real    0m0.018s
user    0m0.013s
sys     0m0.005s

ROUNDMODE

디폴트 값은 "N" ( roundTiesToEven ) 입니다.

ROUNDMODE 를 적용할 땐 -M 옵션을 사용해야 하고 -v 옵션으로만 설정할 수 있습니다.

Rounding Mode IEEE Name ROUNDMODE
Round to nearest, ties to even roundTiesToEven "N" or "n"
Round toward plus Infinity roundTowardPositive "U" or "u"
Round toward negative Infinity roundTowardNegative "D" or "d"
Round toward zero roundTowardZero "Z" or "z"
Round to nearest, ties away from zero roundTiesToAway "A" or "a"
# 디폴트 값은 "N" ( roundTiesToEven )
$ awk '                    
BEGIN {
    x = -4.5
    for (i = 1; i < 10; i++) {
        x += 1.0
        printf("%4.1f => %2.0f\n", x, x)
    }
}'
-3.5 => -4
-2.5 => -2
-1.5 => -2
-0.5 => -0
 0.5 =>  0
 1.5 =>  2
 2.5 =>  2
 3.5 =>  4
 4.5 =>  4
-----------------------------------------

# "U" ( roundTowardPositive )
$ awk -M -v ROUNDMODE=U '
BEGIN {
    x = -4.5
    for (i = 1; i < 10; i++) {
        x += 1.0
        printf("%4.1f => %2.0f\n", x, x)
    }
}' 
-3.5 => -3
-2.5 => -2
-1.5 => -1
-0.5 => -0
 0.5 =>  1
 1.5 =>  2
 2.5 =>  3
 3.5 =>  4
 4.5 =>  5
-----------------------------------------

# "D" ( roundTowardNegative )
$ awk -M -v ROUNDMODE=D '
BEGIN {
    x = -4.5
    for (i = 1; i < 10; i++) {
        x += 1.0
        printf("%4.1f => %2.0f\n", x, x)
    }
}' 
-3.5 => -4
-2.5 => -3
-1.5 => -2
-0.5 => -1
 0.5 =>  0
 1.5 =>  1
 2.5 =>  2
 3.5 =>  3
 4.5 =>  4
----------------------------------------

# "Z" ( roundTowardZero )
$ awk -M -v ROUNDMODE=Z '
BEGIN {
    x = -4.5
    for (i = 1; i < 10; i++) {
        x += 1.0
        printf("%4.1f => %2.0f\n", x, x)
    }
}' 
-3.5 => -3       # 결과적으로 정수 부분만 출력됩니다.
-2.5 => -2
-1.5 => -1
-0.5 => -0
 0.5 =>  0
 1.5 =>  1
 2.5 =>  2
 3.5 =>  3
 4.5 =>  4
-----------------------------------------

# "A" ( roundTiesToAway )
$ awk -M -v ROUNDMODE=A '
BEGIN {
    x = -4.5
    for (i = 1; i < 10; i++) {
        x += 1.0
        printf("%4.1f => %2.0f\n", x, x)
    }
}' 
-3.5 => -4
-2.5 => -3
-1.5 => -2
-0.5 => -1
 0.5 =>  1
 1.5 =>  2
 2.5 =>  3
 3.5 =>  4
 4.5 =>  5

[ 매칭과 관련된 변수 ]


IGNORECASE

변수에 어떤 값이든 설정이 되어 non-null 값을 가지게 되면 (true 가 되면) 모든 스트링 비교와 regex 매칭에 대,소 문자를 구분하지 않습니다. 여기에는 ~ 연산자를 이용한 regex 매칭, gensub(), gsub(), index(), match(), patsplit(), split(), and sub() 함수, RS, FS and FPAT 모두에 적용됩니다.

하지만 array index 와 single-character FS 에는 적용되지 않습니다.

RSTART

match() 함수가 실행될 때 설정되는 변수로 regex 에 의해 매칭 된 스트링의 start index 값이 설정됩니다. 매칭에 실패했을 때는 0 이 설정됩니다.

RLENGTH

match() 함수가 실행될 때 설정되는 변수로 regex 에 의해 매칭 된 스트링의 length 값이 설정됩니다. 매칭에 실패했을 때는 -1 이 설정됩니다.

$ awk 'BEGIN { 
    where = match ( "awk is easy to use", /awk/ )    
    print "where :", where
    print "RSTART :", RSTART
    print "RLENGTH :", RLENGTH
}'
where : 1
RSTART : 1
RLENGTH : 3

$ awk 'BEGIN { 
    where = match ( "awk is easy to use", /easy/ )
    print "where :", where
    print "RSTART :", RSTART
    print "RLENGTH :", RLENGTH
}'
where : 8
RSTART : 8
RLENGTH : 4

$ awk 'BEGIN { 
    where = match ( "awk is easy to use", /XXX/ )
    print "where :", where
    print "RSTART :", RSTART
    print "RLENGTH :", RLENGTH
}'
where : 0
RSTART : 0
RLENGTH : -1

[ 인수와 관련된 변수 ]


ARGC

명령 라인에 사용된 인수의 개수를 나타냅니다.
awk 자신도 포함되므로 실제 사용된 인수 개수 + 1 이 됩니다.

ARGV

명령 라인에 사용된 인수를 값으로 가지는 array 입니다. ARGV[0] 은 awk 명령 자신 입니다.

$ awk 'BEGIN { 
    for (i=0; i<ARGC; i++) 
        printf "ARGV[%d] = %s\n", i, ARGV[i] 
}' FS=, file1 file2

ARGV[0] = awk
ARGV[1] = FS=,
ARGV[2] = file1
ARGV[3] = file2

ARGIND

현재 처리되고 있는 파일의 인수 index 입니다. 항상 ARGV[ARGIND] == FILENAME 가 됩니다.

$ awk 'BEGINFILE { print ARGIND, ARGV[ARGIND] " == " FILENAME }' FS=, file1 FS=: file2
2 file1 == file1
4 file2 == file2

[ Array 관련 변수 ]


SUBSEP

해당 페이지 참조

[ 기타 환경 변수 ]


BINMODE

MS-windows 에서는 unix 와 다르게 개행문자로 \r\n 를 사용하는데요. windows 에서 awk 명령이 실행될 때는 내부적으로 \r\n\n 로 변환하여 읽어들이고 출력시에는 다시 \n\r\n 로 변환하여 출력합니다. 이때 BINary MODE 변수를 이용하면 이렇게 입,출력 때 적용되는 변환을 금지할 수가 있습니다.

변수값으로 1 or r 을 사용하면 입력시 변환이 금지되고, 2 or w 이면 출력시 변환이 금지되며 변수값이 3 or rw or wr 이면 입,출력시 모두 변환이 금지됩니다.

따라서 windows 에서 파일을 unix 파일로 변경하려면 다음과 같이 하면 됩니다.

# 입력 시는 '\r\n' 가 '\n' 로 변환되어 입력되지만
# 출력 시는 BINMODE 설정에 의해 변환이 금지되어 '\n' 로 출력된다.
windows$ gawk -v BINMODE="w" '1' dosfile > unixfile

BINMODE 설정은 -v 옵션으로 하고 코드 실행 중에는 변경할 수 없습니다.

ENVIRON

프로세스의 환경 변수값을 가지고 있는 array 입니다.

$ awk 'BEGIN { 
    for (idx in ENVIRON) 
        printf("ENVIRON[%s] = %s\n", idx, ENVIRON[idx])
}' 
ENVIRON[AWKPATH] = .:/usr/share/awk
ENVIRON[AWKLIBPATH] = /usr/lib/x86_64-linux-gnu/gawk
ENVIRON[SHLVL] = 1
ENVIRON[PWD] = /home/mug896/tmp
ENVIRON[LESSOPEN] = | /usr/bin/lesspipe %s
ENVIRON[SHELL] = /bin/bash
ENVIRON[LESS] = -FRSX
.....
.....

ERRNO

getline, redirection, close() 함수 실행시 오류가 발생하게 되면 관련 스트링 값이 ERRNO 변수에 설정됩니다. 이때 한번 값이 설정되면 자동으로 clear 되지는 않으므로 실질적으로 ERRNO 값은 getline 이 오류를 반환할 때만 의미가 있다고 할 수 있습니다. 따라서 입,출력을 하기 전에 먼저 ERRNO 변수를 clear 한 다음 설정 여부를 체크하는 것도 한 방법이 될 수 있습니다.

한가지 예외적인 것은 명령에 인수로 사용된 입력 파일명이 바뀔 때는 ERRNO 변수가 clear 됩니다. 그러므로 BEGINFILE 블록에서는 ERRNO 변수 설정 여부로 오류를 체크할 수 있습니다.

FILENAME

현재 입력으로 사용되고 있는 파일 이름입니다. STDIN 에서 읽어 들일 경우는 값이 - 가 됩니다. 기본적으로 BEGIN 블록에서는 설정되지 않으나 getline, getline var 를 이용해 메인 입력 스트림으로부터 레코드를 읽기 시작하면 FILENAME 변수도 설정됩니다.

FUNCTAB

awk 에 설정되어 있는 모든 함수 이름을 ( built-in, user-defined, extension functions ) 가지고 있는 array 입니다.

$ awk 'BEGIN { 
    for (idx in FUNCTAB) 
        printf("FUNCTAB[%s] = %s\n", idx, FUNCTAB[idx])
}' 
FUNCTAB[rand] = rand
FUNCTAB[dcgettext] = dcgettext
FUNCTAB[gsub] = gsub
FUNCTAB[match] = match
FUNCTAB[int] = int
FUNCTAB[log] = log
FUNCTAB[sprintf] = sprintf
FUNCTAB[strftime] = strftime
FUNCTAB[systime] = systime
FUNCTAB[length] = length
.....
.....

PROCINFO

현재 실행 중인 awk 프로세스의 정보를 가지고 있는 array 입니다. 가령 PROCINFO["pid"] 는 현재 awk 의 process ID 를, PROCINFO["euid"] 는 effective user ID 를 나타냅니다.

$ 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
.....
.....

TEXTDOMAIN

awk 는 서비스 메시지의 국제화 internationalization (i18n) 를 위한 gettext 기능을 자체적으로 제공합니다.

예제 페이지 보기

https://www.joinc.co.kr/w/Site/PHP/gettext

https://www.joinc.co.kr/w/Site/Python/GetText

Quiz

wc 명령은 파일에 포함된 lines, words, chars 의 개수를 출력해 주는데요.
동일한 기능을 awk 로 구현하는 것입니다.

$ wc /etc/hosts
  9  25 224 /etc/hosts

$ gawk '{ 
        chars += length($0) + 1       # newline 문자도 추가해야 하므로 + 1
        words += NF
} END { print NR, words, chars }
' /etc/hosts

9 25 224

2 .

wc 명령은 words 개수를 셀 때 단순히 whitespace 문자를 이용해 구분하는데요.
알파벳으로만 이루어진 단어를 세려면 어떻게 할까요?

$ gawk -F '[^A-Za-z]+' '
{ for(i = 1; i <= NF; i++) word[$i] }
END { 
    delete word[""]
    print length(word)
}' /etc/hosts

19
--------------------------------

foo@bar@zoo  는 3 개의 필드가 되지만
@foo@bar@zoo 일 경우는 공백 필드가 포함되어 4 개의 필드가 되므로
delete word[""] 를 해주어야 합니다.