Profile picture

[Linux] AWK 명령어 문법 살펴보기

JaehyoJJAng2023년 03월 15일

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 {...}

패턴 BEGINawk의 특별한 패턴으로, 입력 데이터를 읽기 시작하기 전에 딱 한 번만 실행된다.

파일 처리 없이 순수 계산만 할 때 유용하다.


# 예시 명령어
awk "BEGIN {printf \"%.2f\", (100 * (전체변화량 - 유휴변화량) / 전체변화량)}"

Loading script...