关于linux:如何在Bash脚本中将DOS / Windows换行符(CRLF)转换为Unix换行符(LF)?

How to convert DOS/Windows newline (CRLF) to Unix newline (LF) in a Bash script?

如何以编程方式(即,不使用vi)将DOS / Windows换行符转换为Unix?

dos2unixunix2dos命令在某些系统上不可用。 如何使用sed / awk / tr等命令模拟这些?


您可以使用tr从DOS转换为Unix;但是,如果CR仅作为CRLF字节对的第一个字节出现在文件中,则只能安全地执行此操作。通常就是这种情况。然后你使用:

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

请注意,名称DOS-file与名称UNIX-file不同;如果您尝试两次使用相同的名称,则最终文件中不会包含任何数据。

你不能反过来做(使用标准'tr')。

如果你知道如何将回车输入脚本(control-Vcontrol-M进入control-M),那么:

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

其中'^ M'是控制-M字符。您还可以使用bash ANSI-C引用机制来指定回车:

1
2
3
4
sed $'s/
$//'
    # DOS to Unix
sed $'s/$/
/'
    # Unix to DOS

但是,如果您不得不经常这样做(不止一次,粗略地说),安装转换程序更为明智(例如dos2unixunix2dos,或者dtou和< x7>)并使用它们。


1
2
tr -d"
"
< file

看一下使用sed的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 \\
`
/"
           # command line under ksh
sed 's/$'"/`echo \\
`
/"
            # command line under bash
sed"s/$/`echo \\
`
/"
              # command line under zsh
sed 's/$/
/'
                       # gsed 3.02.80 or higher

使用sed -i进行就地转换,例如sed -i 's/..../' file


使用POSIX执行此操作非常棘手:

  • POSIX Sed不支持
    \15。即使它确实如此,到位
    选项-i不是POSIX

  • POSIX Awk支持
    \15,但是-i inplace选项
    不是POSIX

  • d2u和dos2unix不是POSIX实用程序,但ex是

  • POSIX ex不支持
    \15
    \12

要删除回车:

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

要添加回车:

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


使用AWK你可以做到:

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

使用Perl你可以做到:

1
2
perl -pe 's/
$//'
< dos.txt > unix.txt


这个问题可以通过标准工具解决,但是对于粗心大意有足够多的陷阱,我建议你安装flip命令,该命令是20多年前由zoo的作者Rahul Dhesi编写的。
它在转换文件格式方面表现非常出色,例如,避免了二进制文件的无意破坏,如果你只是在改变你看到的每个CRLF,那就太容易了...


您可以使用选项-c {command}以编程方式使用vim:

Dos到Unix:

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

Unix到dos:

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

"set ff = unix / dos"表示将文件的fileformat(ff)更改为Unix / DOS行格式

":wq"表示将文件写入磁盘并退出编辑器(允许在循环中使用该命令)


到目前为止发布的解决方案只处理部分问题,将DOS / Windows的CRLF转换为Unix的LF;他们缺少的部分是DOS使用CRLF作为行分隔符,而Unix使用LF作为行终止符。区别在于DOS文件(通常)在文件的最后一行之后没有任何内容,而Unix则会。要正确进行转换,您需要添加最终的LF(除非文件为零长度,即根本没有行)。我最喜欢的咒语(有一点点添加逻辑来处理Mac风格的CR分离文件,而不是已经采用unix格式的最小文件)有点perl:

1
2
3
4
5
6
perl -pe 'if ( s/

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

请注意,这会将文件的Unixified版本发送到stdout。如果要使用Unixified版本替换该文件,请添加perl的-i标志。


如果您无权访问dos2unix,但可以阅读此页面,那么您可以从此处复制/粘贴dos2unix.py。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/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 + '
'
)

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

从超级用户交叉发布。


要在适当的位置转换文件

1
dos2unix <filename>

要将转换后的文本输出到其他文件,请使用

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

你可以在Ubuntu上安装它

1
sudo apt install dos2unix

或者使用自制软件在macOS上

1
brew install dos2unix


使用PCRE轻松实现超级便携;

作为脚本,或用文件替换$@

1
2
3
4
5
#!/usr/bin/env bash
perl -pi -e 's/

/
/g'
-- $@

This will overwrite your files in place!

I recommend only doing this with a backup (version control or otherwise)


一个更简单的awk解决方案,没有一个程序:

1
2
3
awk -v ORS='

'
'1' unix.txt > dos.txt

技术上'1'是你的程序,b / c awk在给定选项时需要一个。

更新:
在很长一段时间内第一次重新访问这个页面后,我意识到还没有人发布内部解决方案,所以这里有一个:

1
2
3
4
5
while IFS= read -r line;
do printf '%s
'
"${line%$'
'}"
;
done < dos.txt > unix.txt


有趣的是,我在windows sed""上的git-bash已经完成了这个技巧:

1
2
3
4
5
6
7
$ echo -e"abc
"
>tst.txt
$ file tst.txt
tst.txt: ASCII text, with CRLF line terminators
$ sed -i"" tst.txt
$ file tst.txt
tst.txt: ASCII text

我的猜测是,当从输入读取行时,sed会忽略它们,并且总是在输出上写入unix行结尾。


这对我有用

1
2
3
tr"
"
"
"
< sampledata.csv > sampledata2.csv


只是想思考同样的问题(在Windows端,但同样适用于Linux。)
令人惊讶的是,没有人提到使用旧的zip -ll选项(Info-ZIP)对文本文件进行CRLF < - > LF转换的非常自动化的方式:

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

注意:这将创建一个zip文件,保留原始文件名,但将行结尾转换为LF。然后unzip将提取文件为zip'ed,即使用其原始名称(但使用LF结尾),从而提示覆盖本地原始文件(如果有)。

zip --help的相关摘录:

1
2
3
zip --help
...
-l   convert LF to CR LF (-ll CR LF to LF)


对于Mac osx,如果你已经安装了自制软件[http://brew.sh/][1]

1
2
3
brew install dos2unix

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

确保已创建文件的副本,因为此命令将修改文件。
-c mac选项使交换机与osx兼容。


在Linux上,使用sed将^ M(ctrl-M)转换为* nix换行符(^ J)很容易。

在CLI上它会是这样的,文本中实际上会有换行符。然而,将^ J传递给sed:

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

您可以在键入时使用^ V(ctrl-V),^ M(ctrl-M)和(反斜杠)来获得:

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


TIMTOWTDI!

1
2
3
4
5
6
perl -pe 's/

/
/; s/([^
])\z/$1
/ if eof'
PCfile.txt

基于@GordonDavisson

必须考虑[noeol]的可能性......


你可以使用awk。将记录分隔符(RS)设置为与所有可能的换行符或字符匹配的正则表达式。并将输出记录分隔符(ORS)设置为unix样式的换行符。

1
2
3
4
5
6
7
8
awk 'BEGIN{RS="
|
|

|

";ORS="
"}{print}'
windows_or_macos.txt > unix.txt


我根据接受的答案制作了一个脚本,这样你就可以直接转换它,而不需要最后一个额外的文件,然后删除和重命名。

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

只是确保你有一个像"file1.txt"这样的文件"file1.txt2"还没有存在或者它会被覆盖,我把它作为临时存放文件的地方。


作为Jonathan Leffler的Unix到DOS解决方案的扩展,当您不确定文件的当前行结尾时安全地转换为DOS:

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

这会在转换为CRLF之前检查该行是否已在CRLF中结束。


1
2
3
4
sed --expression='s/

/
/g'

由于问题提到了sed,这是使用sed实现这一目标的最直接的方法。表达式所说的是仅使用换行替换所有回车和换行。这是从Windows到Unix时所需要的。我确认它有效。


我试过了
sed's / ^ M $ //'file.txt
OSX以及其他几种方法(http://www.thingy-ma-jig.co.uk/blog/25-11-2010/fixing-dos-line-endings或http://hintsforums.macworld.com /archive/index.php/t-125.html)。没有工作,文件保持不变(btw Ctrl-v Enter需要重现^ M)。最后我使用了TextWrangler。它不是严格的命令行,但它的工作原理并没有抱怨。


有很多awk / sed / etc答案作为补充(因为这是此问题的最佳搜索结果之一):

你可能没有dos2unix,但你有iconv吗?

1
2
3
iconv -f UTF-16LE -t UTF-8 [filename.txt]
-f from format type
-t to format type

或者目录中的所有文件:

1
find . -name"*.sql" -exec iconv -f UTF-16LE -t UTF-8 {} -o ./{} \;

这将在当前文件夹中的所有.sql文件上运行相同的命令。 -o是输出目录,因此您可以将其替换为当前文件,或者出于安全/备份原因,输出到单独的目录。