AWK
- Aho Weinberger Kernighan
awk
는 텍스트 데이터를 한 줄씩 읽어 들여, 특정 패턴을 찾고, 원하는 동작을 수행하는 텍스트 처리 도구 이다!
주로 데이터 열(column
)을 기반으로 정보를 추출, 계산하는 데 탁월한 성능을 보인다.
awk 기본 사용법
awk
는 다음과 같은 구조로 동작한다.
awk '패턴 { 동작 }' 입력파일
- 패턴(Pattern): 어떤 줄에서 '동작'을 실행할지 결정하는 조건이다. 이 부분을 생략하면 모든 줄에 대해 '동작'을 실행한다.
- 동작(Action): 패턴이 일치하는 줄에서 수행할 명령어들의 집합이다.
{ ... }
안에 작성하면 된다.
종합하면 awk
는 파일로부터 레코드(record)를 선택하고, 선택된 레코드에 포함된 값을 조작하거나 데이터화 한다.
file: person.txt
name | phone | birth | sex | score |
---|---|---|---|---|
john | 010-1234-5678 | 1988-01-01 | M | 100 |
anna | 010-3333-4444 | 1989-01-01 | F | 90 |
위와 같은 텍스트 파일이 있을 때, 여기서 각 단어들은 공백으로 이루어져 있다 그리고 여기서 각 줄(line)은 레코드(Record)라고 칭하고 그 안에 각각의 단어들이 필드(Field)라고 칭해진다.
AWK 프로그래밍 문법에서는 레코드가 $0
, 그리고 $1
, ... $N
은 각 필드 인자를 나타내게 프로그래밍 되어있다.
위에서 예시를 든 코드를 다시 보자면, 이런식 으로 해석이 된다.
$0는 한 행을 의미하고 $1는 그 행의 첫 번째 필드, $2는 두 번째 필드가 되는 것이다.
$ cat test.txt | awk '{print $0 " | " $1 * $2;}'
1 2 | 2
3 4 | 12
간단하게 awk가 제공해주는 동작 원리를 살펴보았다.
정리하자면, awk는 명령의 입력으로 지정된 파일로부터 데이터를 분류한 다음, 분류된 텍스트 데이터를 바탕으로 패턴 매칭 여부를 검사하거나 데이터 조작 및 연산 등의 액션을 수행하고, 그 결과를 출력하는 기능을 수행하는 것이다.
awk 옵션 살펴보기
$ awk [옵션] 'pattern { action }' [파일 | 변수값]
awk 옵션 | 설명 |
---|---|
-u | 버퍼를 사용하지 않고 출력한다. |
-F | 확장된 정규 표현식으로 필드구분자를 지정한다, 다중 필드 구분자 사용 가능하다. |
awk -F | 단일로 사용시 ':' 를 필드구분자로 사용 |
awk -F'[ :\t]' | 다중 필드구분자 ':'와 tab을 필드구분자로 사용 |
-v | 스크립트를 실행하기 전에 미리 변수를 지정하여 준다. |
-f | awk 명령 스크립트를 파일에서 읽어온다. |
AWK 동작 원리
패턴(pattern) 과 액션(action)
- awk는 파일 또는 파이프를 통해 입력 라인을 얻어와 $0라는 내부 변수에 라인을 입력한다.
각 라인은 레코드라고 부르고, newline(개행)에 의해 구분되다. 이때 패턴이 없으면 전체 라인을 얻어오고, 원하는 라인만 얻어오고 싶을때는 패턴을 사용해 분별할 수 있다.
- awk를 실행할때 내장 변수인 FS라고 부르는 필드 분리자가 공백을 할당 받는다. (필드 분리 기준을 공백이 아닌 다른 값으로 바꿀수도 있다)
그러면 awk는 라인을 공백을 기준으로 각각의 필드나 단어로 나눈다. 필드는 $1부터 시작해서 많게는 $100 이상의 변수에 저장할 수 있다.
- 각 필드 데이터들을 저장했다면 awk는 액션을 통해 동작 스크립팅을 할 수 있다. 예를 들어, 필드들을 화면에 출력할 때 print 함수를 사용하면 된다.
AWK 필드
awk는 입력 텍스트를 레코드(줄)와 필드(공백이나 특정 구분자로 구분된 단위)로 나눈다.
기본적으로 공백이나 탭 문자를 필드 구분자로 사용한다. 각 필드는 $1
, $2
, ... $NF
와 같이 참조할 수 있다.
$1
: 첫 번째 필드$2
: 두 번째 필드$NF
: 마지막 필드 (NF
는 현재 줄의 필드 개수를 나타냄)
비교연산 패턴
조건문처럼 해당 조건에 부합한 데이터 라인들을 뽑아낼 수 있다.
💡 TIP
숫자, 알파벳 모두 비교 연산이 가능
# /etc/passwd 파일에서 3번째 필드인 $3의 값이 0보다 작거나 같을 경우 나머지 필드들을 print
$ cat /etc/passwd | awk -F ":" '$3 <=0 {print $1, $3}'
0
# 복잡한 논리식 또한 사용 가능
$ awk '$3 > $5 && $3 <= 100 {print $1}' filename.txt
정규표현식 패턴
grep
명령어와 같이 패턴 부분에 정규식 /regex/를 넣어 라인을 분별할 수도 있다
# 대소문자 구분 없는 알파벳으로 시작하고 뒤에 어느 한 문자가 오는 라인 매칭
$ awk '/^[A-Z][a-z]+ /' filename
# "이" 자로 시작하는 라인 골라서 print
$ awk '/^정/{print $1,$2,$3}' filename
패턴 매칭 연산
match 연산자(~) : 표현식과 매칭되는 것이 있는지 검사하는 연산자
- ~ 일치하는 부분
- !~ 일치하지 않는 부분
새로운 문법이지만, 이렇게 이해하면 간단하다. 보통 값을 비교할때, == 연산자를 쓰는데, 패턴매칭을 비교하기 위해선 == 대신 match연산자(~)를 쓰는 것이다.
# 2번 필드 문자열이 문자 g로 끝나지 않는 라인 출력
$ awk '$2 !~ /g$/' filename
awk 제어문
# if문
if ( condition ) { Routine } else { Routine }
# for문
for ( init ; condition ; re ) { Routine }
# while문
while (condition) { Routine }
# do ~ while문
do { Routine } while (condition)
# 반목문 제어
break
continue
return
# 프로그램 제어
next
exit
if문
# 단일 if
$ awk '{if($6>50) print $1 "Too high" }' filename
$ awk '{if($6>20 && $6<=50) {safe++; print "OK"}}' filename
# if ~ else
$ awk '{if($6>50) print $1 "Too high"; else print "Range is OK"}' filename
$ awk '{if($6>50) {count++; print $3} else{x+5; print $2}}' filename
가독성이 안 좋다면 개행 문법을 사용해보도록 하자. awk는 개행 문법을 지원하고 있다
$ awk '{
if ($3 >=35 && $4 >= 35 && $5 >= 35)
print $0,"=>","Pass";
else
print $0,"=>","Fail";
}' filename
$ awk \ # \문자를 써도 터미널에서 개행이 가능하다.
'
{
if ($3 >=35 && $4 >= 35 && $5 >= 35)
print $0,"=>","Pass";
else
print $0,"=>","Fail";
}
'
삼항 연산자 또한 사용 가능
# 삼항 연산자 역시 사용이 가능하다
$ awk '{max={$1 > $2) ? $1 : $2; print max}' filename
for문
$ awk '{for(i=1; i<=NF; i++) print NF, $1}' filename
$ awk '{
for(i=0;i<2;i++)
print( "for loop :" i "\t" $1, $2, $3)
}' filename
while문
$ awk '{i=1; while(i<=NF) {print NF, $1; i++}}' filename
awk 리다이렉션
awk 결과를 리눅스 파일로 리다이렉션할 경우 쉘 리다이렉션 연산자를 사용한다.
다만 > 기호가 논리연산자인지 리다이렉션인지 모호할때가 있는데, 그냥 action 부분에 > 기호가 쓰이면 리다이렉션, pattern 부분에 쓰이면 논리 연산이라고 치부하면 된다.
파일명은 큰따옴표로 둘러쌰아 인식 된다.
$ awk -F: '$4 >= 60000 {print $1, $2 > "new_file"}' awkfile5
awk 사용 예시
필드 값 출력
$ cat file.txt
1 ppotta 30 40 50
2 soft 60 70 80
3 prog 90 10 20
$ awk '{ print $1,$2 }' ./file.txt # 첫 번째, 두 번째 필드 값 출력.
1 ppotta
2 soft
3 prog
$ awk '{ print $0 }' ./file.txt # 레코드 출력.
1 ppotta 30 40 50
2 soft 60 70 80
3 prog 90 10 20
$ cat file.txt
1 ppotta 30 40 50
2 soft 60 70 80
3 prog 90 10 20
$ awk '{print "no:"$1, "user:"$2}' ./file.txt # 필드 값에 문자열을 함께 출력
no:1 user:ppotta
no:2 user:soft
no:3 user:prog
CSV 파일 처리
- CSV 파일 처리 (-F 옵션)
$ cat test.csv
1,2,3
4,5,6
7,8,9
$ cat b.txt | awk -F, '{print $1}' # -F옵션을 통해 , 로 구분기호를 재지정하고 $1필드만 출력
1
4
7
NR 내장변수
- 행 번호 출력 (NR 내장변수)
$ cat test.csv
1,2,3
4,5,6
7,8,9
$ cat test.csv | awk -F, '{print NR " " $0;}'
1 1,2,3
2 4,5,6
3 7,8,9
$ awk_example]$ cat test.csv | awk -F, '{print NR-1 " " $0;}' # 0부터 시작
0 1,2,3
1 4,5,6
2 7,8,9
- 명령어 출력 결과에서 두번째 행만 출력
docker ps --filter "name=mysql" | awk 'NR>1'
산술 계산
$ cat num.txt
100
200
300
400
500
$ awk '{sum+=$1} END {print sum}' num.txt # 합계 계산. END패턴을 통해 레코드를 모두 돌도 난 후 마지막에 합을 출력
1500
$ awk '{sum+=$1} END {print sum/NR}' num.txt # 평균 계산. 내장변수 NR은 출력순번값. 즉 레코드 갯수를 의미
300
## NR==1 {max=$1} 이라는 뜻은 최대/최소를 구하기 위해 초깃값을 설정하는 로직. 출력순번이 첫번째 일떄만 max변수에 $1값 저장
$ awk 'NR==1 {max=$1} {if($1 > max) max = $1} END {print max}' num.txt # 최댓값 계산
500
$ awk 'NR==1 {min=$1} {if($1 < max) min = $1} END {print min}' d.txt # 최솟값 계산
100
$ cat file.txt
1 ppotta 30 40 50
2 soft 60 70 80
3 prog 90 10 20
$ awk '{ sum = 0 } {sum += ($3+$4+$5) } { print $0, sum, sum/NR }' ./file.txt # 합계와 평균 구하기
1 ppotta 30 40 50 120 40
2 soft 60 70 80 210 70
3 prog 90 10 20 120 40
조건문
$ cat file.txt
name phone birth sex score
reakwon 010-1234-1234 1981-01-01 M 100
sim 010-4321-4321 1999-09-09 F 88
$ awk '{ if ( $5 >= 80 ) print ($0) }' file.txt # score가 80점 이상 레코드만 출력
$ awk '$5 >= 80 { print $0 }' ./awk_test_file.txt # action부분에 if를 쓰든 pattern부분에 비교연산자를 쓰든 결과는 동일
루프문
$ cat file.txt
1 ppotta 30 40 50
2 soft 60 70 80
3 prog 90 10 20
$ awk '{ for (i=3; i<=NF; i++) total += $i }; END { print "TOTAL : "total }' ./file.txt # 각 레코드의 $3 ~ $5 필드 총합
TOTAL : 450
파이프 조합
$ cat file.txt
1 ppotta 30 40 50
2 soft 60 70 80
3 prog 90 10 20
$ awk '{ print $0 }' file.txt | sort -r # awk 출력 레코드를 파이프로 보내 역순으로 정렬.
3 prog 90 10 20
2 soft 60 70 80
1 ppotta 30 40 50
행 데이터 세기
- 텍스트 파일을 처리하고 첫 번째 행을 제외한 나머지 행의 데이터 수 세기
#!/bin/bash
# 파일명 설정
file="파일명.txt"
# awk를 사용하여 데이터 수 계산
count=$(awk 'NR > 1 {print}' "$file" | wc -l)
echo "데이터 수 : $count"
위 스크립트는 주어진 파일에서 첫 번째 행을 제외하고 나머지 행의 수를 세는 데 사용됩니다. 여기서 NR > 1는 현재 행 번호가 1보다 큰 경우를 의미하며, 이를 통해 첫 번째 행을 건너뛰고 두 번째 행부터 처리합니다. 그런 다음 wc -l은 출력된 행 수를 계산합니다.
이 스크립트를 실행하면 셸에서 텍스트 파일의 첫 번째 행을 제외한 데이터 수가 표시됩니다.
메모리 사용 계산
- 메모리 사용 현황 계산
free -m | awk 'NR==2{printf "%.2f", $3 * 100 / $2}'
예를 들어, 만약 free -m
명령어의 출력이 다음과 같다고 가정할 때
total used free shared buff/cache available
Mem: 7855 3022 2448 102 2384 4255
Swap: 2047 0 2047
그러면 awk 'NR==2{printf "%.2f", $3 * 100 / $2}'
부분은 3022 * 100 / 7855
를 계산하여 메모리 사용량의 백분율을 출력할 것이다.
결과는 38.50
이 된다.
옵션 | 설명 |
---|---|
NR==2 |
출력의 두 번째 줄에 해당하는 행을 선택. 여기서는 메모리 사용량을 나타내는 줄 |
'{printf "%.2f", $3 * 100 / $2}' |
선택된 행에서 세 번째 필드($3 ,즉 사용중인 메모리)와 두번째 필드($2 , 즉 총 메모리)를 사용하여 사용 중인 메모리의 백분율을 계산하고 소수점 둘째 자리까지 출력함. |
모든 디스크 용량 조회
df -h | grep -v '파일 시스템' | awk '{gsub("%",""); USE=$5;MNT=$6; print USE,MNT}' | column -t
BEGIN
awk 'BEGIN {...}
패턴 BEGIN
은 awk
의 특별한 패턴으로, 입력 데이터를 읽기 시작하기 전에 딱 한 번만 실행된다.
파일 처리 없이 순수 계산만 할 때 유용하다.
# 예시 명령어
awk "BEGIN {printf \"%.2f\", (100 * (전체변화량 - 유휴변화량) / 전체변화량)}"