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[""] 를 해주어야 합니다.