"모든 것은 파일"만큼 중요한 것이 "모든 것은 텍스트"라는 사고방식이다. 그 출발점은 파일 내용을 화면에 출력하는 일이다.
파이프와 표준 스트림. 모든 명령은 표준 입력(stdin), 표준 출력(stdout), 표준 에러(stderr) 세 통로를 가진다. 파이프 |는 한 명령의 stdout을 다음 명령의 stdin으로 연결한다. cat log | grep error | sort처럼 데이터가 흐르는 컨베이어 벨트를 만드는 것이다. 이 단순한 연결이 텍스트 처리의 거의 모든 마법을 가능하게 한다.
cat file.txt # 파일 전체 내용 출력cat -n file.txt # 행 번호와 함께 출력cat f1.txt f2.txt # 여러 파일을 이어붙여 출력(concatenate)cat -A file.txt # 비출력 문자(탭, 줄끝)까지 표시head file.txt # 처음 10행 출력 (기본값)head -n 5 file.txt # 처음 5행 출력head -c 100 file.txt # 처음 100바이트 출력tail file.txt # 마지막 10행 출력tail -n 20 file.txt # 마지막 20행 출력tail -f /var/log/syslog # 파일에 추가되는 내용을 실시간으로 따라가기(follow)
cat은 본래 concatenate(이어붙이기)의 약자이다.
작은 파일을 빠르게 확인할 때 좋지만, 거대한 파일을 cat으로 출력하면 화면이 폭주한다.
그럴 때는 처음 일부만 보는 head나 끝부분만 보는 tail을 쓴다.
tail -f의 마법.tail -f(follow)는 로그 모니터링의 필수 도구이다. 파일을 열어둔 채 새로운 줄이 추가될 때마다 자동으로 화면에 표시한다. 서버 로그를 실시간으로 지켜볼 때 끝없이 활용된다. 비슷하게 tail -F(대문자)는 파일이 로테이션(삭제 후 재생성)되어도 다시 추적을 이어가므로 운영 환경에서 더 안전하다.
grep(global regular expression print)은 텍스트에서 패턴과 일치하는 행을 찾아 출력한다. 리눅스에서 가장 많이 쓰이는 명령 중 하나이다.
grep "error" log.txt # error를 포함한 행 출력grep -i "error" log.txt # 대소문자 무시(ignore case)grep -v "debug" log.txt # debug를 포함하지 않는 행(invert) 출력grep -n "error" log.txt # 행 번호와 함께 출력grep -c "error" log.txt # 일치하는 행의 개수만 출력(count)grep -r "TODO" ./src # 디렉터리 내 모든 파일 재귀 검색(recursive)grep -l "error" *.log # 일치하는 내용을 가진 파일 이름만 출력grep -w "is" text.txt # 단어 단위로 정확히 일치(word)grep -A 2 "error" log.txt # 일치 행과 그 뒤(After) 2행 함께 출력grep -B 2 "error" log.txt # 일치 행과 그 앞(Before) 2행 함께 출력grep -E "cat|dog" pets.txt # 확장 정규식(egrep 동등)으로 OR 검색
grep -E는 확장 정규표현식(ERE)을 사용하며, 별도의 명령 egrep과 동일하다.
grep -F는 정규식을 무시하고 고정 문자열로 검색하며 fgrep과 같다.
grep이라는 이상한 이름.grep은 원조 텍스트 에디터 ed의 명령 g/re/p에서 유래했다. 이는 "global - regular expression - print"의 약자로, "파일 전체에서 정규식과 일치하는 행을 출력하라"는 뜻이다. 너무 자주 쓰이다 보니 영어권에서는 "grep"이 "검색하다"라는 동사로 쓰일 정도이다.
sed(stream editor)는 텍스트 스트림을 한 줄씩 읽으면서 편집 명령을 적용한다. 가장 흔한 용도는 문자열 치환이다.
sed 's/old/new/' file.txt # 각 행의 첫 번째 old를 new로 치환sed 's/old/new/g' file.txt # 각 행의 모든 old를 new로 치환(global)sed 's/old/new/2' file.txt # 각 행의 두 번째 old만 치환sed -n '5p' file.txt # 5번째 행만 출력 (-n은 자동 출력 끔)sed -n '2,5p' file.txt # 2~5행 출력sed '3d' file.txt # 3번째 행 삭제sed '/error/d' file.txt # error를 포함한 행 삭제sed -i 's/old/new/g' file.txt # 파일을 직접 수정(in-place)sed 's/^/> /' file.txt # 각 행 맨 앞에 "> " 추가
치환 문법 s/패턴/대체/플래그가 핵심 문법이다.
s는 substitute(치환), /는 구분자, 마지막 g(global) 플래그는 행 내 모든 일치를 치환하라는 의미이다.
⚠️ 함정: sed -i는 되돌릴 수 없다
기본적으로 sed는 결과를 화면에만 출력하고 원본 파일은 건드리지 않는다. 하지만 -i(in-place) 옵션을 붙이면 원본 파일을 직접 덮어쓴다. 실수하면 복구가 어려우므로, 중요한 파일은 sed -i.bak처럼 백업 확장자를 지정해 원본 사본을 남기는 습관이 안전하다.
awk는 단순한 명령이 아니라 작은 프로그래밍 언어이다. 텍스트를 필드(열) 단위로 처리하는 데 특화되어 있으며, 기본 형식은 awk '패턴 {동작}'이다.
awk '{print $1}' file.txt # 각 행의 첫 번째 필드 출력awk '{print $1, $3}' file.txt # 1번째와 3번째 필드 출력awk '{print $NF}' file.txt # 마지막 필드 출력(NF=필드 개수)awk -F: '{print $1}' /etc/passwd # 구분자를 :로 지정(-F)awk '{print NR, $0}' file.txt # 행 번호(NR)와 전체 행($0) 출력awk '$3 > 100 {print $1}' data.txt # 3번째 필드가 100 초과인 행의 1열awk '/error/ {print $2}' log.txt # error 패턴 일치 행의 2번째 필드awk 'END {print NR}' file.txt # 전체 행 수 출력awk '{sum += $1} END {print sum}' n.txt # 1열 합계 계산
$0은 행 전체, $1, $2...는 각 필드를 의미한다.
기본 구분자는 공백(스페이스, 탭)이며 -F 옵션으로 바꿀 수 있다.
특별 변수 NR(Number of Record)은 현재 행 번호, NF(Number of Field)는 현재 행의 필드 개수이다.
awk의 BEGIN과 END 블록.awk에는 특별한 두 패턴이 있다. BEGIN { }은 입력을 읽기 전에 한 번 실행되고, END { }은 모든 입력을 처리한 후 한 번 실행된다. 그래서 awk 'BEGIN{print "시작"} {print} END{print "끝"}'처럼 헤더/푸터를 붙이거나, END 블록에서 누적된 합계를 출력하는 통계 처리에 활용된다.
sort file.txt # 알파벳 순(사전식) 정렬sort -r file.txt # 역순 정렬(reverse)sort -n numbers.txt # 숫자 순 정렬(numeric)sort -k 2 data.txt # 2번째 필드(키) 기준 정렬sort -t: -k 3 -n /etc/passwd # 구분자 :, 3번째 필드 숫자 정렬sort -u file.txt # 정렬하면서 중복 제거(unique)uniq file.txt # 연속된 중복 행 제거uniq -c file.txt # 중복 개수와 함께 출력(count)uniq -d file.txt # 중복된 행만 출력(duplicated)uniq -u file.txt # 중복 없는 유일한 행만 출력
⚠️ 함정: uniq는 sort와 짝꿍이다uniq는 연속된 중복 행만 제거한다. 떨어져 있는 중복은 인식하지 못한다. 그래서 거의 항상 sort | uniq 형태로 함께 쓴다. 먼저 정렬해 같은 행을 모은 뒤 uniq로 중복을 제거하는 것이다. 단순히 정렬 후 중복 제거만 하려면 sort -u 한 번이면 충분하다.
cut은 각 행에서 특정 위치나 필드를 잘라낸다. -c는 문자 위치, -f는 필드(구분자 기준), -d는 구분자를 지정한다.
tr(translate)은 문자 단위 치환/삭제/압축 도구로, 표준 입력을 받기 때문에 보통 파이프나 리다이렉션(<)과 함께 쓴다.
cut의 -d와 -f는 한 쌍.cut -f로 필드를 추출할 때는 -d로 구분자를 함께 지정해야 의미가 있다. -d를 생략하면 기본 구분자가 탭이라서, 공백이나 쉼표로 구분된 데이터는 제대로 잘리지 않는다. CSV 파일이라면 -d,, /etc/passwd라면 -d:를 반드시 함께 줘야 한다.
정규표현식(regular expression, regex)은 텍스트 패턴을 표현하는 형식 언어이다.
grep, sed, awk 모두 정규식을 지원하므로, 정규식을 알면 텍스트 처리 능력이 비약적으로 향상된다.
기본 정규식(BRE, Basic Regular Expression)의 메타문자는 다음과 같다.
메타문자
의미
.
임의의 한 문자
*
바로 앞 문자의 0회 이상 반복
^
행의 시작
$
행의 끝
[abc]
a, b, c 중 한 문자
[^abc]
a, b, c가 아닌 문자
[a-z]
a부터 z 사이 한 문자
\
메타문자를 일반 문자로(이스케이프)
확장 정규식(ERE, Extended Regular Expression)에서는 추가로 다음을 쓸 수 있다.
메타문자
의미
+
앞 문자의 1회 이상 반복
?
앞 문자의 0회 또는 1회
|
OR (둘 중 하나)
()
그룹화
{n,m}
n회 이상 m회 이하 반복
grep "^root" /etc/passwd # root로 시작하는 행grep "bash$" /etc/passwd # bash로 끝나는 행grep "^$" file.txt # 빈 행 (시작과 끝이 붙음)grep "a.c" file.txt # a, 아무 문자, c (예: abc, axc)grep "ab*c" file.txt # ac, abc, abbc... (b가 0회 이상)grep -E "ab+c" file.txt # abc, abbc... (b가 1회 이상, ERE)grep -E "(cat|dog)" pets.txt # cat 또는 dog
BRE와 ERE의 결정적 차이. 기본 정규식(BRE)에서는 +, ?, |, (, ), {가 일반 문자로 취급되어, 메타 기능을 쓰려면 백슬래시를 붙여 \+, \?, \|처럼 써야 한다. 반면 확장 정규식(ERE, grep -E 또는 egrep)에서는 이들이 백슬래시 없이 메타문자로 동작한다. 즉 BRE의 \(\)가 ERE에서는 ()이다. 이 반전이 시험의 함정이 될 수 있다.
셸 와일드카드와 정규식은 다르다. 파일 이름에 쓰는 와일드카드(*.txt)와 정규식의 *는 완전히 다르다. 셸 와일드카드에서 *는 "임의의 문자열"이지만, 정규식에서 *는 "앞 문자의 0회 이상 반복"이다. 정규식에서 "임의의 문자열"을 표현하려면 .*(임의 문자의 0회 이상)를 써야 한다. 이 둘을 혼동하면 패턴이 전혀 다르게 동작한다.