카테고리 없음

웹 응답에서 원하는 것만 뽑아내기 - grep, sed, tr 조합

바뿌 2026. 10. 23. 17:23

curl로 받은 HTML은 수만 글자입니다. 여기서 제목 하나, 개수 하나를 꺼내려면
파이프로 몇 개를 엮으면 됩니다. 별도 도구를 설치할 필요가 없습니다.

결론부터 — 이 네 개 조합이면 웬만한 건 됩니다

grep -o '패턴'        # 매치된 부분만 출력 (줄 전체가 아니라)
grep -c '패턴'        # 개수만 세기
sed 's/<[^>]*>//g'    # HTML 태그 제거
tr -s ' \n' ' '       # 중복 공백·줄바꿈 압축

grep -o — 이게 핵심입니다

보통 grep은 매치된 줄 전체를 보여줍니다. HTML은 한 줄이 수천 글자라 쓸모가 없습니다.
-o를 붙이면 매치된 부분만 나옵니다.

# 제목 뽑기
curl -s https://example.com/ | grep -o '<title>[^<]*</title>'
# <title>사이트 제목</title>

# 태그까지 벗기려면 sed를 붙인다
curl -s https://example.com/ | grep -o '<title>[^<]*</title>' | sed 's/<[^>]*>//g'
# 사이트 제목

[^<]* 는 "<가 아닌 문자들" 이라는 뜻입니다. 여는 태그 다음부터 닫는 태그 직전까지만 집어냅니다. HTML에서 값을 뽑을 때 가장 많이 쓰는 패턴입니다.

grep -c — 세기

# 사이트맵에 URL이 몇 개인지
curl -s https://example.com/sitemap.xml | grep -c '<loc>'

# 특정 스크립트가 들어있는지 (0이면 없음)
curl -s https://example.com/ | grep -c 'analytics'

⚠️ grep -c 는 매치된 "줄" 수를 셉니다.
HTML이 한 줄로 뭉쳐 있으면 10개가 있어도 1이 나옵니다.
정확히 세려면 grep -o 로 쪼갠 뒤 wc -l 을 씁니다.

curl -s https://example.com/ | grep -o 'adsbygoogle' | wc -l

이 차이 때문에 개수를 잘못 세는 일이 흔합니다.

sed — 태그 지우고 정리하기

# 모든 HTML 태그 제거
sed 's/<[^>]*>//g'

# 스크립트 블록까지 통째로 제거하고 본문만
curl -s https://example.com/ \
  | sed 's/<script[^>]*>.*<\/script>//g; s/<[^>]*>/ /g' \
  | tr -s ' \n' ' '

s/찾을것/바꿀것/g 형태입니다. g는 "한 줄에서 여러 번 나와도 전부"라는 뜻입니다.

tr — 공백 정리

태그를 지우면 공백과 빈 줄이 잔뜩 남습니다.

tr -s ' \n' ' '     # 연속된 공백·줄바꿈을 하나로 압축
tr -d '\r'          # 윈도우 줄바꿈 문자 제거
tr '\n' ' '         # 줄바꿈을 공백으로

-s는 squeeze, 반복을 하나로 줄입니다.

curl -sI 로 헤더를 받으면 줄 끝에 \r이 붙어 있습니다.
비교나 조합이 이상하게 동작하면 tr -d '\r' 를 의심하세요.

실전 조합

사이트맵에서 글 제목만 (한글 디코딩까지)

curl -s https://example.com/sitemap.xml \
  | grep -o '<loc>[^<]*entry[^<]*</loc>' \
  | sed 's/<[^>]*>//g' \
  | grep -v '/m/' \
  | python3 -c "
import sys, urllib.parse
for l in sys.stdin:
    print(urllib.parse.unquote(l.strip()).split('/entry/')[-1].replace('-',' '))
"

한글 주소는 %EB%8D%B0... 로 인코딩돼 있어서 디코딩해야 읽힙니다.

여러 사이트를 표처럼 점검

for h in a.example.com b.example.com c.example.com; do
  page=$(curl -s -H 'Cache-Control: no-cache' "https://$h/?nc=$$")
  printf "%-24s HTTP=%s 제목=%s\n" "$h" \
    "$(curl -s -o /dev/null -w '%{http_code}' https://$h/)" \
    "$(echo "$page" | grep -o '<title>[^<]*</title>' | sed 's/<[^>]*>//g')"
done

printf로 폭을 지정하면 세로줄이 맞아서 한눈에 들어옵니다. %-24s는 "왼쪽 정렬 24칸"입니다.

저는 grep -c 때문에 개수를 잘못 세고 있었습니다.

여러 사이트에 특정 스크립트가 들어있는지 확인하는 작업이었는데, grep -c로 세니 전부 1이 나왔습니다. "하나씩 들어있구나" 하고 넘어갔습니다.

나중에 grep -o ... | wc -l로 다시 세어보니 어떤 곳은 10개, 어떤 곳은 1개로 제각각이었습니다. HTML이 한 줄로 뭉쳐 있어서 grep -c가 "그 줄 하나"만 센 것이었습니다.

숫자가 나왔다고 맞는 게 아니었습니다. 셈의 단위가 무엇인지를 확인해야 했습니다.

주의 — 이 방법의 한계

HTML 파싱을 정규식으로 하는 건 원칙적으로 옳지 않습니다.
중첩 구조나 속성 안의 꺾쇠 같은 예외에서 깨집니다.

  • 일회성 확인·진단에는 충분합니다. 빠르고 설치할 게 없습니다
  • 반복 실행되는 스크립트나 정확성이 중요한 작업에는 제대로 된 파서를 쓰세요
    (Python BeautifulSoup, jq(JSON), xmllint(XML) 등)

JSON이라면 jq가 훨씬 편합니다.

curl -s https://example.com/api/items | jq '.items[].name'

정리

  • grep -o 로 매치된 부분만 뽑습니다. [^<]* 패턴을 기억하세요
  • grep -c는 줄 수를 셉니다. 정확히 세려면 grep -o ... | wc -l
  • sed 's/<[^>]*>//g' 로 태그를 벗깁니다
  • tr -s 로 공백을 정리하고, 헤더를 다룰 땐 tr -d '\r'
  • 진단용으로만 쓰세요. 반복 실행되는 코드에는 제대로 된 파서를