Akhir Baris CRLF dan LF: Apa yang Sebenarnya Rusak
Perbedaan antara CRLF dan LF hanya satu byte. Linux dan macOS memakai LF, satu karakter \n (0x0A), sebagai penanda akhir baris (line ending). Windows memakai CRLF, dua byte \r\n (0x0D 0x0A).
Hampir semua artikel lain salah di bagian ini. Skrip shell dengan akhir baris CRLF biasanya tidak gagal. Skrip itu berjalan, mencetak hasil yang Anda harapkan, dan keluar dengan kode 0. Yang rusak justru nilai yang mengendap di dalam variabel: penugasan ikut menyimpan \r di ujungnya, dan tidak ada satu pun yang memprotes.
Hasil pengukuran memecah perilakunya menjadi empat mode, urut dari yang paling sulit disadari:
- Sukses senyap — keluaran terlihat benar, variabel membawa
\ryang tak kasatmata, exit code 0. - Error yang tidak mengubah apa pun —
: command not foundpada baris kosong, skrip tetap berjalan sampai akhir, exit code 0. - Syntax error —
if,for, dan definisi fungsi rusak, exit code 2. bad interpreter— baris shebang membawa\r, exit code 126.
Sebelum melakukan apa pun, jalankan file yourfile. Satu baris keluarannya langsung memberi tahu Anda ada di sisi CRLF atau LF.
Semua yang ada di bawah ini berasal dari pengukuran di macOS (Darwin arm64) dengan bash 3.2.57(1)-release, zsh 5.9, dash, git 2.55.0, node v26.7.0, dan Python 3.14.6, lalu diperiksa silang di Linux lewat
docker run --rm bash:5, yaitu GNU bash 5.3.15(1)-release (aarch64-unknown-linux-musl).
1. CRLF, LF, dan CR: apa sebenarnya byte-nya
CRLF adalah singkatan dari carriage return line feed, dan memang benar-benar dua karakter yang ditempel jadi satu:
| Nama | Escape | Byte |
|---|---|---|
| Carriage return | \r | 0x0D |
| Line feed | \n | 0x0A |
| CRLF | \r\n | 0x0D 0x0A |
Platform mana menulis yang mana:
| Platform | Akhir baris |
|---|---|
| Windows | CRLF (\r\n) |
| Linux, macOS modern | LF (\n) |
| Mac Classic, OS 9 dan sebelumnya | CR (\r) sendirian |
Nama keduanya berasal dari dunia mesin. Pada teletype, carriage return menarik kepala cetak kembali ke margin kiri dan line feed memutar kertas naik satu baris. Windows mempertahankan kedua gerakan itu sebagai dua byte; Unix memutuskan satu saja sudah cukup. CR sendirian masih muncul di ekspor data lama, dan berkas yang penuh CR terlihat seperti satu baris raksasa bagi kebanyakan perkakas Unix.
Satu perintah memberi tahu Anda yang mana
file membaca byte-nya lalu menyebutkan nama terminatornya:
$ file d1.txt
d1.txt: ASCII text, with CRLF line terminators
$ file d2.txt
d2.txt: ASCII text ← no suffix means pure LF
$ file d3.txt
d3.txt: ASCII text, with CRLF, LF line terminators ← mixed, file lists both
Kasus ketiga yang paling layak Anda ingat. Berkas dengan akhir baris campuran berarti dua perkakas dengan setelan berbeda menulis ke berkas itu secara bergantian: editor menyimpan LF, skrip menambahkan CRLF, merge menjahit dua versi jadi satu. od -c menunjukkan pembagiannya byte demi byte:
$ od -c d3.txt
0000000 a \r \n b \n
0000005
Akhir baris hidup di lapisan byte, bertetangga dengan pengodean karakter; panduan pengodean UTF-8 dan UTF-16 membahas apa yang terjadi satu tingkat di atasnya.
2. Empat mode kegagalan, dari sukses senyap sampai exit 126
Berkas \r\n yang sama, lima hasil berbeda tergantung isi barisnya:
| Isi skrip | Apa yang sebenarnya terjadi | Exit |
|---|---|---|
echo hello dan perintah sederhana lain | Sukses senyap, keluarannya benar | 0 |
Penugasan X=abc | Sukses senyap, tetapi nilainya berakhir dengan \r | 0 |
Baris kosong, lanjutan baris dengan \ di ujung | : command not found, skrip tetap jalan sampai akhir | 0 |
if/fi, for/do/done, f() { | syntax error near unexpected token | 2 |
Baris shebang dengan \r | bad interpreter: No such file or directory | 126 |
Dua baris pertama itulah alasan halaman ini ada. Kalimat “CRLF menyebabkan command not found” beredar di mana-mana, padahal bukan itu yang terjadi. Perintah sederhana sama sekali tidak protes.
Sukses senyap adalah yang paling berbahaya
$ printf 'echo hello\r\necho world\r\n' > win.sh && bash win.sh
hello
world
[exit=0]
Dua baris masuk, dua baris keluar, exit 0. Tidak ada yang perlu di-debug dan tidak ada kata kunci yang bisa Anda cari di log. Suruh skrip yang sama mengerjakan satu perbandingan, dan situasinya langsung berubah:
$ printf 'V=1.2.3\r\ntest "$V" = "1.2.3" && echo MATCH || echo NO-MATCH\r\n' > v.sh
$ bash v.sh
NO-MATCH
[exit=0]
$V berisi 1.2.3\r, bukan 1.2.3. Hasilnya identik di macOS dan Linux. Gerbang versi, if [ "$ENV" = "prod" ], feature flag yang Anda baca dari berkas: semuanya mengambil cabang yang salah, tanpa suara, dengan exit code 0. Inilah bug akhir baris yang paling sulit ketahuan di CI, karena build-nya hijau dan log-nya bersih.
Error yang tidak menghentikan apa pun
Baris yang isinya hanya \r adalah wujud baris kosong di dalam berkas CRLF, dan shell memperlakukannya sebagai perintah yang harus ia jalankan. Perintah itu gagal, mencetak pesan, lalu skrip lanjut ke baris berikutnya dan selesai dengan exit 0. Kata persisnya bergantung pada shell Anda.
Lanjutan baris dengan backslash di ujung rusak lewat jalur yang sama. Byte \r duduk di antara backslash dan newline, sehingga lanjutan itu berhenti menjadi lanjutan dan baris sesudahnya berjalan sendirian.
Syntax error, dan alasan fi bukan fi
c_if.sh: line 5: syntax error: unexpected end of file
[exit=2]
bash 5.3.15 di Linux bercerita lebih banyak tentang berkas yang sama:
i.sh: line 5: syntax error: unexpected end of file from `if' command on line 2
Perulangan dan definisi fungsi justru gagal di baris 1:
c_for.sh: line 1: syntax error near unexpected token `do'
c_for.sh: line 1: `for i in 1 2; do'
c_func.sh: line 1: syntax error near unexpected token `{'
c_func.sh: line 1: `f() {'
Mekanismenya membuat seluruh kelas error ini bisa ditebak. Bash membaca kata penutupnya sebagai fi\r, bukan fi. Karena fi\r bukan kata kunci fi, blok if tidak pernah tertutup dan bash terus membaca sampai berkasnya habis. Itulah sebabnya pesan errornya menunjuk ke baris terakhir, bukan ke baris yang rusak.
bad interpreter dan exit 126
$ ./s.sh
bash: ./s.sh: /bin/bash^M: bad interpreter: No such file or directory
[exit=126]
macOS dan Linux mencetak teks yang sama dan keduanya keluar dengan 126. Perhatikan path di dalam pesannya: /bin/bash^M. Kernel mengambil semua yang ada setelah #! sampai newline sebagai path interpreter, dan \r ikut terhitung sebagai bagiannya. Berkas dengan nama seperti itu tidak ada, jadi exec gagal sebelum satu baris pun skrip Anda sempat berjalan.
Exit 126 juga mencakup “ditemukan, tetapi tidak bisa dieksekusi”, jadi skrip yang menolak jalan belum tentu masalah akhir baris; izin berkas Linux menghasilkan kegagalan dari keluarga yang sama. Kehadiran ^M di dalam path itulah yang membedakan keduanya.
Kenapa Anda tidak pernah melihat \r itu
Membaca keluaran tidak menolong, karena byte itu tidak menyisakan apa pun untuk dilihat atau diseleksi. Anda harus memaksanya muncul:
$ bash d.sh # script: HOST=example.com / echo "connecting to $HOST:8080"
connecting to example.com:8080 ← what you get
$ bash d.sh | cat -v
connecting to example.com^M:8080^M ← what is actually there
$ bash d.sh | od -c
0000000 c o n n e c t i n g t o e x
0000020 a m p l e . c o m \r : 8 0 8 0 \r
0000040 \n
Dua byte \r, tak terlihat pada keluaran normal, keduanya berada di dalam string yang sebentar lagi jadi hostname. Kadang byte itu ikut menumpang ke argumen dan perkakas lain yang tidak bersalah justru kena tuduh:
$ bash v.sh # the script pipes through: ... | head -2
head: illegal line count -- 2\r
head berperilaku benar. Skrip itulah yang menyerahkan 2\r kepadanya. Setiap pesan error yang mengandung \r nyasar di dalam nilai yang dikutip adalah bug yang sama, cuma sedang memakai nama orang lain.
3. Kenapa pesan error Anda sama sekali tidak mirip yang beredar di internet
Satu berkas yang sama, printf 'echo a\r\n\r\necho b\r\n' dengan baris 2 berupa baris kosong yang isinya hanya \r, berjalan di empat shell:
| Shell | Versi | Pesan persisnya |
|---|---|---|
| bash (bawaan macOS) | 3.2.57(1)-release | s_blank.sh: line 2: : command not found |
| bash (Linux arus utama) | 5.3.15(1)-release | t.sh: line 2: $'\r': command not found |
| zsh | 5.9 | s_blank.sh:2: command not found: ^M |
| dash | — | s_blank.sh: 2: : not found |
Hampir semua hasil pencarian mengutip baris kedua. Bash 4 dan 5 mencetak karakter non-printable memakai ANSI-C quoting, yang mengubah carriage return menjadi $'\r'. Bash bawaan macOS masih 3.2 dan tidak melakukannya, jadi yang Anda dapat hanyalah titik dua, spasi, lalu kekosongan di antaranya. zsh mencetak ^M. dash malah membuang kata “command” sama sekali.
Satu kesalahan, empat pesan. Kalau Anda pernah menempelkan error persis milik Anda ke kotak pencarian dan tidak mendapat apa-apa yang berguna, itulah sebabnya. Pesan polos : command not found adalah bug yang sama dengan versi terkenalnya.
4. Git: apa yang sebenarnya disimpan core.autocrlf di repositori Anda
Sebagian besar penjelasan tentang git autocrlf berhenti di definisi. Pertanyaan yang berguna justru berbeda: apa yang akhirnya masuk ke commit, dan apa yang didapat rekan tim saat mereka checkout? Pengukurannya memakai git cat-file -p HEAD:f.txt untuk blob yang tersimpan dan rm f.txt && git checkout -- f.txt untuk salinan kerja:
core.autocrlf | Berkas sumber | Blob di repositori | Salinan kerja setelah checkout |
|---|---|---|---|
true | CRLF | LF | CRLF |
true | LF | LF | CRLF |
input | CRLF | LF | LF |
input | LF | LF | LF |
false | CRLF | CRLF | CRLF |
false | LF | LF | LF |
Tiga hal langsung terbaca dari tabel itu:
truedaninputsama-sama menjamin LF di repositori. Bedanya cuma pada checkout:truemengubahnya kembali menjadi CRLF,inputmembiarkan berkasnya apa adanya.- Hanya
falseyang memasukkan CRLF ke dalam commit. Ketika ada yang bertanya siapa yang meng-commit carriage return itu, baris inilah jawabannya. - Baris kedua adalah kejutannya. Dengan
true, berkas yang di disk berupa LF kembali menjadi CRLF setelah checkout.
Git mengumumkan penulisan ulang itu sebelum terjadi, dalam salah satu dari dua bentuk:
warning: in the working copy of 'f.txt', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'f.txt', CRLF will be replaced by LF the next time Git touches it
“Saya tidak mengubah apa pun, tetapi git bilang seluruh berkas berubah”
Baris kedua lagi. Dengan git config core.autocrlf true, checkout menulis ulang berkas LF menjadi CRLF di direktori kerja. Setiap baris kini berbeda satu byte dari blob-nya, jadi git diff melaporkan semua baris sebagai berubah dan pull request menampilkan berkas yang tak pernah disentuh siapa pun sebagai ditulis ulang seluruhnya. Filter checkout-lah pelakunya.
Bayangan cerminnya menghasilkan kebisingan yang sama: rekan tim yang memakai false meng-commit CRLF, Anda memakai input, dan berkas yang belum pernah Anda buka muncul di diff Anda.
5. .gitattributes adalah jawaban di level tim
core.autocrlf adalah setelan di satu mesin, tak terlihat oleh siapa pun selain pemiliknya. .gitattributes adalah berkas di dalam repositori, jadi ia ikut terbawa di setiap clone. Ketika keduanya berselisih, atribut yang menang. Hasil pengujian empat bentuknya:
.gitattributes | core.autocrlf | Sumber | Blob | Setelah checkout | Pemenang |
|---|---|---|---|---|---|
* text=auto | false | CRLF | LF | LF | atribut |
* text eol=crlf | input | LF | LF | CRLF | atribut |
* -text | true | CRLF | CRLF | CRLF | atribut |
* text eol=lf | true | CRLF | LF | LF | atribut |
Baris ketiga layak Anda ingat: -text mematikan konversi sepenuhnya dan tetap mengalahkan core.autocrlf=true. Begitulah cara melindungi berkas yang byte-nya harus selamat tanpa tersentuh.
.gitattributes yang bisa Anda salin
* text=auto
*.sh text eol=lf
*.bash text eol=lf
Makefile text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
*.png -text
*.jpg -text
*.pdf -text
*.zip -text
text=auto menormalkan apa pun yang dideteksi Git sebagai teks menjadi LF di repositori. Baris eol=lf yang eksplisit menangani berkas yang wajib LF tanpa peduli siapa penulisnya, karena .sh dengan CRLF adalah exit 126 yang tinggal menunggu waktu. Jenis skrip Windows mendapat eol=crlf dengan alasan yang berkebalikan, dan pola berkas biner mendapat -text supaya Git tidak mengonversi apa pun.
core.safecrlf dan core.eol
Dua setelan yang sering muncul bersebelahan dengan core.autocrlf tetapi mengerjakan hal berbeda:
core.safecrlfadalah penjaga, bukan pengubah. Ketika konversi tidak bisa bolak-balik utuh (berkas campuran, misalnya, yang kehilangan informasi begitu Git menormalkannya), nilaitruemenolak operasinya danwarnmeloloskannya disertai peringatan. Setelan ini tidak pernah mengubah byte mana yang tersimpan; ia hanya menolak menjalankan konversi yang merugikan secara diam-diam.core.eolmemilih akhir baris mana yang ditulis Git di direktori kerja untuk berkas bertandatext, saatcore.autocrlfbernilaifalse. Nilainyalf,crlf, dannative.core.autocrlfmenimpanya, dan itulah sebabnya menyetelcore.eoldi mesin yang autocrlf-nya masih menyala biasanya terasa seperti tidak berpengaruh apa-apa.
Mengubah .gitattributes tidak memperbaiki berkas yang sudah di-commit
Atribut baru berlaku saat Git menulis atau membaca berkas, jadi isi yang sudah ada tetap apa adanya sampai ada yang menulis ulang. Paksa sendiri prosesnya:
$ git add --renormalize .
$ git commit -m "Normalize line endings"
Siapkan diri untuk satu diff raksasa, dan memang itu tujuannya. Kerjakan di branch tersendiri, gabungkan lewat satu commit, dan beri tahu semua orang sebelum perubahan itu mendarat.
6. Mengubah CRLF menjadi LF, dan sebaliknya
Empat cara, semuanya terbukti membuang \r di Darwin:
| Perintah | Hasil |
|---|---|
tr -d '\r' < f > f.out | berhasil |
perl -pi -e 's/\r\n/\n/g' f | berhasil |
sed -i '' -e 's/\r$//' f (bentuk BSD) | berhasil |
sed -i -e 's/\r$//' f (bentuk GNU, dijalankan di macOS) | berhasil, dan meninggalkan berkas sampah |
Jebakan sed -i versi GNU di macOS
sed versi BSD mengharuskan -i diikuti sufiks cadangan. Salin tutorial Linux mentah-mentah, dan sed BSD menelan -e sebagai sufiks tersebut. Suntingan Anda tetap terjadi, begitu pula ini:
a5.txt
a5.txt-e
a5.txt-e adalah salinan cadangan, muncul karena sed membaca -e sebagai sufiks yang Anda minta. Bentuk yang benar di macOS memberikan string kosong secara eksplisit: sed -i '' -e 's/\r$//' f. Repositori yang berisi berkas berakhiran -e adalah repositori tempat seseorang pernah menjalankan one-liner GNU di Mac.
macOS tidak punya dos2unix
command -v dos2unix tidak mengembalikan apa pun di macOS bawaan; binernya baru ada setelah brew install dos2unix. Itulah sebabnya jawaban yang paling banyak disalin di internet justru gagal di mesin tempat banyak developer mengetik setiap hari. tr -d '\r' tidak butuh instalasi dan mengerjakan tugas yang sama.
Untuk arah sebaliknya, unix2dos punya masalah ketersediaan yang sama, dan sed -e 's/$/\r/' bisa menggantikannya.
Begitu berkas kembali menjadi LF yang bersih, Anda aman memperlakukannya sebagai daftar baris lagi. Ini penting untuk apa pun yang membandingkan baris sebagai string utuh: urutkan baris teks dan hapus baris duplikat sama-sama membaca value\r dan value sebagai dua baris berbeda, sehingga satu carriage return nyasar diam-diam menggagalkan de-duplikasi.
Di editor Anda
VS Code menampilkan akhir baris berkas yang sedang terbuka sebagai CRLF atau LF di status bar pojok kanan bawah, dan mengekliknya langsung mengalihkan berkas itu. Setelan files.eol mengatur nilai default untuk berkas baru, dan menyetelnya per workspace menjaga tim yang campur platform tetap konsisten. Editor lain menyediakan dua kontrol yang sama dengan nama berbeda. Bagian yang sering luput: indikator per berkas dan setelan default itu terpisah, jadi mengubah salah satunya tidak menyentuh yang lain.
7. Menangani akhir baris di dalam kode
Tujuh titik masuk membaca berkas yang sama, line1\r\nline2\r\n:
| Titik masuk | Apa yang Anda dapat | \r |
|---|---|---|
Node fs.readFileSync(f,"utf8") | "line1\r\nline2\r\n" | tetap ada |
Node, nilai yang sama + .split("\n") | ["line1\r","line2\r",""] | tetap ada di setiap baris |
Node readline dengan crlfDelay:Infinity | ["line1","line2"] | hilang |
Python open(f), mode default | 'line1\nline2\n' | berubah |
Python open(f).readlines() | ['line1\n','line2\n'] | berubah |
Python open(f, newline="") | 'line1\r\nline2\r\n' | tetap ada |
Python open(f,"rb") | b'line1\r\nline2\r\n' | tetap ada |
Tabel itu menuntaskan satu laporan bug yang sudah akrab: jalan di Python, rusak di Node. Mode teks default Python menerapkan universal newlines dan menerjemahkan \r\n menjadi \n sebelum Anda sempat melihatnya. Node menyerahkan byte-nya apa adanya. Tak satu pun dari keduanya salah, tetapi keduanya langsung berselisih begitu membaca berkas yang sama.
rstrip("\n") meninggalkan \r di tempatnya
original: 'line1\r\n' | rstrip("\n"): 'line1\r' | strip(): 'line1'
rstrip("\n") membuang persis karakter yang Anda sebutkan, dan \r tidak ada dalam daftar itu. Hasilnya lalu tidak pernah sama dengan nilai yang seharusnya cocok. Itulah jawaban jujur untuk keluhan “sudah saya strip tapi tetap tidak sama”. Pakai strip(), atau rstrip() tanpa argumen, dan semua spasi di ujung ikut lenyap termasuk carriage return.
Memecah baris dengan aman di kedua platform
Di JavaScript, pecah memakai pola yang menoleransi kedua akhir baris: text.split(/\r?\n/). Di Python, tetaplah di mode teks default dan biarkan universal newlines yang mengurusnya, atau panggil splitlines(), yang sanggup menangani \r\n, \n, dan \r sendirian.
Menulis adalah separuh masalah yang lain. Node menuliskan byte yang Anda berikan, jadi susunlah string dengan \n dan biarkan .gitattributes yang menentukan apa yang mendarat di disk. open(path, "w") milik Python menerjemahkan \n menjadi akhir baris platform kecuali Anda memberi newline="", flag yang diminta penulis CSV persis karena alasan ini.
Carriage return di ujung baris dan byte-order mark di awal berkas adalah kategori bug yang sama dari dua ujung berlawanan: satu byte tak kasatmata yang selamat dari copy-paste lalu merusak pemeriksaan kesamaan. Panduan mengatasi BOM UTF-8 membahas ujung yang satunya.
8. Di mana lagi akhir baris menggigit
CSV dan Excel. RFC 4180 menetapkan CRLF sebagai pemisah record, sehingga CSV menjadi satu dari sedikit tempat yang justru benar memakai CRLF alih-alih menganggapnya cacat. Parser yang penulisnya cuma memikirkan masukan LF meninggalkan \r di field terakhir setiap baris, jadi kalau nilai hasil konversi CSV ke JSON terlihat benar tetapi hasil perbandingannya salah, periksa byte itu lebih dulu.
Docker. Berkas .sh dengan CRLF yang Anda salin ke dalam image adalah mode kegagalan nomor empat. COPY mempertahankan byte, shebang tetap membawa \r-nya, dan kontainer keluar dengan 126. Satu baris *.sh text eol=lf di .gitattributes mencegah seluruh kelas masalah ini.
Diff dan pull request. Berkas yang muncul sebagai berubah total tanpa suntingan yang terlihat adalah mekanisme bagian 4 yang tiba di ruang code review. Membandingkan kedua versinya dengan text diff memastikannya dalam hitungan detik, dan panduan text diff menjelaskan cara membaca hasilnya.
Berkas campuran. ASCII text, with CRLF, LF line terminators berarti ada dua penulis dengan setelan berbeda. Normalkan seluruh berkasnya, bukan cuma baris yang kebetulan Anda sunting, atau diff berikutnya akan sama berisiknya.
FAQ
Apa bedanya CRLF dan LF?
CRLF terdiri dari dua byte, \r\n (0x0D 0x0A). LF hanya satu byte, \n (0x0A). Windows menulis CRLF, Linux dan macOS menulis LF, dan keduanya menandai akhir baris. Teksnya terlihat identik di editor; perbedaannya baru muncul di level byte, perbandingan string, dan diff.
Skrip saya memakai akhir baris CRLF. Kenapa tidak ada error?
Karena perintah sederhana selamat dari byte tambahan itu. echo hello dengan \r di ujungnya tetap jalan dan keluar dengan 0. Error hanya muncul di tempat yang diperhatikan parser: baris kosong, kata kunci seperti fi, atau shebang. Penugasan variabel adalah kasus berbahayanya, karena ia berhasil sekaligus menyimpan \r ke dalam variabel.
Apa arti $'\r': command not found, dan kenapa saya tidak melihatnya?
Artinya shell mencoba menjalankan baris yang isinya hanya carriage return. Bash 4 dan 5 mencetak karakter itu dengan ANSI-C quoting, sehingga muncul $'\r'. Bash 3.2 bawaan macOS tidak mencetak apa pun di antara dua titik dua, sedangkan zsh mencetak ^M. Satu kesalahan, tiga pesan berbeda.
core.autocrlf sebaiknya true, input, atau false?
Pakai input di Linux dan macOS, true di Windows, dan utamakan .gitattributes di atas keduanya. Hasil pengukuran menunjukkan true dan input sama-sama menyimpan LF di repositori dan hanya false yang meloloskan CRLF ke dalam commit. Selain itu true menulis ulang berkas LF menjadi CRLF di salinan kerja Anda saat checkout.
Kalau .gitattributes dan core.autocrlf berselisih, mana yang menang?
.gitattributes yang menang. Keempat bentuknya, * text=auto, * text eol=crlf, * -text, dan * text eol=lf, sama-sama mengalahkan nilai core.autocrlf lokal. Itulah argumen untuk memakainya: atribut ikut ter-commit dan berlaku untuk semua orang, sedangkan core.autocrlf adalah setelan per mesin yang tidak bisa Anda lihat maupun paksakan.
Bagaimana cara memeriksa berkas memakai CRLF atau LF?
Jalankan file yourfile. CRLF mencetak ASCII text, with CRLF line terminators, LF murni mencetak ASCII text tanpa akhiran sama sekali, dan berkas campuran mencetak with CRLF, LF line terminators. Untuk kepastian di level byte, jalankan od -c lalu cari \r yang duduk sebelum setiap \n.
Kapan sebenarnya CRLF layak dipakai?
Ketika sebuah format atau protokol memang mensyaratkannya. RFC 4180 mendefinisikan CRLF sebagai pemisah record untuk CSV, dan header HTTP serta SMTP melakukan hal yang sama di jaringan. Berkas batch Windows dan PowerShell juga lebih aman dengan CRLF. Selebihnya — kode sumber, skrip shell, konfigurasi — pakai LF.