Data Types

awk 에서는 number 와 string 구분이 있습니다. 하지만 프로그래밍 언어에서처럼 엄격한 것은 아니고 산술연산을 하는 곳에서는 스트링 "123" 이 숫자로 취급되고, 스트링 연산을 하는 곳에서는 숫자 123 이 스트링으로 취급됩니다.

$ awk 'BEGIN{ print ( 12 + 3 ) }'
15

# 스트링과 숫자를 혼용해도 된다.
$ awk 'BEGIN{ print ( "12" + 3 ) }'
15
# 모두 스트링이어도 상관없다.
# '+' 로 산술연산을 하는 context 이므로 자동으로 숫자로 변환된다.
$ awk 'BEGIN{ print ( "12" + "3" ) }'
15

# 12345 는 숫자이지만 스트링 연산을 할 수 있다.
# substr() 함수로 스트링 연산을 하는 context 이므로 숫자가 자동으로 스트링으로 변환된다.
$ awk 'BEGIN{ print ( substr(12345, 1, 2) ) }'
12
$ awk 'BEGIN{ print ( substr(12345, 1, 2) + 3) }'
15

String

awk 에서는 코드를 작성할 때 single quotes 이 사용되지 않습니다. 스트링을 만들 때는 double quotes 을 사용합니다. 스트링은 단지 프린트문에만 사용되는 것이 아니고 파일을 open 할 때 사용하는 파일명, 외부 명령을 실행시킬 때 작성하는 명령 라인에 모두 스트링이 사용됩니다. double quotes 이므로 기본적으로 escape sequence 가 처리됩니다.

$ awk -f - <<\EOF
BEGIN {
    print "hello"
    print 'world'      # awk 에서 single quotes 을 사용하여 오류 발생
}
EOF

awk: -:3:     print 'world'
awk: -:3:           ^ invalid char ''' in expression
awk: -:3:     print 'world'
awk: -:3:           ^ syntax error

double quotes 이므로 escape sequence 가 처리됩니다.

# $1 == "111\222" 에서 '\' 가 escape 문자로 처리되어 매칭이 되지 않는다.
$ echo '111\222,333\444' | awk -F, '$1 == "111\222" { print $1 }' 

# '\' 문자를 '\\' 로 escape 하면 정상적으로 매칭이 된다.
$ echo '111\222,333\444' | awk -F, '$1 == "111\\222" { print $1 }' 
111\222

스트링 값은 산술연산에서는 0 과 같습니다. 하지만 "123mb" 와같이 스트링의 앞부분에 숫자가 있을 경우는 산술연산을 할 수 있습니다.

$ awk 'BEGIN { aa = "xxx"; bb = "y123"; cc = aa + bb; print cc }' 
0

$ awk 'BEGIN { aa = "12.5m"; bb = "3.2m"; cc = aa + bb; print cc }' 
15.7

........................................

# top 명령을 이용하여 chrome 프로세스의 메모리 사용량 구하기
# RES 는 Resident Memory Size, SHR 는 다른 프로세스와 Shared Memory Size 이므로
# chrome 프로세스만 사용하는 메모리는 RES - SHR
$ top -bn1 | awk 'NR > 7 && $12 == "chrome" { res+=$6-$7 } END { print res "m" }' 
3717.7m

String Concatenation

awk 에서 공백은 변수에 값을 대입할 때건 프린트할 때건 상관없이 無 에 해당합니다. ( 따라서 공백은 concatenation 효과가 있습니다. ) 그러므로 공백을 두기 위해서는 직접 " " 스트링을 사용해야 합니다.

$ awk 'BEGIN{ AA=11      22  33; print AA }' 
112233

$ awk 'BEGIN{ AA=11 22 33; print "Number is: "        AA }' 
Number is: 112233

$ awk 'BEGIN{ AA=11 " " 22 "    " 33; print AA }' 
11 22    33

# ( two three ) 가 "23" 이 되고 이후에 + 4 가 되어 27 이된다.
$ awk 'BEGIN{ two = 2; three = 3; print ( two three ) + 4 }'
27

Concatenation 은 결과가 스트링이 됩니다.

# 숫자 11 과 1 을 연결한 결과는 "111" 스트링이 되므로 스트링 비교를 하게 되어 결과값이 1 이 된다.
$ awk 'BEGIN{ print (12 > 11 1) }' 
1
$ awk 'BEGIN{ print (12 > 111) }' 
0

+= 연산자는 산술 연산자로 string concatenation 에 사용할 수 없습니다. 대입 연산에서 string concatenation 을 하려면 오른쪽과 같이 합니다.

$ awk 'BEGIN {                         $ awk 'BEGIN { 
    res = "foo"                            res = "foo"
    print res                              print res
    res += "bar"                           res = res "bar"
    print res                              print res
}'                                     }'
foo                                    foo
0                                      foobar
------------------------------------------------------------

$ awk 'BEGIN {                         $ awk 'BEGIN { 
    res = sprintf( "%s", "foo" )           res = sprintf( "%s", "foo" )
    print res                              print res
    res += sprintf( "%s", "bar" )          res = res sprintf( "%s", "bar" )
    print res                              # res = sprintf( res "%s", "bar" )   # OK
}'                                         print res
foo                                    }'
0                                      foo
                                       foobar

숫자와 스트링의 등호, 부등호 사용

awk 에서는 숫자 123 와 스트링 "123" 이 문맥에 맞추어서 자동으로 타입이 변경되지만 한가지 주의할 점은 등호, 부등호를 사용하는 곳에서는 두 피연산자 중에 하나가 스트링이면 lexicographically 스트링 비교를 한다는 것입니다.

숫자가 스트링으로 변경될 때는 printf "%s", 숫자 가 사용됩니다.

# printf "%s", 123.0  값은 "123" 이되므로 참이된다.
$ awk 'BEGIN { print ( 123.0 == "123" ) }' 
1

$ awk 'BEGIN{ print (12 > 111) }' 
0

# 두 피연산자 중에 하나가 스트링이면 스트링 비교를 하므로 12 가 크다고 나온다.
$ awk 'BEGIN{ print (12 > "111") }' 
1

# 마찬가지로 substr 스트링 함수의 리턴값은 스트링이다.
$ awk 'BEGIN{ print (substr(12345, 1, 2) > 111) }'
1

Number

숫자에는 8 진수, 16 진수, scientific notation 모두 사용할 수 있습니다. 한가지 주의할 점은 입력 스트림이나 -v 옵션에 의해 외부로부터 값이 전달될 경우는 숫자 데이터 라도 기본적으로 모두 스트링입니다. 10 진수의 경우는 연산시 자동으로 숫자로 변환이 되어 처리되지만 8 진수, 16 진수의 경우는 그렇지 않으므로 따로 strtonum 함수 를 사용해야 합니다.

11       (10 진수)
011      (8  진수, 10 진수로 9)
0x11     (16 진수, 10 진수로 17)

+3.14    (+, - 기호의 사용)
-3.14

105
1.05e+2  (scientific notation)
1050e-1

스크립트 외부에서 값이 전달될 경우는 숫자 데이터 라도 기본적으로 스트링입니다.

# 스크립트 내에서 직접 8, 16 진수를 사용할 때는 정상적으로 연산이 된다.
$ awk 'BEGIN { print 011; print 011 + 1 }'
9
10

$ awk 'BEGIN { print 0x11; print 0x11 + 1 }'
17
18

# 하지만 외부에서 값이 전달될 때는 스트링이므로 정상적으로 연산이 되지 않는다.
$ echo 011 | awk '{ print $1; print $1 + 1 }'
011
12

$ echo 0x11 | awk '{ print $1; print $1 + 1 }'
0x11
1

# 이때는 연산전에 strtonum 함수를 사용해야 합니다.
$ echo 011 0x11 | awk '{ print strtonum($1) + 1; print strtonum($2) + 1 }'
10
18

# 10 진수는 스트링이 라도 정상적으로 연산이 됩니다.
$ echo +10.1 -10.2 | awk '{ print $1 + 1; print $2 + 1 }'
11.1
-9.2

$ awk 'BEGIN { print 1.05e+2 }'            # 스크립트 내에서 1.05e+2 는 숫자
105
$ echo 1.05e+2 | awk '{ print $1 }'        # 여기서 $1 값은 스트링
1.05e+2
$ echo 1.05e+2 | awk '{ print $1 + 1 }'    # '+' 연산자에 의해 숫자로 됨
106

awk 는 기본적으로 숫자를 double floating point 로 처리합니다 ( all numbers in awk are floating point ). 그러므로 정수값은 53 bits 까지만 정확도가 있습니다 [참조]. 그이상은 -M 옵션을 이용하여 arbitrary-precision 연산을 해야 합니다.

# 32 bits 연산은 오류가 없다.
$ awk 'BEGIN { print 0xaabbccdd + 1 }' 
2864434398

$ bc <<< "ibase=16; AABBCCDD + 1"
2864434398     # 정확한 값
.......................................................

# 64 bits 연산은 연산 결과가 정확하지 않다.
$ awk 'BEGIN { print 0x66778899aabbccdd + 1 }' 
7383520307673025536

$ bc <<< "ibase=16; 66778899AABBCCDD + 1"
7383520307673025758    # 정확한 값

# 다음과 같이 -M 옵션을 사용해야 합니다.
$ awk -M 'BEGIN { print 0x66778899aabbccdd + 1 }'
7383520307673025758

소수점 연산시 높은 수준의 정밀도가 필요할 경우 -M 옵션과 PREC 변수를 사용할 수 있습니다. ( 참조 )

Type Conversion

스트링을 숫자로 만들려면 +0 을 해주면 됩니다.

$ awk 'BEGIN{ print (12 > "111") }'
1

$ awk 'BEGIN{ print (12 > "111"+0) }' 
0

$ awk 'BEGIN{ print (substr(12345, 1, 2)+0 > 111) }'
0

숫자를 스트링으로 만들려면 "" 로 concatenation 을 하면 됩니다.

$ awk 'BEGIN{ print (12 > 111) }' 
0

$ awk 'BEGIN{ print (12 > 111"") }' 
1

awk 는 eval 함수가 없기 때문에 산술식을 스트링으로 만들어서 결과를 구할 수 없습니다.

$ awk 'BEGIN { str = "10 + 20"; str = str " + 30"; print str; print str + 0 }' 
10 + 20 + 30
10           # 결과가 60 이되지 않는다.

$ awk 'BEGIN { str = "10 + 20"; str = str " + 30"; print eval(str) }
function eval(arg,     res) {
    "awk \047BEGIN{ print " arg " }\047" | getline res     # \047 은 single quotes
    return res
}' 
60

$ seq -f '4/%g' 1 2 9999 | paste -s -d '-+' |
    awk '{ "awk \"BEGIN{ print " $0 " }\"" | getline; print }'
3.14139

$ seq -f '4/%g' 1 2 9999 | paste -s -d '-+' | perl -lne "print eval"    # perl 언어
3.14139265359179

소수를 print 문으로 출력할 때는 OFMT 포멧 값이 적용되고 숫자에서 스트링으로 변경될 때는 CONVFMT 포멧 값이 적용됩니다. 기본값은 모두 %.6g 입니다.

입력 스트림으로부터 레코드를 읽어 들일 때는 숫자 데이터라도 기본적으로 스트링입니다. 스트링 데이터는 있는 그대로 프린트되기 때문에 포멧 값이 적용되지 않습니다. 하지만 산술연산을 하게 되면 결과가 숫자가 되고 숫자는 print 문에서 OFMT 적용을 받고 다시 스트링으로 변환시 CONVFMT 값의 적용을 받게됩니다.

# OFMT, CONVFMT 기본값은 모두 '%.6g' 이다.
$ awk 'BEGIN{ print OFMT; print CONVFMT }' 
%.6g
%.6g

$ echo "1.23456789" | awk '{ 
    OFMT = "%.2f"    
    CONVFMT = "%.4f"    
    print $1        # $1 == "1.23456789"  입력값은 기본적으로 스트링
    a = $1 + 0      # a == 1.23456789     산술연산에 의해 숫자로 변경됨
    print a         # a 는 숫자이므로 OFMT 값의 적용을 받음
    b = a ""        # b == "1.2346"  b 는 CONVFMT 값에 따라 스트링이 됨
    print b         # 스트링을 프린트할 때는 OFMT 의 적용을 받지 않음
}' 
1.23456789
1.23
1.2346

위에서 a = $1 + 0 에 의해 숫자가 된 aprint length(a) 해보면 값이 10 이 아니라 6 로 나오는 것을 볼 수 있는데요. 이것은 a 값이 CONVFMT 에따라 먼저 스트링으로 변환되기 때문입니다. 이것은 length 함수뿐만 아니라 다른 스트링 함수도 마찬가지입니다.

위에서 출력 결과를 유심히 보신 분께서는 마지막 값이 1.2346 으로 끝자리 숫자가 반올림 되어있는 것을 알 수 있을 텐데요. 이것은 awk 가 기본적으로 roundTiesToEven 라운드모드 를 사용하고 있기 때문입니다. 다음과 같이 roundTowardZero 라운드모드를 사용하면 값을 1.2345 로 출력할 수 있습니다.

# ROUNDMODE=z : roundTowardZero 를 사용
$ echo "1.23456789" | awk -M -v ROUNDMODE=z '{ 
    OFMT = "%.2f"    
    CONVFMT = "%.4f"    
    print $1
    a = $1 + 0
    print a 
    b = a ""
    print b
}' 
1.23456789
1.23
1.2345       <----- 1.2345 로 출력됨

Floating point numbers

컴퓨터에서 소수점 연산은 크게 fixed point 연산과 floating point 연산으로 나눌 수 있습니다. fixed point 연산은 소수점 자리가 정해져 있는 것으로 내부적으로 정수를 이용해 연산을 합니다. 정수를 사용하기 때문에 결과가 정확해서 돈 계산에 문제없이 사용할 수 있습니다. 예를 들어 데이터베이스에서 볼 수 있는 NUMERIC, DECIMAL 타입이 여기에 해당합니다.

floating point 연산은 2 진수를 사용하는 binary floating point 연산과 10 진수를 사용하는 decimal floating point 연산이 있는데 현재 대부분의 CPU 에 들어있는 것은 binary floating point 연산기 입니다. binary floating point 연산은 2 진수를 사용하기 때문에 속도가 빠르고 과학 계산에는 좋지만 정확히 10 진수를 표현하지 못하는 단점이 있습니다 ( 예를 들어 0.1 은 정확히 2 진수로 표현이 안됩니다 ). decimal floating point 는 내부적으로 10 진수를 이용해 처리하므로 ( Binary Coded Decimal, Densely Packed Deciaml, Binary Integer Decimal ) 2 진수 변환에 따른 rounding errors 가 없습니다. 보통 소프트웨어를 이용해 처리하는데 IBM 에서 나오는 일부 CPU 중에는 decimal floating point 연산기가 들어있는 경우도 있습니다.

binary floating point 연산은 정수 연산과 다르게 비트 값들을 부호비트(sign), 지수부분(exponent), 소수부분(fraction) 으로 나누어 처리하는 IEEE 754 라는 포멧을 사용합니다. 지수부가 있기 때문에 같은 비트수를 사용하는 정수값 보다 dynamic range ( 표현할수 있는 최대값 ~ 최소값 범위 ) 가 크고 precision ( 정밀도: 인접한 두 수 사이의 갭이 작다 ) 가 높게 됩니다. 연산 결과가 정확히 10 진수로 표현이 안되는 경우가 있기 때문에 값을 비교하거나 출력할 때는 주의해야 합니다.

이것은 awk 에만 해당되는 것이 아니고 하드웨어 float 을 사용하는 모든 언어에 해당됩니다.
Javascript 의 숫자형 메뉴 에서 부정확한 계산 참조
Python Floating Point Arithmetic: Issues and Limitations 참조

# 0.1 + 0.1 + 0.1 연산 결과는 정확히 0.3 이 아니다.
$ awk 'BEGIN { n = 0.1 + 0.1 + 0.1; printf "%.20f\n", n }' 
0.30000000000000004441

# 따라서 다음 equal 연산의 경우 "yes" 가 프린트되지 않는다.
$ awk 'BEGIN { n = 0.1 + 0.1 + 0.1; if ( n == 0.3 ) print "yes" }' 
$

# CONVFMT 을 이용해 float 을 스트링으로 변환
$ awk 'BEGIN { n = 0.1 + 0.1 + 0.1; n2 = n ""; print n2 }' 
0.3

# 스트링 비교를 하게 되어 "yes" 가 프린트된다.
$ awk 'BEGIN { n = 0.1 + 0.1 + 0.1; if ( n "" == 0.3 ) print "yes" }' 
yes
---------------------------------------------------------------------

$ awk 'BEGIN { n = 2.15 - 1.1; printf "%.20f\n", n }' 
1.04999999999999982236

$ awk 'BEGIN { n = 2.15 - 1.1; if ( n == 1.05 ) print "yes" }'
$

$ awk 'BEGIN { n = 2.15 - 1.1; n2 = n ""; print n2 }' 
1.05

$ awk 'BEGIN { n = 2.15 - 1.1; if ( n "" == 1.05 ) print "yes" }' 
yes
--------------------------------------------------------------------

$ gawk 'BEGIN {
  for (d = 1; d <= 5; d += 1)
      print d
}'
1      # 5번 반복
2
3
4
5

$ gawk 'BEGIN {
  for (d = 1.1; d <= 1.5; d += 0.1)
      print d
}'
1.1    # 5번 반복이 안된다.
1.2
1.3
1.4

출력과 관련해서는 숫자 1.356.35 를 소수 첫째 자리까지만 출력하면 반올림이 되므로 결과가 각각 1.46.4 가 돼야 하는데요. 하지만 출력을 해보면 6.35 에서는 반올림이 일어나지 않는데 이것은 6.35 는 실제 6.3499999... 가 되기 때문입니다. ( 다른 언어도 마찬가지입니다.)

$ awk 'BEGIN { printf "%.20f\n", 1.35 }' 
1.35000000000000008882

$ awk 'BEGIN { printf "%.20f\n", 6.35 }'        # 6.35 는 실제 6.3499999 ...
6.34999999999999964473

$ awk 'BEGIN { printf "%.1f\n", 1.35 }' 
1.4

$ awk 'BEGIN { printf "%.1f\n", 6.35 }'         # 따라서 반올림이 안된다 
6.3

$ python3 -c 'print("%.1f" % 1.35)'
1.4

$ python3 -c 'print("%.1f" % 6.35)'             # python 도 마찬가지
6.3

Errors Accumulate

이와 같은 binary floating point 의 10 진수 연산에 대한 오차는 한번의 연산에서는 큰 문제가 되지 않겠지만 연산이 누적될 경우 뜻하지 않은 결과가 나올 수 있습니다. 다음은 pi 값을 구하는 series 인데요. x 값을 구하는 연산에서 오차가 누적되어 division by zero 로 프로세스가 종료되고 있습니다.

$ awk 'BEGIN {              
    x = 1 / sqrt(3)
    n = 6
    for (i = 1; i < 30; i++) {
        n = n * 2
        x = (sqrt(x * x + 1) - 1) / x
        printf "%.15f\n", n * x
    }
}' 
3.215390309173475
3.159659942097510
3.146086215131467
3.142714599645573
3.141873049979866
3.141662747055068
. . . .
. . . .
3.141406154737622
3.140543492401100
3.140006864690968
3.134945375658852
3.140006864690968
3.224515243534819
2.791117213058638
0.000000000000000
awk: cmd. line:6: fatal: division by zero attempted
----------------------------------------------------

# -M -v PREC 옵션을 사용하면 정확한 값을 출력할 수 있습니다.
$ awk -M -v PREC=200 'BEGIN {
    x = 1 / sqrt(3)
    n = 6
    for (i = 1; i < 30; i++) {
        n = n * 2
        x = (sqrt(x * x + 1) - 1) / x
        printf "%.15f\n", n * x
    }
}' 
3.215390309173472
3.159659942097500
3.146086215131435
3.142714599645368
3.141873049979824
3.141662747056849
. . . .
. . . .
3.141592653590054
3.141592653589859
3.141592653589810
3.141592653589797
3.141592653589794
3.141592653589793   # 정확한 값이 출력된다.
3.141592653589793
3.141592653589793
3.141592653589793
3.141592653589793   
------------------------------------------

$ bc -l <<< "scale=20; 4 * a(1)"     # 실제 PI 값
3.14159265358979323844

IEEE 754 포멧이 처리되는 방식은 서현우 님이 작성하신 실수값표현법 문서를 참조하세요