Lỗi xảy ra như thế nào
Bạn chạy một shell script và nhận được thông báo sau:
$ ./deploy.sh
-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory
Thấy ký tự ^M đó không? Đó chính là dấu hiệu cho thấy script của bạn đang dùng ký tự xuống dòng kiểu Windows (CRLF — \r\n) thay vì kiểu Unix (LF — \n).
Do các ký tự \r thừa đó, Linux đọc dòng shebang thành #!/bin/bash\r và đi tìm một file thực thi có tên đúng là /bin/bash^M. Tất nhiên file đó không tồn tại.
Chín trong mười trường hợp, lỗi này xảy ra khi script được viết trên Windows, chỉnh sửa bằng Notepad, hoặc clone từ repo mà Git đã âm thầm chuyển đổi ký tự xuống dòng lúc checkout thông qua cài đặt core.autocrlf.
Xác nhận nguyên nhân
Nên kiểm tra nhanh trước khi thay đổi bất cứ thứ gì. Chạy lệnh:
file deploy.sh
Một script có ký tự xuống dòng CRLF sẽ hiển thị rõ ràng:
deploy.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
Muốn xem các ký tự thô? cat -A sẽ hiển thị chúng — mỗi dòng sẽ có ^M ngay trước ký tự $:
cat -A deploy.sh | head -5
#!/bin/bash^M$
echo "Starting deploy..."^M$
Pattern ^M$ đó xác nhận vấn đề. Giờ tiến hành sửa thôi.
Cách sửa
Cách 1: dos2unix (Nhanh nhất)
Chỉ một lệnh là xong:
dos2unix deploy.sh
Có cả thư mục script? Chuyển đổi tất cả cùng lúc:
dos2unix *.sh
Hoặc đệ quy — hữu ích cho các project có shell script lồng nhau:
find . -name "*.sh" -exec dos2unix {} \;
Chưa cài? Một lệnh là xong:
# Debian/Ubuntu
sudo apt install dos2unix
# RHEL/CentOS/Fedora
sudo yum install dos2unix
# macOS
brew install dos2unix
Cách 2: sed (Không cần cài thêm công cụ)
Đã có sed? Xóa trực tiếp các ký tự carriage return:
sed -i 's/\r//' deploy.sh
sed trên macOS hơi khác — cần thêm chuỗi rỗng sau -i:
sed -i '' 's/\r//' deploy.sh
Cách 3: tr
Cách tiếp cận Unix cổ điển. Xuất ra file tạm, sau đó thay thế lại:
tr -d '\r' deploy_fixed.sh
mv deploy_fixed.sh deploy.sh
chmod +x deploy.sh
Cách 4: vim
Đang chỉnh sửa file trong vim? Sửa luôn mà không cần thoát ra:
:set ff=unix
:wq
Kiểm tra sau khi sửa
Chạy lại lệnh file. Script sạch sẽ trông như thế này — không còn đề cập đến CRLF:
file deploy.sh
# deploy.sh: Bourne-Again shell script, ASCII text executable
Kiểm tra thêm với cat -A:
cat -A deploy.sh | head -5
#!/bin/bash$
echo "Starting deploy..."$
Các dòng kết thúc chỉ bằng $ — không còn ^M. Mọi thứ ổn rồi. Chạy script thôi:
./deploy.sh
Phòng tránh lỗi tái diễn
Cấu hình Git
Nguyên nhân gốc rễ thường là cài đặt core.autocrlf của Git. Trên Windows, Git tự động chuyển LF → CRLF khi checkout. Đồng nghiệp dùng Windows clone repo về và vô tình làm hỏng tất cả shell script trong đó.
Khóa lại ở cấp độ repo bằng file .gitattributes:
# .gitattributes
*.sh text eol=lf
*.bash text eol=lf
Commit file này một lần. Từ đó trở đi, Git sẽ đảm bảo shell script luôn dùng LF bất kể ai đang dùng hệ điều hành nào.
Ngoài ra, nếu bạn đang làm việc trên Windows và muốn ngăn Git chuyển đổi ký tự xuống dòng trên toàn cục:
git config --global core.autocrlf input
Chế độ này giữ nguyên LF khi checkout và chuyển CRLF → LF khi commit. Đây là cài đặt an toàn hơn cho các team làm việc đa nền tảng.
VS Code
Đặt LF làm mặc định cho file mới trong cài đặt workspace (.vscode/settings.json):
{
"files.eol": "\n"
}
VS Code cũng hiển thị ký tự xuống dòng của file hiện tại ở thanh trạng thái — góc dưới bên phải, sẽ hiện CRLF hoặc LF. Nhấn vào để chuyển đổi ngay lập tức.
EditorConfig
Thêm file .editorconfig vào thư mục gốc của repo. Hầu hết các editor hiện đại đều tự nhận diện:
[*.sh]
end_of_line = lf
Kết hợp với .gitattributes, cách này bao phủ cả phần chỉnh sửa lẫn phần commit.
Tóm tắt nhanh
- Sửa nhanh nhất:
dos2unix yourscript.sh - Không cần cài thêm công cụ:
sed -i 's/\r//' yourscript.sh - Nguyên nhân gốc rễ: Ký tự xuống dòng kiểu Windows (
\r\n) làm hỏng dòng shebang - Phòng tránh lâu dài: Dùng
.gitattributesvới*.sh text eol=lf

