Bagaimana cara mengubah DOS / Windows newline (CRLF) ke Unix newline (LF) dalam skrip Bash?

336

Bagaimana saya bisa secara pemrograman (yaitu, tidak menggunakan vi ) mengkonversi DOS / Windows baris baru ke Unix?

The dos2unixdan unix2dosperintah yang tidak tersedia pada sistem tertentu. Bagaimana saya bisa meniru ini dengan perintah seperti sed/ awk/ tr?

Koran Molovik
sumber
9
Secara umum, cukup instal dos2unixmenggunakan manajer paket Anda, itu benar-benar jauh lebih sederhana dan memang ada di sebagian besar platform.
Brad Koch
1
Sepakat! @BradKoch Sederhana sebagai 'buatan install dos2unix' di Mac OSX
SmileIT

Jawaban:

323

Anda dapat menggunakan truntuk mengkonversi dari DOS ke Unix; namun, Anda hanya dapat melakukan ini dengan aman jika CR muncul di file Anda hanya sebagai byte pertama dari pasangan byte CRLF. Ini biasanya terjadi. Anda kemudian menggunakan:

tr -d '\015' <DOS-file >UNIX-file

Perhatikan bahwa namanya DOS-fileberbeda dari namanyaUNIX-file ; jika Anda mencoba menggunakan nama yang sama dua kali, Anda akan berakhir tanpa data dalam file.

Anda tidak dapat melakukannya sebaliknya (dengan standar 'tr').

Jika Anda tahu cara memasukkan carriage return ke skrip ( control-V, control-Muntuk memasukkan control-M), maka:

sed 's/^M$//'     # DOS to Unix
sed 's/$/^M/'     # Unix to DOS

di mana '^ M' adalah karakter kontrol-M. Anda juga dapat menggunakan mekanisme bash Kutipan ANSI-C untuk menentukan carriage return:

sed $'s/\r$//'     # DOS to Unix
sed $'s/$/\r/'     # Unix to DOS

Namun, jika Anda harus melakukan ini sangat sering (lebih dari sekali, secara kasar), jauh lebih masuk akal untuk menginstal program konversi (misalnya dos2unixdan unix2dos, atau mungkin dtoudan utod) dan menggunakannya.

Jika Anda perlu memproses seluruh direktori dan subdirektori, Anda dapat menggunakan zip:

zip -r -ll zipfile.zip somedir/
unzip zipfile.zip

Ini akan membuat arsip zip dengan ujung garis diubah dari CRLF ke CR. unzipkemudian akan mengembalikan file yang dikonversi (dan meminta Anda file demi file - Anda dapat menjawab: Ya-untuk-semua). Kredit ke @vmsnomad untuk menunjukkan ini.

Jonathan Leffler
sumber
9
menggunakan tr -d '\015' <DOS-file >UNIX-filemana DOS-file== UNIX-filehanya menghasilkan file kosong. File keluaran harus file yang berbeda, sayangnya.
Buttle Butkus
3
@ButtleButkus: Ya, ya; itu sebabnya saya menggunakan dua nama yang berbeda. Jika Anda zap file input sebelum program membaca semuanya, seperti yang Anda lakukan ketika Anda menggunakan nama yang sama dua kali, Anda berakhir dengan file kosong. Itu adalah perilaku seragam pada sistem seperti Unix. Dibutuhkan kode khusus untuk menangani menimpa file input dengan aman. Ikuti instruksi dan Anda akan baik-baik saja.
Jonathan Leffler
Sepertinya saya ingat fungsi in-file search-replace entah di mana.
Buttle Butkus
4
Ada beberapa tempat; Anda harus tahu di mana menemukannya. Dalam batas, sedopsi GNU -i(untuk di tempat) berfungsi; batasnya adalah file dan symlink yang ditautkan. The sortperintah memiliki 'selalu' (sejak tahun 1979, jika tidak sebelumnya) mendukung -oopsi yang bisa daftar salah satu file masukan. Namun, itu sebagian karena sortharus membaca semua inputnya sebelum dapat menulis outputnya. Program lain secara sporadis mendukung menimpa salah satu file input mereka. Anda dapat menemukan program tujuan umum (skrip) untuk menghindari masalah di 'Lingkungan Pemrograman UNIX' oleh Kernighan & Pike.
Jonathan Leffler
3
Opsi ketiga bekerja untuk saya, terima kasih. Saya memang menggunakan opsi -i: sed -i $'s/\r$//' filename- untuk mengedit di tempat. Saya bekerja pada mesin yang tidak memiliki akses ke internet, jadi instalasi perangkat lunak adalah masalah.
Warren Dew
64
tr -d "\r" < file

lihat di sini untuk contoh menggunakan sed:

# IN UNIX ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format.
sed 's/.$//'               # assumes that all lines end with CR/LF
sed 's/^M$//'              # in bash/tcsh, press Ctrl-V then Ctrl-M
sed 's/\x0D$//'            # works on ssed, gsed 3.02.80 or higher

# IN UNIX ENVIRONMENT: convert Unix newlines (LF) to DOS format.
sed "s/$/`echo -e \\\r`/"            # command line under ksh
sed 's/$'"/`echo \\\r`/"             # command line under bash
sed "s/$/`echo \\\r`/"               # command line under zsh
sed 's/$/\r/'                        # gsed 3.02.80 or higher

Gunakan sed -iuntuk konversi di tempat misalnya sed -i 's/..../' file.

ghostdog74
sumber
10
Saya menggunakan varian karena file saya hanya memiliki \r:tr "\r" "\n" < infile > outfile
Matt Todd
1
@MattTodd dapatkah Anda memposting ini sebagai jawaban? yang -dditampilkan lebih sering dan tidak akan membantu dalam "hanya \r" situasi.
n611x007
5
Perhatikan bahwa usulan \runtuk \npemetaan memiliki efek penspasian ganda file; setiap baris CRLF tunggal yang berakhir di DOS menjadi \n\ndi Unix.
Jonathan Leffler
Bisakah saya melakukan ini secara rekursif?
Aaron Franke
36

Melakukan ini dengan POSIX itu rumit:

  • POSIX Sed tidak mendukung \ratau \15. Bahkan jika itu terjadi, opsi di tempat -ibukanlah POSIX

  • POSIX Awk mendukung \rdan \15, bagaimanapun, -i inplacepilihannya bukan POSIX

  • d2u dan dos2unix bukan utilitas POSIX , tetapi ex adalah

  • POSIX ex tidak mendukung \r, \15, \natau\12

Untuk menghapus pengembalian carriage:

ex -bsc '%!awk "{sub(/\r/,\"\")}1"' -cx file

Untuk menambahkan pengembalian carriage:

ex -bsc '%!awk "{sub(/$/,\"\r\")}1"' -cx file
Steven Penny
sumber
2
Sepertinya mendukung POSIX . tr\r Jadi Anda juga bisa menggunakan printf '%s\n' '%!tr -d "\r"' x | ex file(meskipun diberikan, ini dihapus \rbahkan jika tidak segera sebelumnya \n). Juga, -bopsi untuk extidak ditentukan oleh POSIX.
Wildcard
1
Melakukan ini dalam POSIX itu mudah. Cantumkan CR literal dalam skrip dengan mengetiknya (ini kontrol-M).
Joshua
28

Anda dapat menggunakan vim secara terprogram dengan opsi -c {command}:

Dosis ke Unix:

vim file.txt -c "set ff=unix" -c ":wq"

Unix to dos:

vim file.txt -c "set ff=dos" -c ":wq"

"set ff = unix / dos" berarti mengubah format file (ff) dari file ke Unix / DOS format akhir baris

": wq" berarti menulis file ke disk dan keluar dari editor (memungkinkan untuk menggunakan perintah dalam satu lingkaran)

Johan Zicola
sumber
3
Ini tampak seperti solusi yang paling elegan tetapi kurangnya penjelasan tentang apa arti wq sangat disayangkan.
Jorrick Sleijster
5
Siapa pun yang menggunakan viakan tahu apa :wqartinya. Bagi mereka yang tidak memiliki 3 karakter berarti 1) membuka area perintah vi, 2) menulis dan 3) berhenti.
David Newcomb
Saya tidak tahu Anda bisa secara interaktif menambahkan perintah ke vim dari CLI
Robert Dundon
Anda dapat menggunakan ": x" bukan ": wq"
JosephConrad
25

Menggunakan AWK dapat Anda lakukan:

awk '{ sub("\r$", ""); print }' dos.txt > unix.txt

Menggunakan Perl dapat Anda lakukan:

perl -pe 's/\r$//' < dos.txt > unix.txt
codaddict
sumber
2
Solusi portabel yang bagus awk.
mklement0
24

Untuk mengonversi file di tempat, gunakan

dos2unix <filename>

Untuk menampilkan teks yang dikonversi ke penggunaan file yang berbeda

dos2unix -n <input-file> <output-file>

Anda dapat menginstalnya di Ubuntu atau Debian dengan

sudo apt install dos2unix

atau di macOS menggunakan homebrew

brew install dos2unix
Boris
sumber
1
Saya tahu pertanyaannya meminta alternatif untuk dos2unix tapi ini hasil google pertama.
Boris
18

Masalah ini dapat diselesaikan dengan alat standar, tetapi ada cukup banyak perangkap untuk waspada yang saya sarankan Anda menginstal flipperintah, yang ditulis lebih dari 20 tahun yang lalu oleh Rahul Dhesi, penulis zoo. Itu melakukan pekerjaan yang sangat baik untuk mengkonversi format file sementara, misalnya, menghindari penghancuran file biner yang tidak disengaja, yang agak terlalu mudah jika Anda hanya berlomba mengubah setiap CRLF yang Anda lihat ...

Norman Ramsey
sumber
Adakah cara untuk melakukan ini dengan cara streaming, tanpa memodifikasi file asli?
augurar
@augurar Anda dapat memeriksa "paket yang sama" packages.debian.org/wheezy/flip
n611x007
Saya memiliki pengalaman memecahkan setengah dari OS saya hanya dengan menjalankan texxto dengan bendera yang salah. Hati-hati terutama jika Anda ingin melakukannya di seluruh folder.
A_P
14

Solusi yang diposting sejauh ini hanya menangani sebagian masalah, mengubah DOS / Windows CRLF menjadi LF Unix; bagian mereka hilang adalah bahwa DOS digunakan CRLF sebagai garis pemisah , sementara Unix menggunakan LF sebagai garis terminator . Perbedaannya adalah bahwa file DOS (biasanya) tidak akan memiliki apa pun setelah baris terakhir dalam file, sementara Unix akan. Untuk melakukan konversi dengan benar, Anda perlu menambahkan LF akhir itu (kecuali file tersebut panjangnya nol, artinya tidak ada baris sama sekali). Mantra favorit saya untuk ini (dengan sedikit logika ditambahkan untuk menangani file-file yang dipisahkan dengan CR gaya Mac, dan bukan file yang dianiaya yang sudah dalam format unix) sedikit perl:

perl -pe 'if ( s/\r\n?/\n/g ) { $f=1 }; if ( $f || ! $m ) { s/([^\n])\z/$1\n/ }; $m=1' PCfile.txt

Perhatikan bahwa ini mengirimkan versi Unixified file ke stdout. Jika Anda ingin mengganti file dengan versi Unixified, tambahkan -ibendera perl .

Gordon Davisson
sumber
@LudovicZenohateLagouardette Apakah itu file teks biasa (mis. Csv atau teks tab-demited), atau yang lainnya? Jika itu dalam beberapa format database-ish, memanipulasinya seolah-olah teks sangat mungkin merusak struktur internalnya.
Gordon Davisson
Csv teks biasa, tapi saya pikir enconding itu aneh. Saya pikir itu kacau karena itu. Namun jangan khawatir. Saya selalu mengumpulkan cadangan dan ini bahkan bukan dataset asli, hanya satu GB. Yang asli adalah 26GB.
Ludovic Zenohate Lagouardette
14

Jika Anda tidak memiliki akses ke dos2unix , tetapi dapat membaca halaman ini, maka Anda dapat menyalin / menempelkan dos2unix.py dari sini.

#!/usr/bin/env python
"""\
convert dos linefeeds (crlf) to unix (lf)
usage: dos2unix.py <input> <output>
"""
import sys

if len(sys.argv[1:]) != 2:
  sys.exit(__doc__)

content = ''
outsize = 0
with open(sys.argv[1], 'rb') as infile:
  content = infile.read()
with open(sys.argv[2], 'wb') as output:
  for line in content.splitlines():
    outsize += len(line) + 1
    output.write(line + '\n')

print("Done. Saved %s bytes." % (len(content)-outsize))

Diposting silang dari superuser .

techtonik anatoly
sumber
1
Penggunaannya menyesatkan. Nyata dos2unixmengkonversi semua file input secara default. Penggunaan Anda menyiratkan -nparameter. Dan sebenarnya dos2unixadalah filter yang membaca dari stdin, menulis ke stdout jika file tidak diberikan.
jfs
8

Super duper mudah dengan PCRE;

Sebagai skrip, atau ganti $@dengan file Anda.

#!/usr/bin/env bash
perl -pi -e 's/\r\n/\n/g' -- $@

Ini akan menimpa file Anda di tempat!

Saya sarankan hanya melakukan ini dengan cadangan (kontrol versi atau yang lain)

ThorSummoner
sumber
Terima kasih! Ini berfungsi, meskipun saya sedang menulis nama file dan tidak --. Saya memilih solusi ini karena mudah dimengerti dan beradaptasi untuk saya. FYI, inilah yang dilakukan oleh switch: -pmenganggap loop "selagi input", -iedit file input pada tempatnya, -ejalankan perintah berikut
Rolf
Sebenarnya, PCRE adalah implementasi ulang mesin regex Perl, bukan mesin regex dari Perl. Mereka berdua memiliki kemampuan ini, meskipun ada juga perbedaan, terlepas dari implikasi dalam nama.
tripleee
6

Solusi awk yang lebih sederhana dengan program:

awk -v ORS='\r\n' '1' unix.txt > dos.txt

Secara teknis '1' adalah program Anda, b / c awk memerlukannya ketika diberikan opsi.

UPDATE : Setelah meninjau kembali halaman ini untuk pertama kalinya dalam waktu yang lama, saya menyadari bahwa belum ada yang memposting solusi internal, jadi ini dia:

while IFS= read -r line;
do printf '%s\n' "${line%$'\r'}";
done < dos.txt > unix.txt
nawK
sumber
Itu berguna, tetapi hanya untuk menjadi jelas: ini menerjemahkan Unix -> Windows / DOS, yang merupakan arah berlawanan dari apa yang diminta OP.
mklement0
5
Itu dilakukan dengan sengaja, dibiarkan sebagai latihan untuk penulis. eyerolls awk -v RS='\r\n' '1' dos.txt > unix.txt
nawK
Hebat (dan pujian untuk Anda karena kemahiran pedagogik).
mklement0
1
"b / c awk membutuhkan satu ketika diberikan opsi." - awk selalu membutuhkan program, apakah opsi ditentukan atau tidak.
mklement0
1
Solusi pesta murni menarik, tapi jauh lebih lambat dari yang setara awkatau sedsolusi. Selain itu, Anda harus menggunakan while IFS= read -r lineuntuk menjaga jalur input dengan setia, jika tidak spasi spasi awal dan akhir tidak terpotong (atau gunakan nama variabel dalam readperintah dan bekerjalah $REPLY).
mklement0
5

Baru saja merenungkan pertanyaan yang sama (di sisi Windows, tetapi sama-sama berlaku untuk linux.) Ternyata tidak ada yang menyebutkan cara otomatis melakukan konversi CRLF <-> LF untuk file teks menggunakan zip -llopsi lama yang baik (Info-ZIP):

zip -ll textfiles-lf.zip files-with-crlf-eol.*
unzip textfiles-lf.zip 

CATATAN: ini akan membuat file zip mempertahankan nama file asli tetapi mengubah akhir baris ke LF. Kemudian unzipakan mengekstrak file sebagai zip'ed, yaitu dengan nama asli mereka (tetapi dengan akhiran LF), sehingga mendorong untuk menimpa file asli lokal jika ada.

Kutipan yang relevan dari zip --help:

zip --help
...
-l   convert LF to CR LF (-ll CR LF to LF)
vmsnomad
sumber
Jawaban terbaik, menurut saya, karena dapat memproses seluruh direktori dan sub-direktori. Aku senang aku menggali sejauh itu.
caram
5

yang menarik di git-bash saya di windows sudah sed ""melakukan trik:

$ echo -e "abc\r" >tst.txt
$ file tst.txt
tst.txt: ASCII text, with CRLF line terminators
$ sed -i "" tst.txt
$ file tst.txt
tst.txt: ASCII text

Dugaan saya adalah bahwa sed mengabaikannya ketika membaca baris dari input dan selalu menulis akhiran baris unix pada output.

pengguna829755
sumber
4

Ini berhasil untuk saya

tr "\r" "\n" < sampledata.csv > sampledata2.csv 
Santosh
sumber
9
Ini akan mengkonversi setiap tunggal DOS-baris baru ke dalam dua UNIX-baris.
Melebius
2

Untuk Mac osx jika Anda telah menginstal homebrew [ http://brew.sh/[[1]

brew install dos2unix

for csv in *.csv; do dos2unix -c mac ${csv}; done;

Pastikan Anda telah membuat salinan file, karena perintah ini akan mengubah file yang ada. Opsi -c mac membuat switch menjadi kompatibel dengan osx.

Ashley Raiteri
sumber
Jawaban ini sebenarnya bukan pertanyaan pengirim asli.
hlin117
2
Pengguna OS X tidak boleh menggunakan -c mac, yang untuk mengubah pra-OS X CR-hanya baris baru. Anda ingin menggunakan mode itu hanya untuk file ke dan dari Mac OS 9 atau sebelumnya.
askewchan
2

TIMTOWTDI!

perl -pe 's/\r\n/\n/; s/([^\n])\z/$1\n/ if eof' PCfile.txt

Berdasarkan @GordonDavisson

Seseorang harus mempertimbangkan kemungkinan [noeol]...

lzc
sumber
2

Anda bisa menggunakan awk. Setel pemisah rekaman ( RS) ke regexp yang cocok dengan semua karakter baris baru, atau karakter. Dan mengatur pemisah catatan keluaran ( ORS) ke karakter baris baru unix-style.

awk 'BEGIN{RS="\r|\n|\r\n|\n\r";ORS="\n"}{print}' windows_or_macos.txt > unix.txt
Kazmer
sumber
Itulah yang bekerja untuk saya (MacOS, git diffshow ^ M, diedit dalam vim)
Dorian
2

Di Linux, mudah untuk mengonversi ^ M (ctrl-M) ke * nix newlines (^ J) dengan sed.

Ini akan seperti ini pada CLI, sebenarnya akan ada baris di teks. Namun, \ melewati itu untuk:

sed 's/^M/\
/g' < ffmpeg.log > new.log

Anda mendapatkan ini dengan menggunakan ^ V (ctrl-V), ^ M (ctrl-M) dan \ (backslash) saat Anda mengetik:

sed 's/^V^M/\^V^J/g' < ffmpeg.log > new.log
jet
sumber
2
sed --expression='s/\r\n/\n/g'

Karena pertanyaan menyebutkan sed, ini adalah cara paling lurus ke depan untuk menggunakan sed untuk mencapai ini. Apa yang dikatakan ekspresi adalah ganti semua carriage-return dan line-feed dengan hanya line-feed saja. Itulah yang Anda butuhkan saat Anda beralih dari Windows ke Unix. Saya memverifikasi itu berfungsi.

John Paul
sumber
Hai John Paul - jawaban ini ditandai untuk dihapus sehingga muncul dalam antrian peninjauan untuk saya. Secara umum, ketika Anda memiliki pertanyaan seperti ini yang berusia 8 tahun, dengan 22 jawaban, Anda akan ingin menjelaskan bagaimana jawaban Anda berguna dengan cara yang tidak ada jawaban lain yang ada.
zzxyz
0

Sebagai ekstensi untuk solusi Jonathan Unix to DOS dari Jonathan Leffler, untuk secara aman mengkonversi ke DOS ketika Anda tidak yakin dengan akhiran baris file saat ini:

sed '/^M$/! s/$/^M/'

Ini memeriksa bahwa saluran belum berakhir di CRLF sebelum mengkonversi ke CRLF.

Gannet
sumber
0

Saya membuat skrip berdasarkan jawaban yang diterima sehingga Anda dapat mengonversinya secara langsung tanpa memerlukan file tambahan pada akhirnya dan menghapus serta mengganti nama sesudahnya.

convert-crlf-to-lf() {
    file="$1"
    tr -d '\015' <"$file" >"$file"2
    rm -rf "$file"
    mv "$file"2 "$file"
}

pastikan saja jika Anda memiliki file seperti "file1.txt" yang "file1.txt2" belum ada atau akan ditimpa, saya menggunakan ini sebagai tempat sementara untuk menyimpan file.

OZZIE
sumber
0

Dengan bash 4.2 dan yang lebih baru, Anda dapat menggunakan sesuatu seperti ini untuk menghapus CR trailing, yang hanya menggunakan bash built-in:

if [[ "${str: -1}" == $'\r' ]]; then
    str="${str:: -1}"
fi
Glevand
sumber