문서 읽는 데 49분 · A5

A-5: 텍스트 변환

목차 25
전체 12강 중 5강 · 리눅스
난이도 · 입문

ℹ️서버·배포에서 쓰는 리눅스 셸과 명령어 기본기. 백엔드·인프라로 가기 전에 익혀두면 든든해요.

안녕하세요, 홍순구입니다. 지난 시간엔 파이프와 grep으로 수천 줄짜리 로그에서 원하는 줄만 쏙 뽑아냈죠. cut으로 칸도 오려내 봤고요. "읽고 뽑기"는 이제 제법 익숙해졌을 거예요.

그런데 지난 시간 마지막에 제가 욕심 두 개를 슬쩍 흘려뒀어요. 하나는 "cut이 공백 여러 칸에 약하다"였고, 또 하나는 "찾는 건 했는데, 그 글자를 다른 걸로 바꾸려면?"이었습니다. 오늘은 이 두 욕심을 둘 다 풀어요.

현업에서 이런 상황을 진짜 자주 만나요. 운영 서버 설정 파일 수십 줄에 흩어져 있는 옛 도메인을 새 도메인으로 한 방에 바꿔야 할 때, 또는 로그를 받아서 "에러를 낸 IP가 누가 몇 번씩이지?"를 집계해 보고해야 할 때요. 이걸 손으로 한 줄씩 고치거나 눈으로 세고 있으면 밤을 새웁니다. 오늘 배우는 두 도구가 그 일을 한 줄 명령으로 끝내줘요.

  • sed — 파일을 열지 않고 텍스트가 흐르는 동안 글자를 찾아 바꾸는 도구
  • awk — 텍스트를 필드(칸) 단위로 똑똑하게 다루고, 세고 더하는 계산까지 하는 도구

오늘 걸어갈 길을 한눈에 그려둘게요.

텍스트
  오늘의 흐름
  ─────────────
  ① sed 로 흐름에서 글자를 찾아 바꾼다   (s/old/new/g · 줄 삭제)
        │
        
  ② awk 로 필드를 똑똑하게 뽑는다        ($1 · $NF — cut 의 공백 약점 해결)
        │
        
  ③ awk 로 패턴을 골라 집계한다          (/ERROR/ · BEGIN/END · 에러 IP Top-N)

지난 시간에 만든 가짜 로그 ~/practice/logs/access.log를 오늘도 그대로 씁니다. 혹시 지웠다면 다시 만들어 둘게요. (이미 있으면 건너뛰어도 돼요.)

Bash
mkdir -p ~/practice/logs && cd ~/practice/logs
cat > access.log << 'EOF'
2026-06-16 10:01:22 INFO GET /feed 200 192.168.0.11
2026-06-16 10:01:23 INFO GET /login 200 192.168.0.23
2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:28 WARN GET /config 404 10.0.0.5
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42
2026-06-16 10:01:33 INFO GET /feed 200 192.168.0.11
2026-06-16 10:01:36 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:39 INFO GET /profile 200 192.168.0.23
2026-06-16 10:01:41 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:44 WARN GET /search 404 192.168.0.11
EOF

한 줄은 날짜 시각 레벨 메서드 경로 상태코드 IP 순서예요. 공백으로 나뉜 일곱 칸이라고 기억해두면 오늘 awk를 만날 때 편합니다.

💡 오늘 수업의 핵심 — "sed로 흐름에서 글자를 바꾸고, awk로 필드를 다뤄 패턴별로 집계한다"

grep이 "찾기"였다면, sed는 "찾아서 바꾸기"예요. 파일을 통째로 열지 않고 한 줄씩 흘려보내며 치환합니다. 그리고 awk는 한 줄을 공백 기준으로 칸칸이 잘라 "셋째 칸만", "마지막 칸만" 꺼내 쓰고, 거기에 조건과 계산을 얹어 집계까지 해요. 여기까지가 오늘의 목표입니다.

🎯 학습 목표

  • sed로 흐름에서 치환(s/찾을것/바꿀것/g)하고, 원하는 줄을 삭제·선택하며, -i로 파일을 직접 고칠 때 백업으로 안전하게 하는 습관을 익힌다
  • awk로 필드($1·$NF)를 뽑아 cut이 공백에 약했던 문제를 풀고, 패턴으로 원하는 줄만 골라낸다
  • awkBEGIN/END와 변수로 줄을 세고 더해, "에러 IP가 누가 몇 번"을 awk 한 줄로 집계한다

Step 1: "찾아서 바꾸는 흐름 편집기 — sed의 기본 치환"

지난 시간 grep은 원하는 줄을 찾아주기만 했어요. 그 줄의 글자를 다른 걸로 바꾸려면 어떻게 해야 할까요? 파일을 편집기로 열어 일일이 고치는 방법이 떠오를 거예요. 그런데 줄이 수천 개라면? 같은 단어가 수백 군데 흩어져 있다면?

여기서 sed가 등장합니다. sed(스트림 편집기, stream editor — 파일을 통째로 열지 않고 한 줄씩 흘려보내며 바꾸는 도구)는 텍스트를 컨베이어 벨트 위로 흘려보내요. 벨트 위로 줄이 한 줄씩 지나갈 때, sed가 옆에 서서 "이 글자를 보면 저 글자로 갈아끼워라"라는 규칙대로 바꿉니다. 파일을 열어 커서를 옮길 필요가 없어요.

가장 많이 쓰는 명령이 치환이에요. 문법은 이렇게 생겼습니다.

텍스트
 sed 's/찾을것/바꿀것/'
       │  │      │
       │  │      └ 새로 넣을 글자
       │  └ 찾을 글자
       └ s = substitute(치환)

간단한 문장으로 먼저 익혀볼게요.

Bash
echo "hello world, hello world" | sed 's/world/linux/'
텍스트
hello linux, hello world

world가 두 번 나오는데 처음 하나만 바뀌었죠? sed의 기본 치환은 한 줄에서 처음 만난 것 하나만 바꿉니다. (전부 바꾸는 법은 다음 Step에서 봐요.)

이제 로그 파일에 걸어볼게요. ERROR라는 글자를 우리말 오류로 바꿔보겠습니다.

Bash
sed 's/ERROR/오류/' access.log
텍스트
2026-06-16 10:01:22 INFO GET /feed 200 192.168.0.11
2026-06-16 10:01:23 INFO GET /login 200 192.168.0.23
2026-06-16 10:01:25 오류 GET /admin 403 10.0.0.5
2026-06-16 10:01:28 WARN GET /config 404 10.0.0.5
2026-06-16 10:01:30 오류 POST /upload 500 192.168.0.42
...

ERROR가 든 줄만 오류로 바뀌어 화면에 나왔어요. 여기서 꼭 짚고 갈 게 하나 있습니다. 원본 파일은 전혀 안 바뀌었어요. sed는 기본적으로 결과를 화면(stdout — 지난 시간에 배운 표준 출력이죠)에만 보여주고, 원본은 그대로 둡니다. 확인해볼까요?

Bash
grep -c ERROR access.log
텍스트
4

오류로 다 바꾼 것 같았는데 원본엔 ERROR가 여전히 4개 그대로 있죠. 이게 sed의 안전한 점이에요. 바꾼 결과를 화면으로 먼저 확인하고, "좋아, 이대로 가자" 싶을 때만 파일에 반영하면 됩니다. 현업 서버에서 설정 파일을 바꾸기 전에, 이렇게 화면으로 미리 결과를 보고 검토하는 게 사고를 막는 첫 번째 습관이에요.

💡 한 줄 정리

sed는 파일을 열지 않고 흐름에서 s/찾을것/바꿀것/으로 글자를 바꾼다. 기본은 한 줄에서 처음 하나만 바꾸고, 결과는 화면에만 보여주며 원본은 건드리지 않는다.

🙋 학생 질문 — "튜터님, sed가 원본을 안 바꾸면 바꾼 결과는 어디로 사라지나요?"

사라지는 게 아니라 화면(stdout)으로 흘러나온 거예요. 지난 시간에 배운 표준 출력 기억나시죠? sed의 결과도 다른 명령들처럼 기본은 화면으로 나옵니다.

그러니 저장하고 싶으면 지난 시간에 배운 리다이렉션을 그대로 쓰면 돼요. sed 's/ERROR/오류/' access.log > new.log 하면 바뀐 결과가 new.log에 담깁니다. 원본 access.log는 그대로 두고요.

"그럼 원본 파일 자체를 바꾸려면?" 그건 바로 다음 Step에서 -i 옵션으로 배웁니다. 다만 그건 원본을 덮어쓰는 거라 조심해야 해서, 안전 습관과 함께 알려드릴게요.


Step 2: "한 줄에 여러 번, 그리고 파일을 직접 고치기 — g-i"

Step 1에서 world가 두 번 나왔는데 처음 하나만 바뀌었죠. 한 줄에 있는 걸 전부 바꾸려면 끝에 g를 붙입니다. g는 global(전역)이에요.

Bash
echo "a-a-a" | sed 's/a/X/'
echo "a-a-a" | sed 's/a/X/g'
텍스트
X-a-a
X-X-X

g가 없으면 처음 하나, g가 있으면 한 줄의 전부. 이 차이가 실무에서 꽤 중요해요. "줄마다 첫 번째만 바꾸고 싶은지, 전부 바꾸고 싶은지"를 g 하나로 고릅니다.

여기서 작은 함정 하나. 바꿀 대상에 슬래시(/)가 들어 있으면 어떻게 할까요? 예를 들어 경로 /usr/local/opt로 바꾸려는데, 슬래시가 sed의 구분자랑 겹쳐서 헷갈려요. 이럴 땐 구분자를 슬래시 대신 다른 글자로 바꾸면 됩니다. s 뒤에 오는 글자가 곧 구분자거든요. 파이프(|)를 구분자로 써볼게요.

Bash
echo "/usr/local/bin" | sed 's|/usr/local|/opt|'
텍스트
/opt/bin

슬래시 대신 |로 칸을 나누니 경로 안의 슬래시를 그대로 쓸 수 있죠. 자잘하지만 서버 경로를 다룰 때 정말 자주 쓰는 요령이에요.

이제 진짜 파일을 고쳐봅시다. 지금까지는 화면에만 보여줬는데, 원본 파일 자체를 바꾸려면 -i(in-place — 제자리에서) 옵션을 씁니다.

⚠️ -i는 원본을 덮어씁니다. 지난 시간에 강조했듯 리눅스엔 휴지통이 없어요. 한 번 덮어쓰면 되돌릴 마법이 없습니다. 그래서 안전 습관 하나를 꼭 같이 익혀요 — -i.bak처럼 뒤에 확장자를 붙이면, 바꾸기 전 원본을 .bak 파일로 백업해두고 고칩니다.

Bash
sed -i.bak 's/INFO/정보/g' access.log
ls access.log*
텍스트
access.log  access.log.bak

access.logINFO정보로 바뀌었고, access.log.bak에 손대기 전 원본이 고스란히 남았어요. 혹시 잘못 바꿨더라도 .bak을 되돌리면 됩니다. 현업에서 설정 파일 수십 개의 옛 도메인을 새 도메인으로 일괄 변경할 때 sed -i가 진짜 힘을 발휘하는데, 단 한 번 잘못 치면 전부 덮어써지니 -i.bak을 붙이는 습관이 여러분을 살립니다.

방금 INFO정보로 바꿔봤는데, 오늘 남은 실습은 원래 로그로 이어가야 하니 백업으로 되돌려둘게요. 백업이 있으면 이렇게 간단합니다.

Bash
mv access.log.bak access.log

덮어쓰기 전 원본을 .bak으로 남겨둔 덕분에 마음 편히 되돌렸죠. 이게 -i.bak을 붙이는 진짜 이유예요. 이제 access.log는 다시 원래 상태(INFO가 살아 있는)로 돌아왔습니다.

마지막으로 지난 시간 정규식을 여기서 한 번 더 만나요. sed의 "찾을 것" 칸에는 지난 시간에 배운 정규식이 그대로 들어갑니다. 다만 기본 정규식에선 |(또는) 같은 기호에 백슬래시가 필요해서 번거로운데, -E를 붙이면 지난 시간에 본 그 확장 정규식을 그대로 쓸 수 있어요. 400번대 상태 코드(403이나 404)를 한꺼번에 CLIENT_ERR로 바꿔볼게요.

Bash
sed -E 's/(403|404)/CLIENT_ERR/' access.log
텍스트
2026-06-16 10:01:25 ERROR GET /admin CLIENT_ERR 10.0.0.5
2026-06-16 10:01:28 WARN GET /config CLIENT_ERR 10.0.0.5
...
2026-06-16 10:01:44 WARN GET /search CLIENT_ERR 192.168.0.11

403404가 둘 다 CLIENT_ERR로 바뀌었죠. 지난 시간 grep -E에서 봤던 그 |(또는) 표현이 sed에서도 똑같이 통합니다.

💡 한 줄 정리

g를 붙이면 한 줄의 전부를, 안 붙이면 처음 하나만 바꾼다. 파일을 직접 고칠 땐 -i를 쓰되 되돌릴 수 없으니 -i.bak으로 백업을 남기고, 복잡한 패턴은 -E로 확장 정규식을 그대로 쓴다.

🙋 학생 질문 — "튜터님, 그냥 항상 -i.bak 붙이면 .bak 파일이 계속 쌓이지 않나요?"

맞아요, 바꿀 때마다 .bak이 하나씩 생깁니다. 그래서 보통은 이렇게 써요. 변경이 잘 됐는지 확인한 다음, 안심이 되면 rm access.log.bak으로 백업본을 지웁니다. 일이 끝나면 뒷정리를 하는 거죠.

처음엔 번거로워 보여도, 운영 서버에서 중요한 설정 파일을 만질 땐 이 백업 한 줄이 사고를 막아줘요. "백업부터, 그다음 변경, 확인 후 정리" — 이 순서가 몸에 배면 -i가 더는 무섭지 않습니다.


Step 3: "필요 없는 줄은 지우고, 원하는 줄만 — sed로 줄 다루기"

sed는 글자만 바꾸는 게 아니에요. 줄 단위로 지우거나 골라내는 일도 합니다. grep -v로 특정 줄을 빼던 걸 기억하시죠? sed는 거기서 한 발 더 나아가 "이런 줄을 지워라", "몇 번째부터 몇 번째 줄만 보여라"까지 해요.

먼저 줄 삭제예요. s 대신 d(delete)를 쓰고, 앞에 /패턴/을 두면 "그 패턴이 든 줄을 지워라"가 됩니다. 정상 로그(INFO)를 싹 지우고 문제 있는 줄만 남겨볼게요.

Bash
sed '/INFO/d' access.log
텍스트
2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:28 WARN GET /config 404 10.0.0.5
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42
2026-06-16 10:01:36 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:41 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:44 WARN GET /search 404 192.168.0.11

INFO 줄 네 개가 사라지고 ERROR·WARN만 여섯 줄 남았어요. 로그에서 정상 줄을 걷어내고 문제만 보고 싶을 때 딱이죠.

반대로, 원하는 줄만 골라서 보여줄 수도 있어요. 이땐 두 가지가 짝으로 와요. -n(원래 모든 줄을 보여주는 기본 동작을 끄기)과 p(print — 이 줄만 보여줘). 둘을 같이 쓰면 "고른 줄만" 출력됩니다.

Bash
sed -n '/ERROR/p' access.log
텍스트
2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42
2026-06-16 10:01:36 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:41 ERROR GET /admin 403 10.0.0.5

ERROR 줄 네 개만 나왔죠. "어, 이건 grep ERROR랑 똑같은데요?"라고 생각하셨다면 정확해요. 단순히 한 패턴을 찾는 거라면 grep이 더 간단합니다. sed의 진짜 강점은 줄 번호 범위를 다룰 수 있다는 거예요. grep은 못 하는 일이죠.

Bash
sed -n '1,3p' access.log
텍스트
2026-06-16 10:01:22 INFO GET /feed 200 192.168.0.11
2026-06-16 10:01:23 INFO GET /login 200 192.168.0.23
2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5

1,3p는 "1번째부터 3번째 줄까지만"이에요. head -3과 결과는 같지만, sed는 10,20p처럼 중간 구간도 자유롭게 잘라낼 수 있습니다. "로그 파일 한가운데 그 부분만 보고 싶다" 할 때 유용해요.

현업 한 줄 정리하면, sed는 흐름에서 바꾸고(s), 지우고(/d), 골라내는(-n p) 세 가지를 합니다. 여기까지가 sed의 핵심이에요.

💡 한 줄 정리

sed는 치환만이 아니라 줄도 다룬다. /패턴/d로 그 줄을 지우고, -n/패턴/p로 원하는 줄만 골라내며, 1,3p처럼 줄 번호 범위로 중간 구간을 잘라낸다.

🙋 학생 질문 — "튜터님, ERROR 줄만 보는 건 grep으로도 되는데 왜 굳이 sed를 써요?"

좋은 지적이에요. 단순히 "어떤 단어가 든 줄 찾기"라면 grep이 더 짧고 직관적이라 그걸 쓰는 게 맞아요. 도구는 상황에 맞게 고르는 거니까요.

sed가 빛나는 건 grep이 못 하는 일을 할 때예요. 1,3p10,20p처럼 줄 번호 범위로 자르는 것, 그리고 무엇보다 Step 1·2에서 본 찾아서 바꾸기(s///)죠. grep은 "찾기" 전문, sed는 "바꾸기와 줄 편집" 전문이라고 기억해두면, 둘을 언제 골라 쓸지 감이 옵니다. 지난 시간에 말한 "작은 도구를 골라 쓴다"는 유닉스의 철학이 여기서도 이어져요.


Step 4: "공백에 흔들리지 않는 똑똑한 칼 — awk의 필드 처리"

지난 시간 cut으로 칸을 오려낼 때, 작은 약점을 하나 짚고 넘어갔죠. 공백이 여러 칸이면 cut이 빈 칸을 별도 칸으로 세서 번호가 어긋난다고요. 그리고 "그걸 똑똑하게 처리하는 도구는 다음 시간에"라고 미뤄뒀습니다. 그 도구가 바로 awk예요.

awk(만든 사람 세 명 Aho·Weinberger·Kernighan의 머리글자에서 온 이름이에요)는 한 줄을 공백 기준으로 칸칸이 자동으로 잘라서 번호를 매깁니다. 그 칸을 $1(첫째 칸), $2(둘째 칸)처럼 꺼내 써요. 우리 로그 한 줄로 보면 이렇습니다.

텍스트
 awk 가 한 줄을 공백으로 잘라 번호를 매긴다:

   2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5

   $1 = 2026-06-16    (날짜)
   $2 = 10:01:25      (시각)
   $3 = ERROR         (레벨)
   $4 = GET           (메서드)
   $5 = /admin        (경로)
   $6 = 403           (상태코드)
   $7 = 10.0.0.5      (IP)   마지막 칸이라 $NF 로도 가리킨다
   $0 = 줄 전체

$NF가 눈에 띄죠? NF는 칸 개수(Number of Fields)예요. 한 줄에 칸이 7개면 NF는 7, $NF는 곧 $7, 즉 마지막 칸입니다. 경로($5)와 IP($7)만 뽑아볼게요.

Bash
awk '{print $5, $7}' access.log
텍스트
/feed 192.168.0.11
/login 192.168.0.23
/admin 10.0.0.5
/config 10.0.0.5
/upload 192.168.0.42
/feed 192.168.0.11
/admin 10.0.0.5
/profile 192.168.0.23
/admin 10.0.0.5
/search 192.168.0.11

원하는 칸만 골라 나왔죠. cut -f로도 비슷하게 했지만, 여기서 awk의 진짜 강점을 보여드릴게요. 지난 시간 미뤄둔 그 약점, 직접 재현해봅니다. 일부러 공백을 들쭉날쭉하게 넣은 두 줄을 만들어 cut으로 셋째 칸을 뽑아볼게요.

Bash
printf 'INFO    GET   /feed\nERROR  POST  /x\n' | cut -d' ' -f3
텍스트

POST

셋째 칸으로 경로(/feed·/x)가 나와야 하는데, 첫 줄은 텅 빈 칸이, 둘째 줄은 엉뚱하게 POST가 나왔어요. cut이 공백 하나하나를 칸 경계로 세는 바람에 번호가 밀린 거예요. 같은 입력을 awk로 바꿔봅니다.

Bash
printf 'INFO    GET   /feed\nERROR  POST  /x\n' | awk '{print $3}'
텍스트
/feed
/x

awk는 공백이 몇 칸이든 "단어와 단어 사이"로 알아서 묶어, 셋째 칸을 정확히 /feed·/x로 집어냅니다. 이게 지난 시간에 미뤄뒀던 답이에요. 현업에서 ps·df 같은 명령 출력은 보기 좋으라고 공백을 여러 개 넣어 칸을 맞추는 경우가 많은데, 그런 출력을 다룰 땐 cut보다 awk가 훨씬 안전합니다.

칸 말고 줄 번호가 필요할 때도 있어요. NR(Number of Records — 지금까지 읽은 줄 번호)을 쓰면 줄마다 번호를 매겨줍니다.

Bash
awk '{print NR, $5}' access.log
텍스트
1 /feed
2 /login
3 /admin
...
10 /search

💡 한 줄 정리

awk는 한 줄을 공백 기준으로 칸칸이 잘라 $1·$2·$NF(마지막 칸)·$0(줄 전체)로 꺼낸다. 공백이 여러 칸이어도 단어 사이로 알아서 묶어, cut이 약했던 들쭉날쭉한 출력을 정확히 다룬다.

🙋 학생 질문 — "튜터님, $NF는 왜 마지막 칸이 되나요? 그냥 $7 쓰면 안 돼요?"

이 로그는 칸이 항상 7개라 $7이나 $NF나 똑같아요. 그래서 지금은 둘 다 됩니다.

$NF가 빛나는 건 줄마다 칸 수가 다를 때예요. 어떤 줄은 칸이 5개, 어떤 줄은 9개라면, "마지막 칸"을 $7로 콕 집을 수 없죠. 줄마다 다르니까요. 이때 $NF는 줄이 몇 칸이든 항상 마지막을 가리킵니다. "끝에서부터"가 필요할 때 $NF, $(NF-1)(끝에서 둘째)처럼 쓰면 편해요.


Step 5: "조건에 맞는 줄만 골라내기 — awk의 패턴"

awk는 { 할 일 } 앞에 조건(패턴)을 붙이면 "그 조건이 맞는 줄만" 처리해요. grep과 비슷해 보이지만, awk는 칸의 값으로 조건을 걸 수 있어 훨씬 정밀합니다. 셋째 칸이 정확히 ERROR인 줄만 골라볼게요.

Bash
awk '$3=="ERROR"' access.log
텍스트
2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42
2026-06-16 10:01:36 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:41 ERROR GET /admin 403 10.0.0.5

할 일({ })을 안 쓰고 조건만 두면, awk는 "맞는 줄 전체를 출력"합니다. grep ERROR와 결과는 같아 보여도 차이가 있어요. grep은 줄 어디에든 ERROR란 글자가 있으면 잡아요. 만약 경로에 /error-page 같은 게 있으면 그것도 걸리죠. awk $3=="ERROR"는 "셋째 칸(레벨)이 정확히 ERROR인 줄"만 — 위치와 정확한 값을 함께 짚습니다.

숫자 비교도 돼요. 상태코드($6)가 404인 줄만 볼게요.

Bash
awk '$6==404' access.log
텍스트
2026-06-16 10:01:28 WARN GET /config 404 10.0.0.5
2026-06-16 10:01:44 WARN GET /search 404 192.168.0.11

==뿐 아니라 >·<·>=도 됩니다. $6>=400이라고 쓰면 400 이상 상태코드(클라이언트·서버 에러)를 한꺼번에 잡아요. grep으로는 까다로운 "숫자가 얼마 이상" 조건이 awk에선 자연스럽죠.

마지막으로 "거르기 + 칸 뽑기"를 한 번에 해볼게요. ERROR 줄만 골라, 그 줄의 경로와 IP만 꺼냅니다.

Bash
awk '/ERROR/ {print $5, $7}' access.log
텍스트
/admin 10.0.0.5
/upload 192.168.0.42
/admin 10.0.0.5
/admin 10.0.0.5

/ERROR/로 줄을 거르고, {print $5, $7}로 원하는 칸만 뽑았어요. 지난 시간이라면 grep ERROR | cut ...처럼 파이프로 이었을 일을, awk 하나로 끝낸 거예요. 현업 서버 로그에서 "500 에러를 낸 요청의 경로와 IP만" 같은 조건부 추출을 이렇게 한 줄로 합니다.

💡 한 줄 정리

awk{ 할 일 } 앞에 패턴을 붙여 조건에 맞는 줄만 처리한다. $3=="ERROR"처럼 칸의 값으로, $6>=400처럼 숫자로 정밀하게 거르고, /ERROR/ {print $5,$7}로 거르기와 칸 뽑기를 한 번에 한다.

🙋 학생 질문 — "튜터님, 이것도 grep으로 되는데 굳이 awk를 써야 하나요?"

단순히 "이 단어가 든 줄 찾기"라면 grep이 더 짧아서 그걸 쓰는 게 맞아요. 도구는 상황에 맞게 고르는 거니까요.

awk가 강한 건 grep이 까다로워하는 조건일 때예요. "셋째 칸이 정확히 이 값", "여섯째 칸 숫자가 400 이상" 같은 거요. grep은 줄을 글자 덩어리로만 보지만, awk는 칸을 알고 숫자를 비교할 수 있죠. 그래서 로그처럼 칸이 정해진 데이터를 다룰 땐 awk가 빛납니다. 둘을 다 알아두고 상황에 맞게 골라 쓰면 돼요.


Step 6: "세고, 더하고, 한 줄로 집계 — awk의 BEGIN/END와 변수"

여기가 awk의 진짜 힘이에요. awk는 변수를 만들어 줄을 처리하며 값을 쌓을 수 있어요. 그리고 BEGIN { }은 첫 줄을 읽기 전에 한 번, END { }는 마지막 줄을 다 읽은 뒤에 한 번 실행됩니다. 보통 줄을 훑으며 세다가, END에서 결과를 내놓아요. 먼저 ERROR 줄을 세어볼게요.

Bash
awk '/ERROR/ {c++} END {print c}' access.log
텍스트
4

ERROR 줄을 만날 때마다 변수 c를 1씩 늘리고(c++), 다 읽은 뒤(END)에 c를 출력했어요. grep -c ERROR와 결과는 같지만, awk는 여기서 한참 더 나아갑니다. 이번엔 레벨별로 따로 세어볼게요.

Bash
awk '{count[$3]++} END {for (k in count) print count[k], k}' access.log | sort -rn
텍스트
4 INFO
4 ERROR
2 WARN

count[$3]++가 핵심이에요. 셋째 칸 값을 이름표로 삼아 셉니다. INFO를 만나면 count["INFO"]를 1 늘리고, ERROR를 만나면 count["ERROR"]를 늘리고... 종류별로 따로따로 세는 거죠. 이렇게 이름표로 묶어 세는 걸 연관 배열이라고 해요. awk가 종류별 개수를 내놓는 순서는 정해져 있지 않아서, 지난 시간에 배운 sort -rn으로 정렬하면 깔끔합니다. (개수가 같은 INFO·ERROR는 자리가 바뀔 수 있어요.)

이제 지난 시간의 그 문제를 다시 만나요. 마지막에 우리는 "에러를 가장 많이 낸 IP"를 grep → cut → sort → uniq -c → sort -rn, 명령 다섯 개를 파이프로 이어 구했죠. 그걸 awk로 다시 해볼게요.

Bash
awk '$3=="ERROR" {ip[$7]++} END {for (i in ip) print ip[i], i}' access.log | sort -rn
텍스트
3 10.0.0.5
1 192.168.0.42

$3=="ERROR"로 에러 줄만 고르고, 그 줄의 IP($7)를 이름표로 세고, END에서 출력했어요. 10.0.0.5가 3번으로 가장 많네요. 지난 시간 명령 다섯 개로 얻은 그 결과를, 오늘은 awk와 sort 둘로 끝냈습니다. awk가 "거르기 + 칸 뽑기 + 세기"를 한 도구 안에서 다 해주니까요.

그리고 awk는 계산도 합니다. 전체 줄 중 에러가 몇 퍼센트인지 구해볼게요.

Bash
awk '/ERROR/ {e++} END {printf "%d/%d = %.0f%%\n", e, NR, e/NR*100}' access.log
텍스트
4/10 = 40%

NR은 마지막에 다다르면 "전체 줄 수"가 돼요(지금까지 읽은 줄 번호니까요). 에러 4줄 ÷ 전체 10줄 = 40%. printf로 보기 좋게 출력했고요. grep·sort로는 못 하던 "계산"이 awk에선 이렇게 됩니다. 현업에선 로그에서 에러율·IP별 요청 수·평균 응답 시간 같은 집계를 awk 한 줄로 뽑아 서버 상태를 살펴요.

💡 한 줄 정리

awk는 변수와 연관 배열로 줄을 세고(count[$3]++), END에서 결과를 내며, printf로 계산까지 한다. 지난 시간 명령 다섯 개로 하던 "에러 IP 집계"를 awk와 sort 둘로 끝낸다.

🙋 학생 질문 — "튜터님, BEGIN은 안 썼는데 언제 쓰나요?"

오늘은 END만 썼지만, BEGIN은 "줄을 읽기 전 준비"에 써요. 예를 들어 결과 표의 머리글을 먼저 찍거나(BEGIN {print "IP 횟수"}), 입력을 나누는 기준을 바꿀 때 씁니다.

특히 마지막 게 유용해요. awk는 기본으로 공백 기준으로 칸을 나누는데, BEGIN {FS=","}라고 하면 쉼표 기준으로 나눠요. 그래서 쉼표로 칸을 나눈 CSV 파일을 awk로 가공할 수 있습니다. "CSV 가공"이 awk로 잘 되는 이유가 바로 이거예요.


마무리

오늘은 텍스트를 "읽고 뽑기"에서 "바꾸고 집계하기"로 한 단계 올라섰어요. sed로 흐름에서 글자를 바꾸고, awk로 칸을 똑똑하게 다뤄 패턴별로 세고 계산까지 했죠. 지난 시간 명령 다섯 개로 하던 일을 awk 한 줄로 끝내는 걸 보니 어떤가요? 핵심을 세 가지로 정리할게요.

오늘 배운 핵심 세 가지

💡 하나sed는 흐름 편집기다. s/old/new/로 바꾸되 g를 붙이면 한 줄 전부, 안 붙이면 처음 하나. 파일을 직접 고칠 땐 -i.bak으로 백업부터(휴지통이 없으니까). 줄은 /패턴/d로 지우고 -n + /패턴/p·1,3p로 골라낸다.

💡 awk는 한 줄을 공백으로 칸칸이 잘라 $1·$NF로 꺼낸다. 공백이 여러 칸이어도 단어 사이로 알아서 묶어, 지난 시간 cut이 약했던 들쭉날쭉한 출력을 정확히 다룬다.

💡 awk는 패턴($3=="ERROR")으로 거르고, 변수·연관 배열(count[$3]++)로 세고, ENDprintf로 집계·계산한다. "에러 IP가 누가 몇 번"을 awk와 sort 둘로 끝낸다.

다음 시간 예고

오늘까지 텍스트를 찾고, 뽑고, 바꾸고, 집계하는 게 자유로워졌어요. 그런데 한 가지 — 이 모든 걸 sed·awk는 "자동으로, 한꺼번에" 해줬죠. 만약 파일 하나를 열어 두세 줄만 손으로 직접 고치고 싶다면 어떨까요? 설정 파일에 새 항목을 한 줄 추가하거나, 오타 한 글자를 바꾸려 파일 안으로 들어가야 할 때요.

그게 다음 시간(A-6)의 주제, 편집기예요. 그리고 현업 신입이 서버에서 한 번씩 꼭 겪는 그 순간 — vim을 잘못 열었다가 빠져나오지 못해 식은땀 흘리는 그 상황도 다음 시간에 풀어드립니다. vim이 왜 그렇게 생겼는지, 모드가 뭔지, 무엇보다 갇혔을 때 무사히 빠져나오는 법부터 차근차근 배울 거예요. :q! 하나만 알아도 안 무서워지니 너무 걱정 마세요. 그럼 다음 시간에 만나요.


과제

오늘 과제도 전부 여러분 자신의 리눅스 환경(WSL2·맥 터미널·VM)에서 직접 쳐보는 실습이에요. sed·awk는 눈으로 읽으면 다 아는 것 같다가도 막상 치면 헷갈립니다. 한 단계씩 출력이 어떻게 바뀌는지 확인하며 천천히 가세요. 교안에서 쓴 ~/practice/logs/access.log를 그대로 쓰면 됩니다.

[기초] sed로 바꾸고 골라내기

sed의 치환과 줄 다루기를 직접 쳐보며 연습해보세요.

  • echo "GET GET GET"sed에 넘겨, g 없이 한 번 / g를 붙여 한 번 쳐보고 결과가 어떻게 다른지 직접 비교한다
  • access.log에서 GET요청으로 바꿔 화면에 출력한 뒤, grep -c GET access.log원본은 그대로인지 확인한다
  • sed -n '5,7p' access.log로 5~7번째 줄만 뽑아본다
  • access.logtest.log로 복사한 다음, sed -i.bak 's/200/OK/g' test.log로 직접 고쳐보고, test.logtest.log.bak이 어떻게 다른지 cat으로 각각 열어 확인한다. 마지막에 mv test.log.bak test.log로 되돌려본다

[응용] awk로 칸을 뽑고 조건으로 거르기

awk의 필드 처리와 패턴을 연습해보세요.

  • awk '{print $3, $6}' access.log로 레벨과 상태코드 두 칸만 뽑는다
  • awk '$6>=400' access.log로 상태코드가 400 이상인 "문제 있는 요청"만 골라낸다
  • 공백을 일부러 두세 칸씩 넣은 줄을 printf로 만들어, 같은 칸을 cut -d' 'awk '{print $N}'으로 각각 뽑아 누가 어긋나고 누가 멀쩡한지 눈으로 비교한다
  • awk '$3=="WARN" {print $5}' access.log로 경고가 난 경로만 뽑아본다

[심화] awk로 서버 로그를 집계하라 (서버 상황 상상)

서버 상황을 상상해봅시다. "운영 로그를 받았다. 어떤 IP가 얼마나 들어왔고, 어떤 상태코드가 많고, 에러 비율은 얼마인지 한눈에 보고하고 싶다." 지난 시간엔 이런 집계를 명령 여러 개를 파이프로 이어 구했죠. 오늘은 awk로 다시, 그리고 한 발 더 나아가 봅니다.

  • awk의 연관 배열로 IP별 전체 요청 수(에러만이 아니라 전부)를 세고, sort -rn으로 많은 순으로 정렬한다
  • 같은 방식으로 상태코드별 개수(200·403·404·500이 각각 몇 건)를 집계한다
  • awkENDprintf전체 요청 대비 에러(ERROR) 비율을 퍼센트로 출력한다
  • 마지막으로, "이 집계만 보고 10.0.0.5를 차단 대상으로 단정해도 될까? 더 확인해야 할 게 있다면 무엇일까"를 두세 문장으로 적어본다

생각해볼 주제

1. sed -i는 왜 편하면서도 무서운가

오늘 sed -i 한 줄로 파일 수십 개의 글자를 한꺼번에 바꿀 수 있다는 걸 봤습니다. 엄청나게 편하죠. 그런데 동시에, 한 번 잘못 치면 그 수십 개가 전부 잘못 덮어써집니다. 리눅스엔 휴지통이 없어 되돌릴 수도 없고요. 이 "강력함"과 "위험함"이 사실 같은 동전의 양면이라는 점을 생각해보세요. 우리가 배운 -i.bak 백업 습관은 이 위험을 어떻게 줄여주나요? 그리고 만약 백업조차 없이 운영 서버의 중요한 설정 파일에 sed -i를 바로 쳐야 하는 상황이라면, 그 전에 어떤 안전장치를 스스로 만들 수 있을지 떠올려보세요.

2. cut이 있는데 왜 awk도 배울까

오늘 awk가 공백에 흔들리지 않고 칸을 정확히 집어내는 걸 보며, "그럼 cut은 이제 안 써도 되는 거 아냐?"라고 생각했을 수 있어요. 그런데 현업 개발자들은 둘 다 도구함에 둡니다. 단순히 "정확히 한 칸으로 나뉜 데이터에서 한 칸 빼기"라면 cut이 더 짧고 빠르거든요. 더 단순한 도구와 더 강력한 도구가 함께 있을 때, "더 강력한 게 있으니 단순한 건 버려도 된다"가 아니라 "상황에 맞게 고른다"가 왜 더 나은 태도인지 생각해보세요. 여러분이 아는 다른 도구 중에도 이런 "단순함 대 강력함"의 짝이 있는지 떠올려보면 재밌을 거예요.

3. awk는 어디까지 하고, 어디서 멈춰야 할까

오늘 awk로 세고, 조건을 걸고, 퍼센트를 계산하는 것까지 봤어요. 사실 awk는 변수·조건·반복까지 갖춘, 거의 작은 프로그래밍 언어에 가깝습니다. 그러다 보니 "이것도 awk로, 저것도 awk로" 욕심이 날 수 있어요. 그런데 집계 로직이 점점 복잡해지면(여러 파일을 합치고, 조건이 수십 개로 갈라지고, 결과를 다시 가공하고...) 한 줄짜리 awk는 읽기 어려운 암호가 되어버립니다. "한 줄 awk로 끝내는 게 깔끔한 일"과 "차라리 파이썬 같은 언어로 넘어가는 게 나은 일"의 경계가 어디쯤일지 가늠해보세요. 도구를 잘 쓰는 것만큼, 도구를 멈출 때를 아는 것도 실력이라는 점을 함께 생각해보면 좋겠습니다.

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안입니다. 모든 명령은 여러분 자신의 환경에서 직접 쳐보며 확인하는 게 가장 중요해요. 파일 줄 수나 IP 같은 숫자가 책과 조금 달라도 정상이니, 흐름이 맞는지를 보면 됩니다. sed·awk는 한 번에 외우려 하지 말고, 출력이 바뀌는 걸 눈으로 따라가며 한 단계씩 손에 붙이는 리듬이 핵심이에요.


🎯 [과제 1 예시답안] sed로 바꾸고 골라내기

채점 포인트

확인 항목 무엇을 보면 되는가
g의 차이 g 없이는 줄마다 처음 하나, g를 붙이면 한 줄 전부가 바뀌는 걸 직접 비교했는가
원본 보존 치환 결과를 화면으로 보고도 원본 access.log는 그대로인 걸 grep -c로 확인했는가
줄 범위 sed -n '5,7p'로 가운데 구간만 잘라냈는가
-i.bak 백업 -i.bak이 원본을 .bak으로 남기고 파일을 고치는 걸 보고, mv로 되돌렸는가

풀이 예시

Bash
# g 없이 / g 붙여 — 처음 하나 vs 한 줄 전부
$ echo "GET GET GET" | sed 's/GET/요청/'
요청 GET GET
$ echo "GET GET GET" | sed 's/GET/요청/g'
요청 요청 요청
Bash
# 로그에서 GET을 요청으로 — 화면에만 나오고 원본은 안 바뀐다
$ sed 's/GET/요청/' access.log | head -5
2026-06-16 10:01:22 INFO 요청 /feed 200 192.168.0.11
2026-06-16 10:01:23 INFO 요청 /login 200 192.168.0.23
2026-06-16 10:01:25 ERROR 요청 /admin 403 10.0.0.5
2026-06-16 10:01:28 WARN 요청 /config 404 10.0.0.5
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42

# 원본 확인 — GET이 9줄 그대로 (POST 줄 하나만 GET이 없다)
$ grep -c GET access.log
9
Bash
# 5~7번째 줄만 — head/tail로는 어려운 가운데 구간
$ sed -n '5,7p' access.log
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42
2026-06-16 10:01:33 INFO GET /feed 200 192.168.0.11
2026-06-16 10:01:36 ERROR GET /admin 403 10.0.0.5
Bash
# 복사본을 직접 고쳐보고, 백업으로 되돌리기
$ cp access.log test.log
$ sed -i.bak 's/200/OK/g' test.log
$ ls test.log*
test.log  test.log.bak
$ grep -c OK test.log        # 바뀐 파일엔 OK가 4개
4
$ grep -c 200 test.log.bak   # 백업엔 원래 200이 그대로
4
$ mv test.log.bak test.log   # 백업으로 되돌리기 — .bak이 있어 안심

💡 튜터의 한마디-i는 강력한 만큼 되돌릴 수 없어요. 신입 때 운영 서버의 설정 파일에 sed -i를 바로 쳤다가, 한 글자 잘못 써서 파일 전체가 엉망이 되는 사고가 정말 흔합니다. 리눅스엔 휴지통이 없으니, "백업부터(-i.bak), 그다음 변경, 확인하고 정리" 이 순서를 몸에 붙여두면 sed -i가 더는 무섭지 않아요.

🎯 [과제 2 예시답안] awk로 칸을 뽑고 조건으로 거르기

채점 포인트

확인 항목 무엇을 보면 되는가
칸 뽑기 $3·$6로 원하는 두 칸만 골라 나왔는가
숫자 조건 $6>=400으로 문제 있는 요청만 걸러냈는가
cut의 약점 공백이 여러 칸일 때 cut은 어긋나고 awk는 정확한 걸 눈으로 비교했는가
거르기+뽑기 $3=="WARN" {print $5}로 거르기와 칸 뽑기를 한 번에 했는가

풀이 예시

Bash
# 레벨과 상태코드 두 칸만
$ awk '{print $3, $6}' access.log | head -4
INFO 200
INFO 200
ERROR 403
WARN 404
Bash
# 상태코드 400 이상 — 클라이언트·서버 에러만
$ awk '$6>=400' access.log
2026-06-16 10:01:25 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:28 WARN GET /config 404 10.0.0.5
2026-06-16 10:01:30 ERROR POST /upload 500 192.168.0.42
2026-06-16 10:01:36 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:41 ERROR GET /admin 403 10.0.0.5
2026-06-16 10:01:44 WARN GET /search 404 192.168.0.11
Bash
# cut의 약점 재현 — 공백을 일부러 여러 칸씩 넣은 줄
$ printf 'INFO    GET   /feed\nERROR  POST  /x\n' | cut -d' ' -f3

POST
# 셋째 칸으로 경로가 나와야 하는데, cut은 빈 칸을 세느라 어긋난다

# 같은 입력을 awk로 — 공백이 몇 칸이든 정확히 셋째 칸
$ printf 'INFO    GET   /feed\nERROR  POST  /x\n' | awk '{print $3}'
/feed
/x
Bash
# 경고(WARN)가 난 경로만 — 거르기와 칸 뽑기를 한 번에
$ awk '$3=="WARN" {print $5}' access.log
/config
/search

💡 튜터의 한마디grep은 줄을 글자 덩어리로 보고, awk는 칸을 알고 숫자를 비교해요. 그래서 "이 단어 든 줄"은 grep, "셋째 칸이 정확히 이 값"이나 "상태코드 400 이상"은 awk가 편합니다. 특히 ps·df 같은 명령 출력은 보기 좋으라고 공백을 여러 개 넣어 칸을 맞추는데, 그런 출력을 다룰 땐 cut보다 awk가 안전하다는 걸 기억해두세요.

🎯 [과제 3 예시답안] awk로 서버 로그를 집계하라

채점 포인트

확인 항목 무엇을 보면 되는가
IP별 집계 연관 배열로 IP별 전체 요청 수를 세고 sort -rn으로 정렬했는가
상태코드 집계 같은 방식으로 상태코드별 개수를 냈는가
계산 ENDprintf로 에러 비율을 퍼센트로 출력했는가
판단의 신중함 집계만으로 차단을 단정하지 않고, 더 확인할 점을 적었는가

풀이 예시

Bash
# IP별 전체 요청 수 — 많은 순으로
$ awk '{ip[$7]++} END {for (i in ip) print ip[i], i}' access.log | sort -rn
4 10.0.0.5
3 192.168.0.11
2 192.168.0.23
1 192.168.0.42
Bash
# 상태코드별 개수
$ awk '{code[$6]++} END {for (c in code) print code[c], c}' access.log | sort -rn
4 200
3 403
2 404
1 500
Bash
# 에러 비율 계산
$ awk '/ERROR/ {e++} END {printf "ERROR %d / 전체 %d = %.0f%%\n", e, NR, e/NR*100}' access.log
ERROR 4 / 전체 10 = 40%

마지막 판단을 적어보면 이런 식이에요.

"10.0.0.5가 4번으로 요청이 가장 많고, 그중 에러도 많다. 하지만 이 10줄은 아주 짧은 시간의 기록이라, 이것만으로 공격자라 단정하긴 이르다. ① 더 긴 기간의 로그에서도 같은 패턴인지, ② 정상 사용자가 잠깐 몰린 것은 아닌지, ③ 같은 IP가 다른 정상 요청도 많이 했는지를 함께 봐야 한다. 차단은 그 뒤에 결정한다."

💡 튜터의 한마디 — 집계 결과는 "단서"지 "결론"이 아니에요. awk 한 줄로 현황을 빠르게 파악하는 건 정말 강력하지만, 거기에 사람의 판단을 한 겹 더 얹어야 합니다. 숫자가 가리키는 걸 보되 곧바로 단정하지 않는 태도가, 현업에서 신뢰받는 엔지니어의 자세예요.


🤔 [생각해볼 주제 1] sed -i는 왜 편하면서도 무서운가

문제 상황 요약

sed -i 한 줄이면 파일 수십 개의 글자를 한꺼번에 바꿀 수 있다. 엄청나게 편하다. 그런데 한 번 잘못 치면 그 수십 개가 전부 잘못 덮어써지고, 리눅스엔 휴지통이 없어 되돌릴 수도 없다. 이 강력함과 위험함은 어떻게 다스려야 할까.

튜터의 가이드 및 해설

핵심은 "강력함과 위험함이 같은 동전의 양면"이라는 점이에요. sed -i가 한 번에 수십 개를 바꿀 수 있다는 건, 한 번의 실수도 수십 개로 번진다는 뜻이거든요. 도구의 힘이 셀수록 안전장치도 같이 키워야 합니다.

우리가 배운 -i.bak이 1차 안전망이에요. 덮어쓰기 전 원본을 남겨두니, 잘못돼도 되돌릴 수 있죠. 하지만 더 좋은 습관들이 있어요. ① 먼저 -i 없이 그냥 쳐서 화면으로 결과를 미리 확인하고, "이대로 바뀌는 게 맞다" 싶을 때만 -i를 붙입니다. ② 운영 파일이라면 적은 수의 파일이나 복사본에 먼저 시험해 보고요. ③ 요즘은 설정 파일을 Git 같은 버전 관리로 두는 곳이 많아, 잘못 바꿔도 이전 버전으로 돌아갈 수 있게 해둡니다.

겁먹으라는 얘기가 아니에요. sed -i는 현업에서 정말 자주, 아주 유용하게 씁니다. 다만 "힘센 도구일수록 치기 전에 한 박자 확인한다"는 리듬이 몸에 배면, 그 힘을 마음껏 쓰면서도 사고를 피할 수 있어요.

💡 핵심을 한마디로

"sed -i의 힘은 백업과 미리 확인하는 습관과 한 세트일 때만 안전하다. 도구가 강력할수록, 치기 전 한 박자가 더 중요하다."

🤔 [생각해볼 주제 2] cut이 있는데 왜 awk도 배울까

문제 상황 요약

awk가 공백에 흔들리지 않고 칸을 정확히 집어내는 걸 봤다. 그럼 cut은 이제 안 써도 되는 것 아닐까? 그런데 현업 개발자들은 둘 다 도구함에 둔다. 왜일까.

튜터의 가이드 및 해설

"더 강력한 게 나왔으니 약한 건 버린다"가 늘 옳은 건 아니라는 걸 보여주는 좋은 예예요. cut은 단순합니다. "정확히 한 글자로 나뉜 데이터에서 몇 번째 칸"을 빼는 거라면, cut -d',' -f2처럼 짧고 직관적이라 오히려 awk보다 빠르게 손이 가요. CSV처럼 구분자가 깔끔한 데이터엔 cut이 제격이죠.

awk는 강력하지만 그만큼 문법이 조금 더 무거워요. 공백이 들쭉날쭉하거나, 칸 값으로 조건을 걸거나, 세고 계산해야 할 때 awk를 꺼냅니다. 즉 둘은 경쟁자가 아니라 쓰임이 다른 짝이에요. 망치와 드라이버처럼요.

여기서 더 큰 교훈은 "도구는 상황에 맞게 고른다"는 태도예요. 신입 때는 "제일 강력한 도구 하나만 마스터하면 되겠지" 싶지만, 실력이 붙을수록 "이 상황엔 이게 더 간단하네"를 아는 눈이 생깁니다. 단순한 도구를 우습게 보지 않는 것, 그게 도구를 잘 쓰는 사람의 특징이에요.

💡 핵심을 한마디로

"더 강력한 도구가 단순한 도구를 항상 대체하진 않는다. cut은 단순함으로, awk는 강력함으로 각자의 자리가 있고, 잘하는 사람은 상황에 맞게 고른다."

🤔 [생각해볼 주제 3] awk는 어디까지 하고, 어디서 멈춰야 할까

문제 상황 요약

awk로 세고, 조건을 걸고, 퍼센트를 계산하는 것까지 봤다. awk는 변수·조건·반복까지 갖춘, 거의 작은 프로그래밍 언어다. 그러다 보니 "이것도 awk로, 저것도 awk로" 욕심이 난다. 어디까지 awk로 하고, 어디서 멈춰야 할까.

튜터의 가이드 및 해설

awk가 작은 언어에 가깝다는 건 양날의 검이에요. 한 줄짜리 집계는 awk만큼 빠르고 깔끔한 게 없어요. 로그에서 IP별 횟수, 에러율 같은 걸 한 줄로 뽑는 건 awk의 전성기죠.

문제는 로직이 복잡해질 때예요. 여러 파일을 합치고, 조건이 수십 가지로 갈라지고, 결과를 다시 가공해 또 다른 형식으로 내보내야 한다면, 한 줄짜리 awk는 점점 길어지다가 결국 나도 남도 못 읽는 암호가 됩니다. 한 달 뒤 내가 봐도 "이게 뭐였지?" 싶어지죠.

판단 기준을 하나 드리면, "이 awk 한 줄을 옆 사람에게 보여줬을 때 한눈에 이해되는가"예요. 그 선을 넘어 길고 복잡해지면, 차라리 파이썬 같은 언어로 옮기는 게 읽기도 고치기도 쉬워요. 도구를 잘 쓰는 것만큼, 도구를 멈출 때를 아는 것도 실력입니다. "한 줄로 끝나면 awk가 최고, 그 선을 넘으면 언어로" — 이 감각을 키워두면 됩니다.

💡 핵심을 한마디로

"한 줄로 끝나는 집계는 awk가 최고지만, 남이 못 읽을 만큼 길어지면 그건 awk를 멈추고 프로그래밍 언어로 넘어갈 신호다."

전체 목록 리눅스