git worktree list porcelain 형식은 스크립트가 경로와 브랜치를 읽기 위한 안정적인 출력이다. 반면 git worktree list의 기본 출력은 사람이 보기 좋게 열을 맞춘 목록이다. 스크립트에서 awk '{print $1}'처럼 첫 단어만 읽으면 feature worktree가 feature로 잘린다. 경로와 브랜치를 자동으로 읽을 때는 git worktree list --porcelain -z를 실행하고 NUL 문자 단위로 속성을 읽는다.
먼저 저장소에서 다음 읽기 전용 명령을 실행한다.
git worktree list --porcelain -z |
while IFS= read -r -d '' field; do
case "$field" in
"worktree "*) printf '경로: %s\n' "${field#worktree }" ;;
"branch "*) printf '브랜치: %s\n' "${field#branch refs/heads/}" ;;
esac
done
IFS=는 앞뒤 공백을 보존하고, read -d ''는 NUL까지 한 속성으로 읽는다. 명령은 목록만 읽으며 파일이나 worktree를 지우지 않는다.

목차
기본 목록의 첫 칸을 읽으면 왜 깨지나
공백이 든 임시 경로로 Git 2.38.1에서 직접 확인했다.
/private/tmp/.../main repo adbebfe [main]
/private/tmp/.../feature worktree adbebfe [feature-space]
여기에 다음 명령을 연결하면 경로가 공백 앞에서 끝난다.
git worktree list | awk '{print $1}'
/private/tmp/.../main
/private/tmp/.../feature
공개 이슈에서도 같은 방식으로 공백 경로가 잘려 worktree 정리가 실패했다. 문제는 공백 경로 자체가 아니라 사람용 열 출력에 공백 구분 파서를 붙인 것이다.
--porcelain은 경로와 브랜치를 서로 다른 속성으로 보여준다
git worktree list --porcelain
worktree /private/tmp/.../feature worktree
HEAD adbebfe183f28eec461af66401e4f4c70452f517
branch refs/heads/feature-space
worktree 뒤의 나머지 전체가 경로이고, branch refs/heads/ 뒤의 나머지가 로컬 브랜치다. 따라서 공백 경로만 다룬다면 첫 단어가 아니라 라벨 뒤의 전체 값을 읽어야 한다.
Git 공식 문서는 porcelain 형식을 Git 버전과 사용자 설정에 영향받지 않는 스크립트용 형식으로 설명한다. 기본 목록의 열 위치를 세거나 대괄호 안 텍스트를 잘라 브랜치를 추정하는 방식보다 이 속성을 사용한다.
-z가 필요한 이유는 경로 안 줄바꿈이다
공백만 생각하면 줄 단위 porcelain으로 충분해 보인다. 하지만 파일 경로에는 줄바꿈도 들어갈 수 있다. 이번 Git 2.38.1 재현에서 line과 break 사이에 실제 줄바꿈이 든 worktree를 만들자, --porcelain만 쓴 출력도 두 줄로 갈라졌다.
worktree /private/tmp/.../line
break
HEAD adbebfe...
반면 --porcelain -z는 각 속성을 줄바꿈이 아니라 NUL로 끝냈다. 위의 read -d '' 예제는 경로 안 줄바꿈을 속성의 일부로 유지했다. 출력할 때만 이해를 위해 line\nbreak로 표시했다.
정리하면 공백 경로만 있는 현재 데이터에 우연히 맞는 파서가 아니라, 레코드 경계를 명시하는 -z까지 붙여야 나중에 잘못된 경로를 지목하지 않는다.
경로와 브랜치를 한 레코드로 묶어 출력하기
다음 Bash 예제는 worktree 속성에서 새 레코드를 시작하고 빈 NUL 필드인 레코드 경계를 만났을 때 한 줄을 출력한다. 브랜치가 없는 detached worktree는 (detached)로 남긴다.
path=''
branch='(detached)'
while IFS= read -r -d '' field; do
if [[ -z "$field" ]]; then
printf '경로=%q 브랜치=%s\n' "$path" "$branch"
path=''
branch='(detached)'
elif [[ "$field" == worktree\ * ]]; then
path=${field#worktree }
elif [[ "$field" == branch\ refs/heads/* ]]; then
branch=${field#branch refs/heads/}
fi
done < <(git worktree list --porcelain -z)
%q는 Bash가 경로의 공백과 줄바꿈을 다시 구분해 볼 수 있게 표시한다. 이 코드는 Bash용이다. PowerShell에서 Bash 구문을 그대로 붙여 넣지 말고 NUL 구분자를 지원하는 바이트·문자열 처리로 별도 구현해야 한다. 이번 글에서는 PowerShell 파서를 직접 재현하지 않았다.
결과가 이상할 때 확인할 것
git --version을 실행해 확인 환경을 기록한다.git worktree list --porcelain -z | od -An -t x1 | head로00구분자가 나오는지 확인한다.for x in $(...),awk '{print $1}', 공백 기준cut이 남아 있다면 제거한다.- 브랜치가 출력되지 않으면 오류로 단정하지 말고 해당 레코드에
detached속성이 있는지 본다. - 자동 삭제 스크립트라면 바로 실행하지 말고 먼저 파싱된 경로와 브랜치를 출력해 실제 목록과 대조한다.
공백이 든 경로를 명령 인자로 전달하는 문제는 수정 파일이 남은 worktree를 제거하는 방법에서 다룬다. 이 글은 이미 만들어진 목록의 출력을 읽는 단계만 해결한다. 기존 브랜치 연결 오류라면 branch already exists 해결 순서로 이동한다.
자주 묻는 질문
--porcelain만 쓰면 공백 경로는 안전한가?
worktree 접두사 뒤 전체를 값으로 읽으면 공백은 보존된다. 다만 경로 안 줄바꿈까지 안전하게 처리하려면 -z를 함께 쓰고 NUL 단위로 읽는다.
기본 목록의 대괄호에서 브랜치 이름을 읽어도 되나?
스크립트에서는 권하지 않는다. porcelain의 branch refs/heads/... 속성을 사용한다. detached worktree에는 branch 대신 detached가 나타날 수 있다.
검증 기준
- 주요 근거: Git 공식 git-worktree 문서
- 공개 질문: 공백 경로를 첫 필드로 읽어 정리가 실패한 이슈
- 마지막 업데이트일: 2026-09-21
- 확인 환경: macOS 비민감 임시 저장소, Git 2.38.1
- 직접 결과: 기본 목록의 첫 필드 파싱은 공백 경로를 잘랐고, 줄 단위 porcelain은 줄바꿈 경로를 나눴다.
--porcelain -z를 NUL 단위로 읽자 공백·줄바꿈 경로와 각 브랜치를 보존했다. - 한계: 정확 월간 검색량, seoin.dev 독자의 직접 반응, PowerShell 파서는 미확인이다. 자동 삭제는 이번 재현 범위가 아니다.
직접 만든 실습 자료와 새 도구 소식
AI 도구로 만든 실습 자료, 달라진 기능과 강의 소식을 준비하고 있습니다. 발송을 시작하면 안내해 드려요. 먼저 확인 메일에서 본인 이메일을 확인해 주세요.
신청하기 전에 첫 소식 미리보기에서 내용과 자료를 확인해 보세요.