CSV 한 줄, PATH 같은 콜론 목록, 설정 파일의 key=v1;v2;v3처럼 구분자로 이어진 문자열을 배열로 나누는 일은 셸 스크립트에서 매일 나온다. 그런데 흔히 쓰는 방법들은 공백이 든 필드, * 같은 glob 문자, 빈 필드, 끝 구분자, 줄바꿈을 만나면 각자 다른 결과를 낸다.
이 글에서는 대표적인 분리 방법 네 가지를 같은 입력 여섯 개로 Ubuntu 24.04(bash 5.2.21)에서 실행해 비교하고, 빈 필드 보존·여러 글자 구분자·필드 하나만 꺼내기까지 상황별 방법을 정리한다.
비교한 방법
#!/usr/bin/env bash
show() { printf '%-10s n=%d ' "$1" "$2"; shift 2; printf '[%s]' "$@"; echo; }
split_all() {
local in=$1 arr
IFS=';' read -ra arr <<< "$in"; show "read -ra" ${#arr[@]} "${arr[@]}"
arr=(${in//;/ }); show "(\${//})" ${#arr[@]} "${arr[@]}"
readarray -td';' arr <<< "$in"; show "readarray" ${#arr[@]} "${arr[@]}"
readarray -td';' arr < <(printf '%s' "$in"); show "readarr+p" ${#arr[@]} "${arr[@]}"
}
for in in 'a;b;c' 'a b;c' 'a;*;c' 'a;;c' 'a;b;' $'a;b\nc;d'; do
printf -- '--- in=%q\n' "$in"
split_all "$in"
done작업 디렉터리에는 glob 확인용으로 x.txt, y.txt를 만들어 두었다. readarr+p는 here-string 대신 printf로 넘겨 끝 줄바꿈을 없앤 변형이다.
입력별 실행 결과
$ touch x.txt y.txt && bash split.sh
--- in=a\;b\;c
read -ra n=3 [a][b][c]
(${//}) n=3 [a][b][c]
readarray n=3 [a][b][c
]
readarr+p n=3 [a][b][c]
--- in=a\ b\;c
read -ra n=2 [a b][c]
(${//}) n=3 [a][b][c]
readarray n=2 [a b][c
]
readarr+p n=2 [a b][c]
--- in=a\;\*\;c
read -ra n=3 [a][*][c]
(${//}) n=5 [a][split.sh][x.txt][y.txt][c]
readarray n=3 [a][*][c
]
readarr+p n=3 [a][*][c]
--- in=a\;\;c
read -ra n=3 [a][][c]
(${//}) n=2 [a][c]
readarray n=3 [a][][c
]
readarr+p n=3 [a][][c]
--- in=a\;b\;
read -ra n=2 [a][b]
(${//}) n=2 [a][b]
readarray n=3 [a][b][
]
readarr+p n=2 [a][b]
--- in=$'a;b\nc;d'
read -ra n=2 [a][b]
(${//}) n=4 [a][b][c][d]
readarray n=3 [a][b
c][d
]
readarr+p n=3 [a][b
c][d]
${in//;/ }는 치환 결과를 따옴표 없이 다시 word splitting·glob 확장에 넘기므로 공백 필드가 쪼개지고 *가 파일 목록으로 바뀐다.
| 입력 | read -ra | (${in//;/ }) | readarray -td';' | readarray + printf |
|---|---|---|---|---|
a b;c (공백 필드) | ✔ | ✘ 3개로 쪼개짐 | ✔ | ✔ |
a;*;c (glob) | ✔ | ✘ 파일명으로 확장 | ✔ | ✔ |
a;;c (빈 필드) | ✔ | ✘ 빈 필드 사라짐 | ✔ | ✔ |
a;b; (끝 구분자) | 빈 필드 버림 | 빈 필드 버림 | ✘ \n 필드 추가 | 빈 필드 버림 |
| 여러 줄 | 첫 줄만 | ✘ 줄 경계 무시 | ✘ 줄바꿈 섞임 | 줄바꿈이 필드에 포함 |
| 마지막 필드 | ✔ | ✔ | ✘ 끝에 \n | ✔ |
한 줄 입력이면 IFS=';' read -ra가 가장 무난하고, readarray는 here-string이 붙이는 줄바꿈을 반드시 제거해야 한다.
상황별 분리 방법
#!/usr/bin/env bash
show() { printf '%-12s n=%d ' "$1" "$2"; shift 2; printf '[%s]' "$@"; echo; }
echo "--- 끝의 빈 필드 보존"
for in in 'a;b;c' 'a;b;' ';'; do
readarray -td';' arr < <(printf '%s;' "$in")
show "$in" ${#arr[@]} "${arr[@]}"
done
echo "--- IFS 공백 vs 비공백"
IFS=' ' read -ra arr <<< ' a b '; show "IFS=' '" ${#arr[@]} "${arr[@]}"
IFS=',' read -ra arr <<< ',a,,,b,'; show "IFS=','" ${#arr[@]} "${arr[@]}"
echo "--- IFS는 문자 집합"
IFS=', ' read -ra arr <<< 'a, b,c d'; show "IFS=', '" ${#arr[@]} "${arr[@]}"
echo "--- 여러 글자 구분자 ::"
split_str() { # split_str 문자열 구분자 → 전역 배열 parts
local s=$1 d=$2
parts=()
while [[ $s == *"$d"* ]]; do
parts+=("${s%%"$d"*}")
s=${s#*"$d"}
done
parts+=("$s")
}
split_str 'usr::local:bin::' '::'; show "split_str" ${#parts[@]} "${parts[@]}"
echo "--- 필드 하나만: cut / awk / 파라미터 확장"
line='root:x:0:0:root:/root:/bin/bash'
echo "cut : $(cut -d: -f6 <<< "$line")"
echo "awk : $(awk -F: '{print $NF}' <<< "$line")"
echo "\${} : ${line%%:*}"$ bash split2.sh
--- 끝의 빈 필드 보존
a;b;c n=3 [a][b][c]
a;b; n=3 [a][b][]
; n=2 [][]
--- IFS 공백 vs 비공백
IFS=' ' n=2 [a][b]
IFS=',' n=5 [][a][][][b]
--- IFS는 문자 집합
IFS=', ' n=4 [a][b][c][d]
--- 여러 글자 구분자 ::
split_str n=3 [usr][local:bin][]
--- 필드 하나만: cut / awk / 파라미터 확장
cut : /root
awk : /bin/bash
${} : root
구분자를 하나 덧붙여 readarray에 넘기면 끝의 빈 필드까지 개수가 정확히 맞는다. 공백 IFS는 연속 공백을 하나로 합치고 앞뒤를 잘라내지만, , 같은 비공백 IFS는 빈 필드를 그대로 남긴다.
| 필요한 것 | 방법 |
|---|---|
| 한 줄을 배열로 (일반적인 경우) | IFS=';' read -ra arr <<< "$in" |
| 빈 필드·끝 빈 필드까지 정확히 | readarray -td';' arr < <(printf '%s;' "$in") |
| 두 글자 이상 구분자 | ${s%%"$d"*} / ${s#*"$d"} 루프 |
| 필드 하나만 | cut -d: -f6, awk -F: '{print $NF}', ${line%%:*} |
| 여러 줄 파일을 줄 단위로 | while IFS=';' read -ra f; do ...; done < file |
주의사항
IFS=';' read의 IFS는 그read명령에만 적용된다.IFS=';'를 단독 줄로 쓰면 이후 스크립트 전체의 word splitting이 바뀌므로, 꼭 필요하면local IFS로 함수 안에 가둔다.IFS의 각 문자가 개별 구분자다.IFS='::'로 두 글자 구분자를 만들 수 없고,:하나짜리 구분자와 같다.read에-r이 없으면 역슬래시가 이스케이프로 해석되어C:\dir같은 값이 깨진다.readarray -d는 bash 4.4 이상에서만 쓸 수 있다. macOS 기본/bin/bash(3.2)에는readarray자체가 없다.for x in $(echo $in | tr ';' '\n')형태도 따옴표 없는 확장이라(${in//;/ })와 같은 glob·공백 문제를 그대로 가진다.
마무리
구분자 분리에서 문제를 일으키는 것은 거의 항상 따옴표 없는 확장이다. 한 줄은 read -ra, 필드 개수가 정확해야 하면 구분자를 덧붙인 readarray -td, 여러 글자 구분자는 파라미터 확장 루프를 쓴다.