환경  Git 2.50 · macOS · Windows · Linux 서버 · 2026-09-21 확인

윈도우에서 파일 하나 고치고 커밋하려는데 git status 에 손도 안 댄 파일이 서른 개쯤 modified 로 따라 올라옵니다. diff 를 열어 보면 줄마다 지웠다가 똑같이 다시 넣은 것처럼 나옵니다. 며칠 뒤에는 맥에서 만든 배포 스크립트가 리눅스 서버에서 bad interpreter 라며 첫 줄부터 죽습니다. 둘 다 눈에 안 보이는 줄 끝 문자, CR 하나가 벌이는 일입니다. 같은 질문이 팀에 사람이 바뀔 때마다 다시 나오니, 이번에 묻는 순서대로 한 번에 답해 둡니다.

아무것도 안 고쳤는데 파일이 통째로 modified 로 잡힙니다. 뭐가 바뀐 건가요?

글자는 그대로고 줄 끝만 바뀐 겁니다. 윈도우는 줄을 CR 과 LF 두 글자로 끝내고, 맥과 리눅스는 LF 하나로 끝냅니다. 편집기가 저장하면서 줄 끝을 자기 방식으로 바꿔 놓으면, Git 은 모든 줄이 달라졌다고 봅니다. 그래서 diff 가 파일 전체를 지우고 다시 넣은 모양이 됩니다.

진짜 내용이 바뀐 건지 줄 끝만 바뀐 건지는 한 줄로 가릅니다. git diff 에 --ignore-cr-at-eol 을 붙이면 줄 끝의 CR 을 빼고 비교합니다. 이 옵션을 붙였을 때 diff 가 텅 비면 내용 변경은 하나도 없고 줄 끝만 갈린 겁니다.

어느 파일이 어떤 줄 끝을 쓰는지는 git ls-files --eol 이 알려 줍니다. i/ 는 인덱스에 들어 있는 상태, w/ 는 작업 폴더 파일의 상태, attr/ 은 그 파일에 걸린 속성입니다. i/lf 인데 w/crlf 면 저장소는 LF 인데 편집기가 CRLF 로 저장했다는 뜻이고, 이 두 칸이 어긋난 파일이 곧 status 에 올라온 그 파일들입니다.

git diff --ignore-cr-at-eol --stat     # 비어 있으면 줄 끝만 바뀐 것
git ls-files --eol | grep -v 'i/lf *w/lf'   # 인덱스와 작업 폴더가 어긋난 파일만

내용 변경인지 줄 끝 변경인지부터 가른다

--ignore-cr-at-eol 을 붙인 diff 가 비어 있으면, 그 modified 는 전부 줄 끝 CR 이다.

core.autocrlf 는 true·input·false 중 뭘 켜야 하나요?

세 값이 하는 일이 다릅니다. true 는 모든 파일에 text=auto 를 건 것과 같고, 거기에 작업 폴더 줄 끝을 CRLF 로 두는 core.eol=crlf 까지 얹은 것입니다. 커밋할 때 LF 로 넣고, 꺼낼 때 CRLF 로 바꿔 줍니다. 윈도우에서 CRLF 파일을 다루면서 저장소는 LF 로 유지하고 싶을 때 쓰는 값입니다.

input 은 커밋할 때만 LF 로 바꾸고, 꺼낼 때는 아무것도 바꾸지 않습니다. 맥과 리눅스처럼 작업 폴더도 LF 인 환경에서, 어쩌다 섞여 들어온 CRLF 가 저장소로 흘러가는 것만 막고 싶을 때 맞습니다. false 는 양쪽 다 손대지 않습니다. 파일이 CRLF 면 CRLF 채로 저장소에 들어갑니다.

core.eol 을 따로 만졌다면 한 가지 더 알아 둘 게 있습니다. core.autocrlf 가 true 나 input 이면 core.eol 은 무시됩니다. 두 값을 같이 건드려 놓고 왜 안 먹느냐고 묻는 경우가 여기서 나옵니다.

그런데 이 설정의 진짜 문제는 값이 아니라 위치입니다. core.autocrlf 는 사람마다 자기 컴퓨터에 두는 설정이라, 팀원 한 명이 다른 값을 쓰면 그 사람이 커밋할 때마다 줄 끝이 뒤집힙니다. 무엇을 켤지보다 팀이 같은 규칙을 쓰는지가 먼저입니다. 그래서 다음 질문으로 이어집니다.

git config --show-origin --get core.autocrlf   # 어느 파일의 설정이 먹고 있는지
git config --show-origin --get core.eol

아무것도 안 나오면 설정이 없는 것, 파일 경로가 나오면 그 파일이 이긴다

팀 전체에 같은 규칙을 강제하려면 어떻게 하나요?

저장소 안에 .gitattributes 를 두고 거기서 정합니다. 개인 설정과 달리 이 파일은 커밋되어 같이 따라다니니, 누가 clone 하든 같은 규칙이 붙습니다. 첫 줄은 대개 하나입니다. * text=auto. 모든 경로에 대해 Git 이 텍스트인지 바이너리인지 스스로 판단하고, 텍스트면 커밋할 때 LF 로 정규화합니다.

이 파일이 있으면 core.autocrlf 는 뒤로 물러납니다. text 속성이 정해진 경로에는 core.autocrlf 를 보지 않고, 속성이 비어 있는 경로에만 개인 설정이 쓰입니다. 팀원이 자기 컴퓨터에 뭘 켜 두었든 저장소 규칙이 이기는 구조라, 사람마다 줄 끝이 뒤집히는 일이 여기서 끝납니다.

작업 폴더에서 어떤 줄 끝으로 꺼낼지는 eol 속성으로 정합니다. eol=lf 는 인덱스와 같은 줄 끝으로 꺼내고, eol=crlf 는 꺼낼 때 CRLF 로 바꿉니다. eol 만 적고 text 를 빼먹어도 됩니다. eol 을 지정하면 text 가 비어 있던 경로에는 text 가 자동으로 붙습니다. eol 을 안 적은 파일은 개인 설정의 core.eol 을 따르고, 그것도 없으면 윈도우는 CRLF, 나머지는 LF 입니다.

# .gitattributes
*        text=auto
*.sh     text eol=lf
*.bat    text eol=crlf
*.png    binary
*.jpg    binary

저장소 루트에 두고 커밋한다. 개인 설정보다 이 파일이 먼저다

core.autocrlf 는 text 속성이 비어 있는 경로에만 쓰인다. 저장소에 .gitattributes 가 있으면 개인 설정은 뒤로 밀린다.

.gitattributes 를 넣었는데 이미 커밋된 파일은 그대로 CRLF 입니다

text=auto 가 조심스러운 값이라 그렇습니다. 파일이 텍스트이고 아직 CRLF 로 저장소에 들어간 적이 없을 때만 변환하고, 이미 CRLF 로 들어가 있는 파일은 건드리지 않습니다. 속성을 붙였다고 저장소 안의 파일이 저절로 바뀌지는 않는다는 뜻입니다.

그래서 한 번은 손으로 밀어 줘야 합니다. git add --renormalize . 가 그 명령입니다. 추적 중인 모든 파일에 정규화를 새로 적용해서 인덱스에 다시 넣습니다. 이어서 git status 를 보면 줄 끝이 바뀔 파일이 전부 modified 로 올라오고, 그걸 한 커밋으로 묶으면 정리가 끝납니다. 이 커밋 하나에는 줄 끝 변경만 담고 다른 수정은 섞지 않는 게 나중에 blame 을 볼 때 편합니다.

status 에 바뀌면 안 되는 파일이 올라오면 그 파일은 텍스트로 오판된 겁니다. 커밋하기 전에 그 경로에 -text 나 binary 를 걸어 빼야 합니다. 한번 LF 로 바뀐 바이너리는 되돌릴 수 없습니다.

renormalize 는 인덱스만 고칩니다. 작업 폴더에 있는 파일은 그 뒤 Git 이 그 파일을 다시 꺼낼 때 새 규칙으로 바뀝니다. 실제로 renormalize 직후 git ls-files --eol 을 보면 i/lf w/crlf 로 남아 있다가, 파일을 지우고 checkout 으로 다시 받으면 그제야 w/lf 가 됩니다. 커밋했는데 내 편집기에서는 여전히 CRLF 라면 그 이유입니다.

git add --renormalize .
git status --short                 # 줄 끝이 바뀔 파일이 전부 M 으로 뜬다
git commit -m "Normalize line endings"

# 작업 폴더도 새 규칙으로 다시 받고 싶을 때(수정 중인 파일이 없을 때만)
git rm --cached -r . -q && git reset --hard -q
git ls-files --eol | grep -v 'i/lf *w/lf'   # 비어 있으면 끝

인덱스는 renormalize 로, 작업 폴더는 다시 꺼내서 맞춘다

서버에서 셸 스크립트가 bad interpreter: /bin/bash^M 으로 죽습니다

첫 줄 #!/bin/bash 뒤에 CR 이 붙어서 그렇습니다. 커널은 셔뱅 줄을 줄 끝까지 읽어 실행 파일 경로로 쓰는데, 줄 끝이 CRLF 면 경로가 /bin/bash 가 아니라 /bin/bash 뒤에 CR 이 붙은 이름이 됩니다. 그런 파일은 없으니 no such file or directory 로 끝납니다. 에러 메시지의 ^M 이 바로 그 CR 입니다.

급하면 파일에서 CR 만 벗겨 내면 당장은 돕니다. 하지만 다음에 윈도우 쪽에서 누가 저장하면 또 돌아옵니다. 근본 처방은 .gitattributes 에 *.sh text eol=lf 를 적어 두는 겁니다. 이 속성이 붙은 파일은 어느 운영체제에서 꺼내도 LF 로 나옵니다. 윈도우 개발자의 core.autocrlf 가 true 여도 이 경로만은 CRLF 로 안 바뀝니다.

속성을 추가하는 순간 한 가지 눈에 띄는 일이 생깁니다. 파일을 건드리지 않았는데 그 스크립트가 status 에 modified 로 올라옵니다. 작업 폴더 파일은 CRLF 인데 새 속성대로라면 LF 여야 하니 Git 이 다르다고 보는 겁니다. 고장이 아니라 앞 질문의 renormalize 를 돌리라는 신호입니다.

file deploy.sh            # 'with CRLF line terminators' 가 보이면 확정
sed -i 's/\r$//' deploy.sh    # 응급 처치(macOS 는 sed -i '' 's/\r$//')

# 근본 처방
echo '*.sh text eol=lf' >> .gitattributes
git add --renormalize . && git commit -m "Force LF for shell scripts"

응급은 sed, 재발 방지는 eol=lf

bad interpreter 뒤의 ^M 은 셔뱅 줄 끝에 붙은 CR 이다. sed 는 오늘을 살리고, eol=lf 가 내일을 살린다.

반대로 CRLF 여야 하는 파일까지 LF 로 바뀌었습니다

윈도우 배치 파일이 대표적입니다. * text=auto 하나만 걸어 두면 .bat 도 텍스트로 잡혀 LF 로 정규화되고, 그 파일을 윈도우에서 꺼내면 개인 설정에 따라 LF 로 나올 수 있습니다. 이런 경로는 eol=crlf 를 따로 적어 줍니다. 저장소 안에서는 LF 로 두되 꺼낼 때는 반드시 CRLF 로 내놓으라는 뜻입니다.

더 위험한 쪽은 바이너리입니다. Git 의 텍스트 판정은 내용을 보고 짐작하는 것이라, 드물게 바이너리를 텍스트로 잘못 잡습니다. 그 상태로 CRLF 변환이 걸리면 파일 안의 바이트가 바뀌어 깨집니다. 확장자를 아는 바이너리는 미리 binary 속성을 겁니다. binary 는 -text -diff -merge 를 한꺼번에 거는 내장 매크로라, 줄 끝 변환도 텍스트 diff 도 모두 끕니다.

add 할 때 CRLF will be replaced by LF the next time Git touches it 이라는 경고가 뜨는 것은 정상입니다. 작업 폴더의 CRLF 가 커밋할 때 LF 로 바뀐다는 알림일 뿐입니다. 정말 조심할 것은 한 파일 안에 CRLF 와 LF 가 섞인 경우입니다. 섞인 파일은 한 번 변환하면 원래 모양으로 되돌릴 수 없습니다. git ls-files --eol 에서 w/mixed 로 나오는 파일이 그것이라, 커밋 전에 열어서 어느 쪽이 맞는지 정리해 둡니다. core.safecrlf 를 true 로 두면 이렇게 되돌릴 수 없는 변환은 경고가 아니라 거부로 바뀝니다.

git ls-files --eol | grep 'w/mixed'   # 한 파일에 CRLF·LF 가 섞인 것
git ls-files --eol | grep 'i/-text'   # 바이너리로 판정된 파일 목록

섞인 파일과 바이너리 판정을 눈으로 확인한다

조치 후 확인할 것

  • status 에 안 고친 파일이 올라오면 git diff --ignore-cr-at-eol --stat 부터. 비어 있으면 줄 끝 문제이지 내용 변경이 아니다
  • 저장소 루트에 .gitattributes 가 있는지 본다. 없으면 * text=auto 를 넣고 git add --renormalize . 를 한 커밋으로 묶는다
  • 셸 스크립트·Makefile 처럼 LF 여야 하는 경로는 eol=lf, .bat·.cmd 는 eol=crlf, 이미지·압축은 binary 로 못 박는다
  • renormalize 뒤 git ls-files --eol 에서 i/ 와 w/ 가 어긋난 파일이 없는지 확인한다. 작업 폴더는 다시 꺼내야 바뀐다
  • git ls-files --eol 에 w/mixed 가 있으면 한 파일에 줄 끝이 섞인 것이다. 변환하면 되돌릴 수 없으니 열어서 정리한 뒤 커밋한다

줄 끝 문제는 언제나 같은 모양으로 돌아옵니다. 누군가의 개인 설정이 다르고, 저장소에는 규칙이 없고, 그래서 커밋할 때마다 파일이 뒤집힙니다. 처방도 언제나 같습니다. 규칙을 사람 컴퓨터가 아니라 저장소 안 .gitattributes 에 두고, 한 번은 renormalize 로 과거를 정리하고, LF 여야 하는 파일과 CRLF 여야 하는 파일과 아예 손대면 안 되는 파일을 이름으로 못 박아 두는 것. 이 세 줄을 해 두면 다음 사람이 들어와도 같은 질문이 다시 나오지 않습니다.