git worktree list porcelain, 공백 경로가 잘리면 `–porcelain -z`로 읽는다

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를 지우지 않는다.

공백으로 자른 worktree 경로가 잘리는 실패와 porcelain NUL 파싱으로 경로와 브랜치를 보존하는 순서 개념도

기본 목록의 첫 칸을 읽으면 왜 깨지나

공백이 든 임시 경로로 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 파서를 직접 재현하지 않았다.

결과가 이상할 때 확인할 것

  1. git --version을 실행해 확인 환경을 기록한다.
  2. git worktree list --porcelain -z | od -An -t x1 | head로 00 구분자가 나오는지 확인한다.
  3. for x in $(...), awk '{print $1}', 공백 기준 cut이 남아 있다면 제거한다.
  4. 브랜치가 출력되지 않으면 오류로 단정하지 말고 해당 레코드에 detached 속성이 있는지 본다.
  5. 자동 삭제 스크립트라면 바로 실행하지 말고 먼저 파싱된 경로와 브랜치를 출력해 실제 목록과 대조한다.

공백이 든 경로를 명령 인자로 전달하는 문제는 수정 파일이 남은 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 도구로 만든 실습 자료, 달라진 기능과 강의 소식을 준비하고 있습니다. 발송을 시작하면 안내해 드려요. 먼저 확인 메일에서 본인 이메일을 확인해 주세요.

신청하기 전에 첫 소식 미리보기에서 내용과 자료를 확인해 보세요.

질문이나 의견을 남겨주세요

이름을 입력하지 않아도 돼요. ‘깜짝 놀란 올빼미’ 같은 별명이 자동으로 붙어요.