SED & AWK
credits to CodeSnipcademy
Sed
- Unix utility that parses and transforms text (25 commands)
- Written in a C programming language by Lee E. McMahon of Bell Labs from 1973 to 1974
- One of the earliest tools to support regular expressions
- Remains in use for text processing, most notably with the substitution command
- Other options for doing “stream editing” include AWK and Perl.
아래와 같은 내용으로 ‘OneOS.txt’ 이라는 이름의 텍스트 파일을 만들고 커맨드를 따라해보도록 하자
One OS to rule them all,
One OS to find them.
One OS to call them all,
And in salvation bind them.
In the bright land of Linux,
Where the hackers play.
특정 라인 출력
# 한 줄씩 읽고 print
$ sed 'p' oneOS.txt
# 입력 에코 끄기
$ sed -n 'p' oneOS.txt
# 2번 라인 출력
$ sed -n '2p' oneOS.txt
# 3번 라인부터 5까지 출력
$ sed -n '3,5p' oneOS.txt
# 마지막 라인 ($) 출력
$ sed -n '$p' oneOS.txt
# 4번 라인부터 마지막까지 출력
$ sed -n '4,$p' oneOS.txt
# 1번 라인부터 3번까지를 제외한 나머지 출력
$ sed -n '1,3!p' oneOS.txt
# 3번 라인부터 마지막까지를 제외한 나머지 출력
$ sed -n '3,$!p' oneOS.txt
regex를 만족시키는 라인만 출력
# 단어 'One' 으로 시작하는 라인 출력
$ sed -n '/^One/p' oneOS.txt
# 단어 'OS' 가 없는 라인 출력
$ sed -n '/OS/!p' oneOS.txt
# 두 regex 패턴 사이의 라인 출력
$ sed -n '/call/,/hackers/p' oneOS.txt
라인 삭제
# 2번 라인부터 4번까지 라인 삭제
$ sed '2,4d' oneOS.txt
# 1번 라인부터 5번까지 삭제 후 "in-plcae" 저장, 원본은 {파일이름}.bak 으로 백업저장
$ sed -i.bak '1,5d' oneOS.txt
# 모음으로 시작하는 라인 삭제
$ sed '/^[AEIOU]/d' oneOS.txt
# 단어 'all.' 로 끝나는 라인 삭제
$ sed '/all\.$/d' oneOS.txt
# 시작 라인과 마지막 삭제
$ sed '1d;$d' oneOS.txt
# 특정 라인 삭제
$ sed '1d;3d;5d' oneOS.txt
# 빈 라인 삭제
$ sed '/^$/d' oneOS.txt
# 빈 라인만 제외하고 삭제
$ sed '/./!d' oneOS.txt
# 비어있거나 공백이 있는 라인 삭제
$ sed '/^ *$/d' oneOS.txt
# 패턴에 일치하는 라인부터 마지막 라인까지 삭제
$ sed '/salvation/,$d' oneOS.txt
치환
# 각 라인의 첫번째 출현 패턴만 치환
$ echo -e "hello, hello\nhello, hello" | sed 's/hello/hi/'
# 모든 출현 패턴 전역 (global) 치환
$ echo -e "hello, hello\nhello, hello" | sed 's/hello/hi/g'
# 단어 One을 Two로 치환
$ sed 's/One/Two/g' oneOS.txt
# substitute 커맨드 's'다음에 오는 문자가 delimiter로 간주된다
$ sed 's;One;Two;g' oneOS.txt
$ sed '1,2sXOneXThreeXg' oneOS.txt
# 치환이 이루어진 라인 출력
$ sed -n 's/OS/ring/p' oneOS.txt
# 1번 라인부터 3번까지의 모든 '9'를 '0'으로 치환
$ sed '1,3s/9/0/g' grades.txt
# tab delimited 파일을 semicolon delimited 으로 변환
$ sed -E $'s/\t/;/g' grades.txt
# backrefrence 이용해 두 단어의 위치 바꾸기
$ sed 's/\([a-z]*\) \(them\)/\2 \1/' oneOS.txt
# -E 옵션으로 확장 regex를 사용하면 괄호를 '\'로 escape 하지 않아도 된다
$ sed -E 's/([a-z]*) (them)/\2 \1/' oneOS.txt
# Ampersand (&) 이용해 찾은 패턴 참조하기
$ sed 's/^[AEIOU][a-z]*/\+&\+/' oneOS.txt
$ sed 's/^[AEIOU][a-z]*/+&+/' oneOS.txt
복수의 sed command 실행하기
# -e 옵션 이용
$ sed -e 's/OS/ring/g' -e 's/Linux/Mordor/g' -e 's/hackers/orcs/g' oneOS.txt
# semicolon 이용
sed 's/OS/ring/g;s/Linux/Mordor/g;s/hackers/orcs/g' oneOS.txt
# semicolon 이용, in-place 치환, 원본은 bak 확장자로 추가 저장
$ sed -i.bak 's/OS/ring/g;s/Linux/Mordor/g;s/hackers/orcs/g' oneOS.txt
# sed script 이용
$ sed -f mySedScript oneOS.txt
파일로 출력하기
# 첫번째 라인 output.txt 파일로 출력
$ sed -n '1w output.txt' oneOS.txt
# 첫번째 라인과 마지막 파일로 출력
$ sed -n -e '1w output.txt' -e '$w output.txt' oneOS.txt
# 패턴과 일치하는 라인 파일로 출력
$ sed -n '/One OS/w output.txt' oneOS.txt
AWK
- Programming language designed for text processing, data extraction, and reporting tool
- Created at Bell Labs in 1977, and its name is derived from the surnames of its authors
“AWK is a language for processing text files. A file is treated as a sequence of records, and by default each line is a record. Each line is broken up into a sequence of fields, so we can think of the first word in a line as the first field, the second word as the second field, and so on. An AWK program is a sequence of pattern-action statements. AWK reads the input a line at a time. A line is scanned for each pattern in the program, and for each pattern that matches, the associated action is executed.”
- Alfred V. Aho
AWK의 작업흐름
BEGIN {
// 변수 초기화, 선언, 및 커맨드 실행
}
{
/패턴/ {커맨드} // 패턴을 일치하는 라인에만 커맨드 적용
/패턴/ // 패턴에 일치하는 모든 라인을 출력
{커맨드} // 모든 라인에 커맨드 적용
}
END {
// 정리 및 요약 파일 출력
}
예제 파일 작성 (탭으로 항목 구분)
Gilbert Strang 94 95 95 A
Vern Wynne 85 78 93 B
Ingram Dannie 84 85 94 B+
Wright Morty 75 76 79 C+
Johnnie Adair 78 94 87 B
‘test.awk’ 스크립트 작성
# test.awk
BEGIN {
# Print the header out before starting anything
print "--------------------------------------------"
printf "FName\tLName\tExam1\tExam2\tFinal\tGrade\n";
# Initialize any variables
n=0;
}
{
# Print each line (called "record")
print $0
# If the sixth column (called "field") is a B, then increment n
if($6 == "B") { ++n }
}
END {
# Wrap things up and print out summary variables
print "--------------------------------------------"
print "Number of students with a B in the class = " n;
}
AWK script 실행
$ awk -f test.awk grades.txt
Record Seperator (RS), Record Text (RT)
# RS는 한개 이상의 숫자로 정의하고, 그 패턴에 일치하는 RT를 출력
$ echo FirstRecord 111 SecondRecord 222 ThirdRecord 333 LastRecord | \
awk 'BEGIN { RS = "([[:digit:]]+)" } \
{ print "RS = " RS " and RT = " RT }'
Output Record Seperator (ORS)
# RS는 ';', ORS는 ' +'로 정의해 출력
# $0는 현재의 record를 가르킨다
$ echo 'hello; nihao; hola; anyonghasaeyo' | \
awk 'BEGIN { RS = ";"; ORS = " +"} { print $0 }'
Field Seperator (FS)
# FS의 초기값은 한개 이상의 공백 (whitespace)
$ echo -e 'Joe John Jose\nJoe John Jose' | \
awk '{ print NF " fields in line " NR ": " $0 }'
# FS를 ';'로 정의
$ echo -e 'Joe John Jose\nJoe;John;Jose' | \
awk 'BEGIN { FS=";" } { print NF " fields in line " NR ": " $0 }'
# -F 옵션을 사용해 FS를 ','로 정의
$ echo -e 'Joe John Jose\nJoe,John,Jose' | \
awk -F ',' '{ print NF " fields in line " NR ": " $0 }'
Output Field Seperator (OFS)
# RS는 ';', OFS는 ' loves ' 라는 string으로 정의
# $1, $2는 각각 첫번째와 두번째 필드를 가르킨다
$ echo 'John Mary; Cholsoo Younghee' | \
awk 'BEGIN { RS=";"; OFS=" loves " } { print $1, $2 }'
패턴에 일치하는 라인 출력
# 6번 필드에 'B'를 포함하는 라인 출력
$ awk '$6 ~ /B/ { print $0 }' grades.txt
# 'B' 가 있는 라인의 두번째 필드와 첫번째 출력
$ awk '/B/ { print $2 ", " $1 }' grades.txt
# 두번째 필드가 'e'로 끝나는 라인의 두번째 필드 출력
$ awk '$2 ~ /e$/ { print "col. 2 of line", NR, "ends with e -", $2 }' grades.txt
# 패턴에 대한 boolean 출력
$ awk '{ print $2 ~ /e$/ }' grades.txt
# 4번 필드와 5번 필드가 같은 라인 출력
$ awk '$4 == $5' grades.txt
# 4번 필드와 5번 필드가 같지 않은 라인 출력
$ awk '$4 != $5' grades.txt
# 3번 필드가 4번 필드보다 크다면 마지막 필드 출력
$ awk '$3 > $4 { print $NF }' grades.txt
# 3번 필드가 94이고 2번 필드가 'ng'를 포함하는 라인의 1번 그리고 2번 필드 출력
$ awk '$3 == "94" && $2 ~ /ng/ { print $1, $2 }' grades.txt
그 밖의 예약어들
# 환경변수 ENVIRON
$ awk 'BEGIN { print ENVIRON["HOME"]; print ENVIRON["USER"]; }'
# 파일의 레코드 번호 FNR, 작업 레코드 번호 NR, 필드 개수 NF, 파일이름 FILENAME
$ awk '{ print FNR, "\t", NR, "\t", NF, "\t", FILENAME }' grades.txt test.awk
추가 예
# 1번 라인부터 3까지 라인 번호, 필드 개수, 그리고 마지막 필드 출력
$ awk 'NR > 0 && NR < 4 { print NR, NF, $NF}' grades.txt
# 5번 필드에 있는 값들의 평균 출력
$ awk 'BEGIN { total = 0 } { total += $5 } END { print total / NR }' grades.txt
$ awk '{ total += $5 } END { print total / NR }' grades.txt
# 'I'나 'W'로 시작하는 라인 출력
$ awk '/^(I|W)/' grades.txt
# 두번째 필드가 'e'로 끝나는 라인 출력
$awk '$2 ~ /e$/' grades.txt
# - 길고 복잡한 파일이름에서 복수의 delimiter를 사용해 '001-out'만 추출해내기
# awk 사용
$ echo 'Sample_0000860156-001-out_20150224150524.xml.gz' | \
awk -F '[-_]' '{ print $3 "-" $4 }'
# sed 사용
$ echo 'Sample_0000860156-001-out_20150224150524.xml.gz' | \
sed -E 's/.*_[0-9]*-([0-9]*-.*)_.*/\1/'
# perl 사용
$ echo 'Sample_0000860156-001-out_20150224150524.xml.gz' | \
perl -pe 's/.*_.+?-(.+?-.*)_.*/\1/'
# cut 사용
$ echo 'Sample_0000860156-001-out_20150224150524.xml.gz' | \
cut -d '_' -f 2 | cut -d '-' -f 2,3
# 파일을 50 라인 단위로 자르고 지정한 포맷으로 저장하기
$ awk -v lines=50 -v fmt="chunk%d.txt" \
'{ print > sprintf(fmt, 1+(NR-1)/lines) }' sample_corpus.txt
# awk 스크립트 파일 실행
$ awk -f split_sent_goal.awk sample_corpus.txt
# sample_corpus.txt 코퍼스 내용
take me to that <LOC_POI> Nav
drive to the <LOC_POI> Nav
give me directions to a <LOC_POI> Nav
.
.
.
# split_sent_goal.awk 스크립트 내용
{
printf "*\t*\tSQ\n*\t*\tSQ\n"
for ( i = 1; i < NF; i++ ) {
if ( $i ~ "<.*>" ) {
printf "%s\t%s\n", $i, $NF
} else {
printf "%s\t%s\n", $i, $NF
}
}
printf "**\t**\tEQ\n\n"
}
Sung Hah Hwang