tr 'a' 'b' file.txt는 동작하지 않는다. 반드시 cat file.txt | tr 'a' 'b' 또는 tr 'a' 'b' < file.txt로 써야 한다.
3. sed — 스트림 편집기
sed는 줄 단위로 텍스트를 치환, 삭제, 삽입하는 도구다. tr보다 강력하고, 정규식을 사용할 수 있다.
기본 치환
# 첫 번째 매칭만 치환echo "foo bar foo" | sed 's/foo/baz/' # baz bar foo# 모든 매칭 치환 (g 플래그)echo "foo bar foo" | sed 's/foo/baz/g' # baz bar baz# 파일 직접 수정 (-i)sed -i 's/old/new/g' file.txt # macOS: sed -i '' 's/old/new/g'
줄 단위 조작
# 특정 줄 삭제sed '3d' file.txt # 3번째 줄 삭제sed '/^#/d' file.txt # 주석 줄 삭제sed '/^$/d' file.txt # 빈 줄 삭제# 특정 줄만 출력 (-n + p)sed -n '5p' file.txt # 5번째 줄만 출력sed -n '3,7p' file.txt # 3~7번째 줄# 줄 앞/뒤에 추가sed 's/^/PREFIX: /' file.txt # 모든 줄 앞에 추가sed 's/$/ SUFFIX/' file.txt # 모든 줄 뒤에 추가
정규식 활용
# 영숫자와 언더스코어만 남기기echo "$RAW_COL" | sed 's/[^a-z0-9_]//g'# 그룹 캡처와 역참조echo "2026-06-01" | sed 's/\([0-9]*\)-\([0-9]*\)-\([0-9]*\)/\3\/\2\/\1/'# 01/06/2026# ERE (확장 정규식) 사용 — -E 옵션echo "2026-06-01" | sed -E 's/([0-9]+)-([0-9]+)-([0-9]+)/\3\/\2\/\1/'
4. awk — 패턴 매칭 + 필드 처리
awk는 텍스트를 필드(열) 단위로 처리하는 프로그래밍 언어다. tr(문자), sed(줄)보다 한 단계 강력하다.
기본 구조
awk 'pattern { action }' file
pattern: 조건 (생략하면 모든 줄에 적용)
action: 해당 줄에서 실행할 명령
# 기본: 모든 줄의 첫 번째 필드 출력awk '{ print $1 }' file.txt# 구분자 지정 (-F)awk -F',' '{ print $2 }' data.csv # CSV의 2번째 열awk -F':' '{ print $1, $3 }' /etc/passwd # 사용자명, UID# 조건부 출력awk '$3 > 100 { print $1, $3 }' data.txt # 3번째 필드가 100 초과인 줄# 내장 변수awk '{ print NR, NF, $0 }' file.txt# NR: 현재 줄 번호# NF: 현재 줄의 필드 개수# $0: 줄 전체
BEGIN / END 블록
# BEGIN: 첫 줄 처리 전에 실행# END: 마지막 줄 처리 후에 실행awk 'BEGIN { sum=0 } { sum += $1 } END { print "Total:", sum }' numbers.txt# 소수점 계산awk 'BEGIN { printf "%.2f\n", 10/3 }' # 3.33
실무 예제 분석
프로젝트 쉘 스크립트에서 이런 awk를 만날 수 있다.
awk -F"${DELIM}" -v start="${START_ROW}" -v col="${COL_CNT}" 'NR >= start && NR < start + 100 { if (NF != col) { printf "ERROR: column count mismatch at line %d (expected=%d, actual=%d)\n", NR, col, NF exit 1 }}' "${CSV_FILE}"
한 줄씩 분해하면:
부분
의미
-F"${DELIM}"
필드 구분자를 $DELIM 변수 값으로 설정
-v start="${START_ROW}"
쉘 변수를 awk 내부 변수 start로 전달
-v col="${COL_CNT}"
쉘 변수를 awk 내부 변수 col로 전달
NR >= start && NR < start + 100
패턴: start번째 줄부터 100줄만 처리
NF != col
현재 줄의 필드 개수가 기대값과 다르면
printf "ERROR: ..."
에러 메시지 출력
exit 1
awk를 비정상 종료
' "${CSV_FILE}"
홑따옴표로 awk 프로그램 끝, 입력 파일 지정
awk에 쉘 변수를 전달하는 방법
-v var="$SHELL_VAR" : 안전한 방법. awk 코드가 홑따옴표 안에 있어도 동작
awk 코드를 쌍따옴표로 감싸면 $ 충돌이 생긴다 (쉘이 먼저 해석하려 함)
항상 -v 옵션을 사용하는 것이 권장됨
5. 한 줄 텍스트 도구 모음
파이프로 조합해서 쓰는 도구들을 정리한다.
head / tail
head -n 1 "$CSV_FILE" # 첫 줄만 (헤더 추출)head -n 20 file.txt # 앞 20줄tail -n 10 file.txt # 뒤 10줄tail -f /var/log/app.log # 실시간 로그 모니터링
cut
cut -d',' -f2 data.csv # CSV의 2번째 필드cut -d':' -f1,3 /etc/passwd # 1번째, 3번째 필드cut -c1-10 file.txt # 각 줄의 1~10번째 문자
sort / uniq
sort file.txt # 정렬sort -n numbers.txt # 숫자 기준 정렬sort -t',' -k2 data.csv # 2번째 필드 기준 정렬sort -u file.txt # 정렬 + 중복 제거# uniq는 연속 중복만 제거하므로 sort와 함께 사용sort file.txt | uniqsort file.txt | uniq -c # 중복 횟수 표시sort file.txt | uniq -d # 중복된 줄만 표시
grep 옵션들
grep "pattern" file.txt # 기본 검색grep -i "pattern" file.txt # 대소문자 무시grep -r "pattern" dir/ # 재귀 검색grep -n "pattern" file.txt # 줄 번호 표시grep -c "pattern" file.txt # 매칭 줄 수grep -v "pattern" file.txt # 매칭되지 않는 줄grep -l "pattern" dir/* # 매칭된 파일명만# -q : quiet 모드 (출력 없이 종료 코드만)if grep -q ":${PORT} " <<< "$(ss -ltn)"; then echo "Port $PORT is in use"fi
grep -q는 조건 검사에 최적
출력이 필요 없고 “있는지 없는지”만 알면 될 때 사용한다. 종료 코드 0(매칭됨) 또는 1(없음)만 반환한다.
date
date # 현재 날짜/시간date +%Y-%m-%d # 2026-06-01date +%H:%M:%S # 14:30:00date +"%Y-%m-%d %H:%M:%S" # 2026-06-01 14:30:00date -d "yesterday" +%Y-%m-%d # 어제 (GNU date)date -d "+3 days" +%Y-%m-%d # 3일 후
포맷
의미
예시
%Y
4자리 연도
2026
%m
월 (01-12)
06
%d
일 (01-31)
01
%H
시 (00-23)
14
%M
분 (00-59)
30
%S
초 (00-59)
00
%s
Unix 타임스탬프
1780300200
ss — 네트워크 소켓 확인
ss -ltn# -l : listening 소켓만# -t : TCP만# -n : 포트/주소를 숫자로 표시 (DNS 역조회 안 함)
포트 사용 여부를 확인하는 패턴:
check_port() { if grep -q ":${1} " <<< "$(ss -ltn)"; then echo "Port $1 is already in use" return 1 fi}
6. IFS와 read 심화
IFS란
IFS(Internal Field Separator)는 Bash가 단어를 분리할 때 사용하는 구분자다. 기본값은 공백, 탭, 줄바꿈이다.
변수 할당과 명령을 한 줄에 쓰면, 해당 변수는 그 명령의 실행 환경에서만 유효하다. 명령이 끝나면 변수는 원래 값으로 돌아간다.
IFS=: read -ra parts <<< "a:b:c"echo "${parts[@]}" # a b cecho "$IFS" # 원래 IFS (변경 안 됨)
단, 이 문법은 명령어가 있어야 동작한다. IFS=":" arr=("a" "b")처럼 변수 할당 2개를 붙여쓰면 둘 다 현재 쉘에 영구 적용된다.
CSV 헤더 파싱 실전 예제
DELIM=$(printf '\x07') # BEL 문자를 구분자로 사용HEADER_LINE=$(head -n 1 "${CSV_FILE}")IFS="$DELIM" read -ra HEADER_ARR <<< "$HEADER_LINE"for col in "${HEADER_ARR[@]}"; do # 각 열 이름을 정규화: 따옴표 제거, 공백 제거, 소문자 변환, 특수문자 제거 clean=$(echo "$col" | tr -d '"' | tr -d ' ' | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9_]//g') echo "$clean"done
7. Job Control
백그라운드 실행
# & 로 백그라운드 실행long_task &echo "PID: $!" # 백그라운드 프로세스의 PID# 여러 작업 병렬 실행 후 대기task1 &task2 &task3 &wait # 모든 백그라운드 작업 완료 대기echo "All done"# 특정 PID만 대기pid1=$!wait "$pid1"
jobs, fg, bg
인터랙티브 쉘에서 사용하는 명령들이다.
jobs # 백그라운드 작업 목록fg %1 # 1번 작업을 포그라운드로bg %1 # 정지된 1번 작업을 백그라운드로 재개kill %1 # 1번 작업 종료
스크립트에서의 병렬 실행
스크립트에서는 jobs/fg/bg보다 &와 wait를 조합하는 것이 일반적이다.
pids=()for url in "${urls[@]}"; do curl -sO "$url" & pids+=($!)donefor pid in "${pids[@]}"; do wait "$pid" || echo "Failed: $pid"done
8. 에러 핸들링
set 옵션
#!/bin/bashset -euo pipefail
옵션
동작
-e
명령이 실패하면 즉시 종료
-u
미선언 변수 사용 시 에러
-o pipefail
파이프라인 중 하나라도 실패하면 전체 실패
디버깅할 때는 set -x
실행되는 명령을 한 줄씩 출력해준다. 특정 구간만 감싸서 쓸 수도 있다.
set -x# 디버그 구간set +x
trap
프로세스가 시그널을 받거나 종료될 때 실행할 명령을 등록한다.
# 임시 파일 정리tmpfile=$(mktemp)trap "rm -f $tmpfile" EXIT# 여러 시그널 처리cleanup() { echo "Cleaning up..." rm -f "$tmpfile"}trap cleanup EXIT INT TERM# trap 해제trap - EXIT
시그널
설명
EXIT
스크립트 종료 시 (정상/비정상 모두)
INT
Ctrl+C
TERM
kill 명령
ERR
명령 실패 시 (set -e와 조합)
에러 처리 패턴
# || 로 실패 시 대체 동작cd /some/dir || { echo "디렉토리 없음"; exit 1; }# 커스텀 에러 함수die() { echo "ERROR: $*" >&2 exit 1}[[ -f config.yml ]] || die "config.yml not found"