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) NR>1 # Line number clyde47(john Smith) banaba(john Kennis) printf # formating jamtaeng(jambo Park)
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 o 10 진수양의정수형 8 진수양의정수형 x, X 16 진수양의정수형 f, F 실수형 설명 e, E 실수형 (scientific notation 으로지정된 ) e.g. 1.23456e+05 c s 숫자인경우 ASCII 코드로변환하여문자 1 개출력문자열인경우맨앞한개의문자만출력 문자열출력
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 END 연산이일어나기에수행 initialization, pre-processing 작업 연산이끝난뒤에수행 주로출력을수행한다. 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++) { x = (i+0.5) * sz_step; atan2() - arc-tangent in radians angle 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 if (conditional) {statement_list1} [else {statement_list2}] while (conditional) {statement_list} for (init;condition;ctrl) {statement_list} Desc. C-style if C-style while C-style for loop break 루프를종료시키고다음 statement 로넘어가게한다. continue 현재위치에서루프의다음 iteration 으로넘어가게한다. next exit 현재 input line 에대해서남아있는 pattern 을 skip 남아있는 input 을모두 skip 한다. 만일 END pattern 이있다면처리한후 awk 를종료시킨다.
print, var control print control statement print [expr_list] [>file] printf format [,expr_list] [>file] sprintf (format [,expression]) expr_list 에있는 expression 을 stdout 이나특정파일로리다이렉트한다. file 은 file descriptor 를사용한다. C 언어의 printf 문과동일포맷된형식의 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 Fred 95 55 85 62 297 Kim 88 42 85 60 275 6th field 는새로할당된필드다. $ 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 gsub(r,s[,t]) index(str1,str2) length(str) match(s,r) split(str,array[,sep]) sprintf(fmt,awk_exp) sub(r,s[,t]) description 정규표현식 r 과매치되는문자열 t 를 s 로대치한다. (t 를지정하지않으면 $0 ; 문자열자체가변경되니조심할것!) * return value : 대치에성공한패턴개수 ( 양수 ) Returns the starting position of str2 in str1. If str2 is not present in str1, then 0 is returned Returns the length of string str 문자열 s 에서정규표현식 r 과매치되는부분의위치를넘겨준다 구분자 sep 를기준으로 (default 는 space) 문자열 str 을배열로변환 Returns the values of awk_exp formatted as defined by fmt 정규표현식 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 substr(str,start,length) tolower(str) toupper(str) description Returns a substring of the string str starting at positio n start for length characters ex) substr("believe", 3, 5) == "lieve" 문자열 str 을모두소문자로바꾼다 문자열 str 을모두대문자로바꾼다
function: math function description (angle 은모두 radian 으로쓴다 ) int(x) x 의소수점이하값을버린다. rand() 0 에서 1 사이의 random variable 을취한다. srand(x) cos(x) sin(x) atan2(x,y) rand 을위해 x 를새로운 seed 로 setting ( 특정값이나새로운랜덤변수발생시사용 ) x 의 cosine 값을리턴 x 의 sine 값을리턴 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}'