awk - shell programming - sunyzero

Download Report

Transcript awk - shell programming - sunyzero

awk / shell programming
김선영
가메 출판사
http://www.kame.co.kr
인사이트
http://blog.insightbook.co.kr
저자블로그
http://sunyzero.tistory.com
sunyzero@gmail(dot)com
버 전: 2014-12-22
awk
syntax, practice
What is awk?

설계자인 Aho. Weinbrger, Kernighan의 머릿글자에서 유래


/ɔːk/ 라고 읽는다.
공식적으로 "패턴 검색과 처리언어"라고 정의

독자적인 처리 문법과 언어 구성을 갖추고 있음

expr, grep, sed의 모든 기능을 포함하고 있음

grep, sed로 처리가 불가능하다면 awk를 사용

gawk (GNU awk) - 확장된 기능 제공

이 강의는 gawk를 기준으로 작성되었으나
대부분의 표현식은 POSIX awk에서도 잘 작동합니다.
awk command

awk의 실행 방법
awk [-F 필드구분자] '스크립트코드' <처리할파일>...
awk [-F 필드구분자] -f <awk스크립트파일> <처리할파일>...

awk script는 single quotes로 묶는다. Why?

GNU awk(gawk)에서 POSIX로 작동하려면 --posix 옵션을 준다.
실행 명령어만 알아도 절반은
먹고 들어가는 법이다!
awk CLI

data file : account.txt
# UNIX ACCOUNT : No.00704
sunyzero:Sunyoung Kim:1600:AA-1342R:010-2007-8080:19991222
ppan:Peter Pan:1200:CC-0100R:010-3593-1277:19921202
clyde47:John Smith:650:CC-1106R:010-6532-1004:20000123
banaba:John Kennis:1100:CA-0971R:010-4321-1234:20000123
jamtaeng:Jambo Park:880:AD-1000R:010-6420-3578:19970802
$ awk -F : '{print $1, $4;}' account.txt
# UNIX ACCOUNT
sunyzero AA-1342R
ppan CC-0100R
clyde47 CC-1106R
banaba CA-0971R
jamtaeng AD-1000R
-F : # field separator
$1, $4 # fields
print 명령은 자동 개행!
awk script file

data file : account.txt
$ cat pr_1a4.awk
{ print $1, $4 }
comma의 역할은? print $1 $4로 해보자
$ awk -F : -f pr_1a4.awk account.txt
# UNIX ACCOUNT
sunyzero AA-1342R
ppan CC-0100R
clyde47 CC-1106R
banaba CA-0971R
jamtaeng AD-1000R
-f file # script filename
awk script block

조건/패턴이 true일 때 { command; ... } 실행
[ condition or pattern ] { command; ... }
$ awk -F : 'NR>1 { printf "%s(%s)\n",$1,$2 }' account.txt
sunyzero(Sunyoung Kim)
ppan(Peter Pan)
clyde47(John Smith)
banaba(John Kennis)
jamtaeng(Jambo Park)
NR>1 # Line number
printf # formating
printf

C-style formatting
%[flag][x][.y]c
flag
플래그 지정: -, 0, ' 등을 지정할 수 있음.
x
최소 필드 너비(min. field width)를 지정
.y
c
정밀도 지정
변환형 지정: s,d,f,s,c ... 등등을 지정할 수 있음.
설명
flag
-
왼쪽으로 정렬
0
숫자 출력시 빈공백 부분을 0으로 채움
'
숫자 출력시 천(thousand)단위로 점을 찍을때 사용
홀 따옴표에 주의
printf : single quotes

홀 따옴표 사용 주의
결론 : escape가 필요하다.
하지만 %\'10d으로 하면 single quotes로 인해
escape전과 다를 바가 없다.
printf : single quotes (con't)

escape를 위한 블록 분리
홀 따옴표에 주의
(쌍 따옴표 아님)

1000단위 출력이 실패하는 경우

LC_LOCALE이 설정된 경우(or LANG의 영향)
printf: conversion (C-style)
변환형
설명
d, i
10진수 정수형
u
10진수 양의 정수형
o
8진수 양의 정수형
x, X
16진수 양의 정수형
f, F
실수형
e, E
실수형 (scientific notation 으로 지정된) e.g. 1.23456e+05
c
숫자인 경우 ASCII코드로 변환하여 문자 1개 출력
문자열인 경우 맨앞 한개의 문자만 출력
s
문자열 출력
printf: practice
숫자 출력 예제
printf: practice (con't)
문자열 출력 예제
awk field

field 선택
$ awk -F: 'NR>1 {var=$2;$2=$3;$3=var; print}' account.txt
sunyzero 1600 Sunyoung Kim AA-1342R 010-2007-8080 19991222
ppan 1200 Peter Pan CC-0100R 010-3593-1277 19921202
clyde47 650 John Smith CC-1106R 010-6532-1004 20000123
banaba 1100 John Kennis CA-0971R 010-4321-1234 20000123
jamtaeng 880 Jambo Park AD-1000R 010-6420-3578 19970802
2th, 3th 필드의 스왑

awk variable

string, integer, real number 모두 지원
Pratice

조건 적용하여 출력해보자.
$ awk 'NR>1 {printf "%d:%s\n",NR-1,$1}' account.txt
1:sunyzero:Sunyoung
2:ppan:Peter
3:clyde47:John
4:banaba:John
5:jamtaeng:Jambo
$ awk -F: 'NR>1 {printf "%d:%s\n",NR-1,$1}' account.txt
1:sunyzero
2:ppan
3:clyde47
4:banaba
5:jamtaeng
awk pattern

패턴 형식
pattern { .... }
/regular expression/
relational expression
pattern && pattern
pattern || pattern
pattern ? pattern : pattern
(pattern)
! pattern
pattern1, pattern2
range 패턴은 데이터 파일을 처리하는데 유용하다.
pattern: REGEX

/REGEX/ 를 이용하여 조건 사용
$ awk '/C[A-Z]-[0-9A-Z]+/ {print}' account.txt
ppan:Peter Pan:1200:CC-0100R:010-3593-1277:19921202
clyde47:John Smith:650:CC-1106R:010-6532-1004:20000123
banaba:John Kennis:1100:CA-0971R:010-4321-1234:20000123
$ awk -F: '$4 ~ /C[A-Z]-[0-9A-Z]+/ {print}' account.txt
ppan:Peter Pan:1200:CC-0100R:010-3593-1277:19921202
clyde47:John Smith:650:CC-1106R:010-6532-1004:20000123
banaba:John Kennis:1100:CA-0971R:010-4321-1234:20000123
operator
Operator
Description
Example
equality operator, not equal to
$1=="Fred", $4!=NF
less than
$1<$3
less than or equal to
$1<=$1+NR
>
greater than
$10>100
?:
Condition operator (equal to C)
~
contain regular expressions (match)
$4~/LOC/ , $9~/[A-Z]/
!-
Dose not contain regular expression
$9!~/..[a-z]/
==, !=
<
<==
operator (con't)

blank line을 제외하고 첫번째 필드가 100보다 큰 경우
$ awk '($0 !~ /^$/) && ($1 > 100) {print $0}' datafile.txt

범위 연산을 사용하는 경우
$ awk '(NR == 3),(NR == 10)' /etc/passwd
operator (con't)

다양한 패턴 조합이 가능하다.
/^[^#]+/ { print }
/^[^#]+/ || $0 !~ /^$/ { print }
/^#start data/,/^#end data/ { print }
(NR == 5),/^#end of file/ { print }

데이터 파일 내부에 start, end를 명시하는 comment line이 있다면 패
턴으로 걸러내서 읽을 수 있다.
BEGIN, END

BEGIN

연산이 일어나기 에 수행


initialization, pre-processing 작업
END

연산이 끝난 뒤에 수행

주로 출력을 수행한다.
BEGIN { action }
pattern { action }
...
END { action }
BEGIN, END (con't)

BEGIN 사용 방식
$ awk 'BEGIN {FS=":"} (NR == 3),(NR == 10)' /etc/passwd

END 사용 방식
$ awk 'END { print NR }' /etc/passwd
34
$ wc -l /etc/passwd
34 /etc/passwd
BEGIN, END (con't)

BEGIN, END를 이용한 계산
$ cat pi.awk
BEGIN { print "Pi (numerical integration method)" }
{ n_steps = $1; sz_step = 1.0/n_steps; sum=0.0; }
END {
for (i=0; i<n_steps; i++) {
atan2() - arc-tangent in radians angle
x = (i+0.5) * sz_step;
sum += 4.0/(1.0 + x*x);
}
printf("n_steps = %d, sz_step = %.8f\n", n_steps, sz_step);
printf("pi = %.8f, 4*atan(1) = %.8f\n", sz_step*sum, atan2(1,1)*4);
}
$ time echo 2000000 | awk -f pi.awk
Pi (numerical integration method)
n_steps = 2000000, sz_step = 0.00000050
pi = 3.14159265, 4*atan(1) = 3.14159265
real
user
sys
0m0.546s
0m0.544s
0m0.001s
built-in var.
변수명
의미
ENVIRON
환경 변수들을 모아둔 관계형 배열
ARGC
커맨드 라인 아규먼트의 갯수
ARGV
커맨드 라인 아규먼트의 어레이
FILENAME
문자열 = 현재 입력 파일명
FNR
현재 file의 현재 레코드 번호 (각 입력 파일에서 1부터 시작한다.)
FS
입력 필드 seperator 문자 (기본값 = 공백)
NF
현재 레코드의 필드 번호
NR
현재의 레코드 번호 (모든 입력 파일을 통틀어)
FIELDWIDTHS
FS대신에 사용할 고정 필드 크기 (구분자가 없는 데이터 처리시 사용한다.)
OFMT
숫자를 위한 출력 포멧 (기본값 = %.6g)
OFS
출력 필드 seperator (기본값 = 스페이스)
ORS
출력 레코드 seperator (기본값 = 개행문자)
RS
입력 레코드 seperator (기본값 = 개행문자)
SUBSEP
배열에 쓰는 첨자 구획 문자
RSTART
매칭 연산(match function)을 만족하는 문자열의 맨 앞부분
RLENGTH
매칭 연산(match function)을 만족하는 문자열의 길이
IGNORECASE
대소문자 무시 *
* IGNORECASE 관련 함수 : gensub(), gsub(), index(), match(), split(), sub()
flow control
Flow control statement
Desc.
if (conditional)
{statement_list1}
[else
{statement_list2}]
C-style if
while (conditional)
{statement_list}
C-style while
for (init;condition;ctrl)
{statement_list}
C-style for loop
break
루프를 종료시키고 다음 statement로 넘어가게 한다.
continue
현재 위치에서 루프의 다음 iteration으로 넘어가게 한다.
next
현재 input line에 대해서 남아있는 pattern을 skip
exit
남아있는 input을 모두 skip한다.
만일 END pattern이 있다면 처리한 후 awk를 종료시킨다.
print , var control
print control statement
print [expr_list] [>file]
expr_list에 있는 expression을
stdout이나 특정 파일로 리다이렉트 한다.
file은 file descriptor를 사용한다.
printf format [,expr_list] [>file]
C언어의 printf문과 동일
포맷된 형식의 expression을 보여준다.
출력은 위와 같다.
sprintf (format [,expression])
C언어의 sprintf처럼 버퍼에 리턴하기만 하고, 출력하
지 않는다. 변수에 저장할 때 사용한다.
assignment statement
variable=awk_expr
awk expr에서 나온 expression을 variable에 할당한다.
printf의 default precision은 6자리이다.
userdefined fd는 3-19까지 사용 가능하다.
Practice

합산 계산, 새로운 필드 할당
$ cat score.txt
#name:math1:math2:datastructure:algorithm
Jack,78,45,68,49
Mick,77,32,86,67
Fred,95,55,85,62
Kim,88,42,85,60
$ awk -F, 'NR>1 { $6=$2+$3+$4+$5; print $0 }' score.txt
Jack 78 45 68 49 240
Mick 77 32 86 67 262
6th field는 새로 할당된 필드다.
Fred 95 55 85 62 297
Kim 88 42 85 60 275
$ awk -F, '$0 !~ /^#.*/ { $6=$2+$3+$4+$5; print $0 }' score.txt
...생략...
Practice (con't)

2번째 과목의 평균 계산
$ cat avg.awk
$0 !~ /^#.*/ { sum += $3 }
END { printf "math2 avg = %.2f\n", sum/(NR-1) }
$ awk -F, -f avg.awk score.txt
math2 avg = 43.50
Practice

System daemon의 Minor pagefault, RSS 계산
$ cat chk_sysdaemon.sh
#!/bin/bash
ps -eo user,uid,pid,ppid,sz,rss,vsz,min_flt,maj_flt,cmd |
awk '
BEGIN { n_ps=0; n_tot_minflt=0; n_tot_rss=0; }
($1 == "root") && ($4 == 1) { n_ps++; n_tot_minflt+=$8; n_tot_rss+=$6; }
END {
printf "-------- Daemon process monitor --------\n";
printf "Processes\tMin.PageFault\tTotalRSS\n";
printf "%9d \t %12d \t %7d\n", n_ps, n_tot_minflt, n_tot_rss;
}'
function

length(x)는 x의 길이를 리턴한다.
$ awk -F: '(length($2)<11 && NR>1) {printf "%d:%s,%s\n",NR-1,$1,$2}' \
account.txt
2:ppan,Peter Pan
3:clyde47,John Smith
5:jamtaeng,Jambo Park
function: string
function name
description
gsub(r,s[,t])
정규표현식 r과 매치되는 문자열 t를 s로 대치한다.
(t를 지정하지 않으면 $0 ; 문자열 자체가 변경되니 조심할 것!)
* return value : 대치에 성공한 패턴 개수 (양수)
index(str1,str2)
Returns the starting position of str2 in str1.
If str2 is not present in str1, then 0 is returned
length(str)
Returns the length of string str
match(s,r)
문자열 s에서 정규표현식 r과 매치되는 부분의 위치를 넘겨준다
split(str,array[,sep])
구분자 sep를 기준으로(default는 space) 문자열 str을 배열로 변환
sprintf(fmt,awk_exp)
Returns the values of awk_exp formatted as defined by fmt
sub(r,s[,t])
정규표현식 r과 매치되는 문자열 t를 s로 대치한다.
gsub와 다른점은 t에서 r과 매치되는 부분이 여러 개라 할지라도 처음
한 개만 대치한다는 것이다.
* return value: 대치에 성공한 패턴 개수 (0 or 1)
* gsub (global sub)
* gawk는 gsub, sub의 general version인 gensub를 지원한다.
* gawk는 strtonum()으로 base 진수로 표기된 문자열을 숫자로 변환할 수 있다.
function: string
function name
description
Returns a substring of the string str starting at positio
substr(str,start,length) n start for length characters
ex) substr("believe", 3, 5) == "lieve"
tolower(str)
문자열 str을 모두 소문자로 바꾼다
toupper(str)
문자열 str을 모두 대문자로 바꾼다
function: math
function
description (angle은 모두 radian으로 쓴다)
int(x)
x의 소수점 이하 값을 버린다.
rand()
0에서 1사이의 random variable을 취한다.
srand(x)
rand을 위해 x를 새로운 seed로 setting
(특정값이나 새로운 랜덤 변수 발생시 사용)
cos(x)
x의 cosine값을 리턴
sin(x)
x의 sine값을 리턴
atan2(x,y)
y/x의 arc tangent값을 리턴
exp(x)
e^x의 값을 리턴 (무리수 e의 x승 값)
log(x)
log_e x의 값을 리턴 (자연로그 x값을 리턴)
sqrt(x)
음수가 아닌 x의 루트값 (the radical sign, value = x)
function: date, time
function
description
systime()
UNIX epoch timestamp를 리턴한다.
strftime([fmt [, timestamp]])
C-style strftime
$ awk 'BEGIN { print systime() }'
1400908965
$ awk 'BEGIN { print strftime("%Y%m%d-%H%M%S %z") }'
20140524-143007 +0900
Practice : function : substr

substr - substring
$ cat contdata.txt
B6011KR70053800010000003886390002340000002335000000000030310
B6012KR70357600080000000082690003844000003843000000000000140
B6011KR70059300030000001237900014410000014400000000000007510
B6011KR70059300030000001238240014410000014400000000000007270
B6012KR70357600080000000126490003837000003836000000000000280
B6011KR70053800010000003886630002340000002335000000000030310
$ awk 'substr($0,6,12) == "KR7005380001" { print substr($0,18,12) }' \
contdata.txt
000000388639
000000388663
Practice : function : match

POSIX awk에서는 {n,m} 표현을 지원하지 않는다.
$ gawk --re-interval \
'match($0,/KR[0-9]{10}/) { print substr($0,RSTART+3,RLENGTH-6) }' \
contdata.txt
005380
035760
005930
005930
035760
005380

--re-interval 옵션 : REGEX interval expression을 지원

--re-interval 대신에 --posix 으로 실행해보자.

gawk manual>>extensions in gawk Not in POSIX awk 참고
Practice : function : gsub, sub, gensub

substitution function
$ cat contdata.txt
B6011KR70053800010000003886390002340000002335000000000030310
B6012KR70357600080000000082690003844000003843000000000000140
B6011KR70059300030000001237900014410000014400000000000007510
B6011KR70059300030000001238240014410000014400000000000007270
B6012KR70357600080000000126490003837000003836000000000000280
B6011KR70053800010000003886630002340000002335000000000030310
$ awk --re-interval '{ sub(/KR7[0-9]{9}/, "###123456789"); print }' \
contdata.txt
B6011###1234567890000003886390002340000002335000000000030310
B6012###1234567890000000082690003844000003843000000000000140
B6011###1234567890000001237900014410000014400000000000007510
B6011###1234567890000001238240014410000014400000000000007270
B6012###1234567890000000126490003837000003836000000000000280
B6011###1234567890000003886630002340000002335000000000030310
Practice : function : gensub

gensub(regex, replace, how [,target var.])
$ awk '{ a=gensub(/([0-9]+)/, "(\\1)", "g"); print a}' /etc/passwd
...생략...
sunyzero:x:(500):(500):SY Kim:/home/sunyzero:/bin/bash
$ awk '{ a=gensub(/([0-9]+)/, "(\\1)", "1"); print a}' /etc/passwd
...생략...
sunyzero:x:(500):500:SY Kim:/home/sunyzero:/bin/bash
$ awk '{ a=gensub(/([0-9]+)/, "(\\1)", "2"); print a}' /etc/passwd
...생략...
sunyzero:x:500:(500):SY Kim:/home/sunyzero:/bin/bash

gawk를 사용한다면 gensub는 매우 유용하다.
* how : g (global) , index (replace the n-th field)
Practice : function : gensub (con't)

substitution function - backreference를 지원하는 gensub
$ cat contdata.txt
B6011KR70053800010000003886390002340000002335000000000030310
B6012KR70357600080000000082690003844000003843000000000000140
B6011KR70059300030000001237900014410000014400000000000007510
B6011KR70059300030000001238240014410000014400000000000007270
B6012KR70357600080000000126490003837000003836000000000000280
B6011KR70053800010000003886630002340000002335000000000030310
$ awk --re-interval '{ a=gensub(/KR7([0-9]{9})/, "###\\1", "g"); print a}' \
contdata.txt
B6011###0053800010000003886390002340000002335000000000030310
B6012###0357600080000000082690003844000003843000000000000140
B6011###0059300030000001237900014410000014400000000000007510
B6011###0059300030000001238240014410000014400000000000007270
B6012###0357600080000000126490003837000003836000000000000280
B6011###0053800010000003886630002340000002335000000000030310
* gsub, sub는 backreference를 지원하지 못한다.
Practice : gen. source code

test_posixopt.awk
BEGIN { cnt = 0 }
# get posix options from file.
{ elem[cnt++] = $1; }
# end; print them; generate C source code.
END {
for (i=0; i<=cnt; i++)
{
if (i == 0) {
print "#include <unistd.h>\n#include <stdio.h>\nint main()\n{\n"
} else if (i == cnt) {
print "\treturn 0;\n}"
} else {
printf("#if %s > 0L\n\tprintf(\"[O] %s\\n\");\n", elem[i], elem[i]);
printf("#else\n\tprintf(\"[X] %s\\n\");\n", elem[i], elem[i]);
printf("#endif\n");
}
}
}
Practice : gen. source code (con't)

test_posixopt.txt (posixoptions manpage 참조)
_POSIX_ADVISORY_INFO
_POSIX_ASYNCHRONOUS_IO
_POSIX_BARRIERS
_POSIX_CLOCK_SELECTION
_POSIX_CPUTIME
...생략...
Practice : gen. source code (con't)

Makefile
# Makefile
AWK
= awk
OUT
= test_posixopt
all: $(OUT)
.SUFFIXES:
.PRECIOUS: .o
%.c: %.awk
$(AWK) -f $< $*.txt | tee $@
%: %.o
$(CC) $< $(LOADLIBES) $(LDLIBS) -o $@
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
clean:
rm -f *.o core core.* $(OUT)
shell variable

-v varname="$shell_var"
$
$
$
$
msg="Hello unixer"
var_r="unix"
var_s="linux"
echo $msg | awk '{gsub($var_r, $var_s);print}'
$ echo $msg | awk -v r1="$var_r" -v s1="$var_s" '{gsub(r1, s1);print}'