Featured image of post [Bash Shell] 16. cut - 컬럼 추출

[Bash Shell] 16. cut - 컬럼 추출

cut이 바이트·문자·필드라는 단일 위치 규칙만으로 컬럼을 잘라내는 경량 도구인 이유와 -d·-f·-c·-b 옵션, CSV·passwd·고정폭 텍스트 예제, 단일 구분자 한계·필드 재배열 불가 같은 실무 함정을 정리합니다.

cut은 줄마다 바이트(-b), 문자(-c), 필드(-f) 중 하나의 기준으로 정해진 위치만 잘라 표준 출력으로 내보내는 필터 명령어다. /etc/passwd처럼 구분자로 나뉜 시스템 파일이나 CSV·로그에서 특정 열만 뽑아낼 때 가장 먼저 손이 가는 도구다.

이 장을 읽기 전에

선행 챕터: 이 장은 15장: awk에서 레코드·필드를 패턴과 액션으로 처리하는 법을 다룬 뒤 이어진다. awk가 조건·연산·재배열까지 가능한 만능 텍스트 처리 언어였다면, cut은 그중 “필드 하나 뽑기"라는 가장 단순한 작업만 떼어내 가볍게 처리하는 도구다. awk가 강력하지만 무겁다면, cut은 단순 컬럼 추출만 할 때 쓰는 가벼운 도구라고 이해하면 된다.

이 장의 깊이: 입문 난이도다. 옵션 자체가 많지 않아 표 하나로 전체 문법을 파악할 수 있다. 다루지 않는 것: 구분자가 여러 문자이거나 정규식이어야 하는 상황, 필드를 원본과 다른 순서로 재배열하는 작업, 조건부 필터링은 cut의 설계 범위 밖이라 15장: awk로 돌아가야 한다. 잘라낸 필드나 문자를 다른 문자로 바꾸는 작업은 17장: tr에서 다룬다.

당신의 수준에 맞는 경로

수준읽을 부분핵심 목표
CSV·passwd에서 열 하나만 급하게 뽑아야 하는 사람“개요 + 정신 모델”, “사용법 · 옵션"의 “필드 선택”, “예시"의 “필드 추출”-d-f를 조합해 원하는 열을 즉시 뽑아낼 수 있다
cut의 한계를 정확히 알고 awk와 구분해 쓰려는 사람“주의사항·함정”, “흔한 오개념” 전체단일 구분자·필드 재배열 불가·멀티바이트 이슈를 이해하고 언제 awk로 넘어가야 하는지 판단할 수 있다

개요 + 정신 모델

cut의 세상은 줄마다 똑같이 적용되는 위치 규칙 하나로 이루어져 있다. “몇 번째 바이트인가”, “몇 번째 문자인가”, “구분자로 나눴을 때 몇 번째 필드인가” — 이 세 가지 중 정확히 하나만 고르고 나면, cut은 그 규칙을 모든 입력 줄에 기계적으로 반복 적용할 뿐 그 이상은 하지 않는다. 조건을 걸어 특정 줄만 고르거나(awk의 패턴), 잘라낸 값을 계산하거나(awk의 연산), 필드 순서를 뒤바꾸는 일은 애초에 cut이 풀도록 설계된 문제가 아니다.

이 단순함이 곧 cut의 정체성이다. POSIX 사양은 cut을 “각 줄에서 지정한 바이트·문자·필드만 골라 그대로 출력하는” 유틸리티로 정의하며, 선택된 항목은 입력에 나타난 순서 그대로 기록된다고 명시한다. 즉 cut은 스트림을 읽고 판단하는 프로그램이 아니라, 매 줄에 똑같은 “자” 하나를 대고 잘라내는 재단기에 가깝다. 옵션을 외우기보다 “이 재단기에는 눈금이 하나뿐이다"라는 사실을 먼저 받아들이면 뒤에서 다룰 함정(필드 재배열 불가, 단일 구분자 한계)이 왜 생기는지 자연스럽게 이해된다.

사용법 · 옵션

1
cut [옵션] [파일...]
  • 파일: 생략하면 표준 입력에서 읽는다. 여러 파일을 지정하면 파일 구분 없이 순서대로 이어서 처리한다.
  • -b, -c, -f정확히 하나만 지정해야 한다. 두 개 이상을 함께 쓰면 오류가 난다.

위치 선택 (-b, -c, -f 중 하나)

옵션설명
-b LIST, --bytes=LIST바이트 위치 기준으로 자른다. 멀티바이트 문자를 중간에서 자를 수 있다
-c LIST, --characters=LIST문자 위치 기준으로 자른다. 로케일을 인식해 멀티바이트 문자를 하나로 센다
-f LIST, --fields=LIST구분자로 나눈 필드 위치 기준으로 자른다
-n-b와 함께 써서 멀티바이트 문자를 중간에서 자르지 않게 한다(GNU coreutils 확장)

LIST1(1번째), 1,3(1번째와 3번째), 1-3(1–3번째), 3-(3번째부터 끝까지), -3(처음부터 3번째까지)처럼 콤마로 여러 항목을, 하이픈으로 범위를 지정하는 공통 문법을 쓴다.

구분자 · 출력 제어

옵션설명
-d DELIM, --delimiter=DELIM-f와 함께 쓰는 필드 구분자. 기본값은 탭. 한 글자만 지정할 수 있다
-s, --only-delimited-f와 함께 쓰며, 구분자가 아예 없는 줄은 출력하지 않고 건너뛴다(기본값은 그런 줄도 그대로 통과시킨다)
--complement선택한 위치를 제외한 나머지를 출력한다(선택 반전)
-O STRING, --output-delimiter=STRING출력할 때 쓸 구분자를 따로 지정한다(기본은 입력 구분자를 그대로 재사용)
-z, --zero-terminated줄바꿈 대신 NUL 문자를 줄 구분자로 쓴다(파일명처럼 개행이 포함될 수 있는 데이터용)

예시

필드 추출

1
2
3
4
5
6
7
8
# 콜론 구분, 사용자 이름(1번째)만 추출
cut -d: -f1 /etc/passwd

# 콜론 구분, 사용자 이름과 홈 디렉터리(1번, 6번 필드)
cut -d: -f1,6 /etc/passwd

# CSV에서 2~4번째 열만 (쉼표 구분)
cut -d, -f2-4 data.csv

문자·바이트 위치

1
2
3
4
5
6
7
8
# 앞 10글자만 (문자 단위)
cut -c1-10 file.txt

# 11번째 글자부터 끝까지
cut -c11- file.txt

# 고정폭 로그에서 8~15번째 바이트(타임스탬프 컬럼)만
cut -b8-15 fixed-width.log

선택 반전 · 출력 구분자 변경

1
2
3
4
5
# 2번째 필드만 제외하고 나머지 전부 출력
cut -d, --complement -f2 data.csv

# 입력은 콜론 구분이지만 출력은 탭으로 바꿔서 다음 파이프에 넘기기
cut -d: -f1,3 -O$'\t' /etc/passwd

파이프 조합

1
2
3
4
5
6
7
8
# du 결과에서 크기(기본 구분자 탭의 1번째 필드)만 뽑아 합산
du -s */ | cut -f1 | paste -sd+ | bc

# who 출력에서 접속 중인 사용자 이름(왼쪽 정렬이라 첫 필드가 바로 끊긴다)만
who | cut -d' ' -f1 | sort -u

# YYYYMMDD 형식 문자열에서 연도만 (문자 위치로 자르는 예)
date +%Y%m%d | cut -c1-4

구분자 없는 줄 처리

1
2
3
4
5
# 구분자가 없는 줄은 그대로 통과 (기본 동작)
printf 'a:b:c\nno-delimiter-here\n' | cut -d: -f2

# -s로 구분자 없는 줄은 건너뛰기
printf 'a:b:c\nno-delimiter-here\n' | cut -d: -f2 -s

주의사항·함정

-d는 한 글자 구분자만 지원한다. cut -d, -f1처럼 콤마 하나는 되지만, 연속된 공백 여러 개를 하나의 구분자로 묶어 처리하는 것은 고전적인 cut의 설계 범위 밖이다. ps -eo pid,comm처럼 오른쪽 정렬로 값이 패딩되는 출력에 공백을 구분자로 쓰면, 값 앞의 공백들이 각각 빈 필드로 잡혀 원하는 열이 엉뚱한 번호로 밀린다.

1
2
3
4
5
# PID가 오른쪽 정렬로 패딩돼 있어 첫 공백 앞이 빈 문자열이 된다 -> 1번 필드가 비어버림
ps -eo pid,comm | cut -d' ' -f1

# 연속 공백을 tr -s로 먼저 하나로 압축한 뒤에야 필드 번호가 맞아떨어진다
ps -eo pid,comm | tr -s ' ' | cut -d' ' -f2

GNU coreutils는 9.4(2023년)부터 -w(--whitespace-delimited) 옵션으로 공백 연속을 한 구분자로 묶어 처리하는 기능을 추가했지만, 이는 POSIX 표준에 없는 GNU 확장이라 오래된 시스템이나 BSD 계열 cut에는 없을 수 있다. 이식성을 고려해야 하거나 애초에 공백 개수가 일정하지 않은 입력이라면 awk '{print $2}'처럼 awk의 기본 필드 분리(연속 공백을 자동으로 하나로 묶음)를 쓰는 편이 안전하다 — 15장: awk가 필요한 이유가 바로 이 지점이다.

필드를 지정한 순서대로 재배열할 수 없다. POSIX 사양은 “선택된 바이트·문자·필드는 선택 목록의 순서가 아니라 입력 데이터의 순서대로 출력된다"고 명시한다. 즉 cut -f3,1 file.txt를 실행해도 결과는 3번, 1번 순서가 아니라 원본 그대로인 1번, 3번 순서로 나온다. 열 순서를 바꿔서 출력하고 싶다면(예: 3번째 필드를 1번째 필드보다 먼저 보여주기) cut으로는 불가능하고 awk '{print $3, $1}'을 써야 한다 — 이 차이가 “cut은 자르기만 하고, awk는 자르고 재조합까지 한다"는 두 도구의 역할 분담을 가장 잘 보여준다.

멀티바이트(UTF-8 한글 등) 환경에서 -c-b의 결과가 다르다. -c는 로케일을 인식해 “글자 하나"를 기준으로 세지만, -b는 인코딩과 무관하게 순수 바이트 수만 센다. UTF-8 로케일에서 한글 한 글자는 보통 3바이트를 차지하므로, cut -c1-3은 한글 3글자를 온전히 잘라내지만 cut -b1-3은 한글 1글자의 중간 바이트에서 잘려 깨진 문자가 출력될 수 있다. GNU cut-n 옵션으로 -b를 쓸 때도 멀티바이트 문자 중간을 자르지 않도록 보정할 수 있지만, -n-b와 함께일 때만 의미가 있고 -c에는 애초에 해당하지 않는다. 로케일이 CPOSIX로 설정된 환경(스크립트 초반에 LC_ALL=C를 흔히 거는 이유 중 하나)에서는 -c도 사실상 바이트 단위로 동작하므로, 한글이 섞인 텍스트를 다룰 때는 현재 로케일(locale 명령으로 확인)이 UTF-8인지 먼저 확인하는 습관이 필요하다.

흔한 오개념

"-d에 정규식이나 여러 문자를 넣을 수 있다"는 오해가 흔하다. awk의 FS(필드 구분자)는 정규식을 받아들이지만, cut -d는 정확히 한 글자만 구분자로 인정한다. cut -d", " -f1처럼 여러 문자를 넣으면 대부분의 cut 구현은 오류를 내거나 첫 글자만 구분자로 취급한다. 구분자가 문자열이거나 패턴이어야 하는 상황이라면 애초에 cut이 아니라 awk -F 또는 sed를 선택해야 한다.

다음 장에서는

다음은 17장: trcut으로 잘라낸 문자·필드를 이번에는 다른 문자로 바꾸거나 삭제하는 문자 단위 변환을 다룬다.

평가 기준

  • -b, -c, -f 세 가지 위치 선택 기준의 차이를 설명하고 상황에 맞게 고를 수 있다.
  • -d-f를 조합해 CSV·/etc/passwd처럼 구분자로 나뉜 텍스트에서 원하는 열을 추출할 수 있다.
  • cut이 필드를 입력 순서대로만 출력하며 재배열할 수 없다는 한계를 설명하고, 재배열이 필요할 때 awk로 전환할 판단을 할 수 있다.
  • cut -d가 단일 문자 구분자만 지원한다는 제약과, 연속 공백을 하나로 묶어야 하는 상황에서 왜 awk가 더 적합한지 설명할 수 있다.
  • UTF-8 등 멀티바이트 환경에서 -c-b의 결과가 달라질 수 있다는 점을 이해하고 로케일을 점검할 수 있다.

참고