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

Posted

技术标签:

【中文标题】如何在 Bash 脚本中将 DOS/Windows 换行符 (CRLF) 转换为 Unix 换行符 (LF)【英文标题】:How to convert DOS/Windows newline (CRLF) to Unix newline (LF) in a Bash script 【发布时间】:2011-02-06 12:19:48 【问题描述】:

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

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

【问题讨论】:

一般来说,只需使用你的包管理器安装dos2unix,它确实要简单得多,并且在大多数平台上都存在。 同意! @BradKoch 在 Mac OSX 上就像“brew install dos2unix”一样简单 并非所有用户都有root权限,因此无法安装软件包。也许这就是用户提出他提出的非常具体的问题的原因。 【参考方案1】:

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

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

注意名称DOS-file与名称UNIX-file不同;如果您尝试使用相同的名称两次,则最终文件中将没有数据。

你不能反其道而行之(使用标准的 'tr')。

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

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

'^M' 是 control-M 字符。也可以使用bashANSI-C Quoting机制指定回车:

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

但是,如果您必须经常这样做(粗略地说不止一次),安装转换程序(例如 dos2unixunix2dos,或者可能是 @987654324 @ 和 utod) 并使用它们。

如果需要处理整个目录和子目录,可以使用zip

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

这将创建一个 zip 存档,其中行尾从 CRLF 更改为 CR。 unzip 然后将转换后的文件放回原处(并逐个文件询问您-您可以回答:全部是)。感谢@vmsnomad 指出这一点。

【讨论】:

使用tr -d '\015' &lt;DOS-file &gt;UNIX-file where DOS-file == UNIX-file 只会产生一个空文件。不幸的是,输出文件必须是不同的文件。 @ButtleButkus:嗯,是的;这就是为什么我使用了两个不同的名字。如果您在程序读取所有输入文件之前将其删除,就像您两次使用相同名称时所做的那样,您最终会得到一个空文件。这是类 Unix 系统上的统一行为。它需要特殊的代码来安全地处理覆盖输入文件。按照说明进行操作,您会没事的。 有地方;你必须知道在哪里可以找到它们。在限制范围内,GNU sed 选项 -i(用于就地)有效;限制是链接文件和符号链接。 sort 命令具有“总是”(从 1979 年开始,如果不是更早)支持可以列出输入文件之一的 -o 选项。然而,这部分是因为sort 必须先读取其所有输入,然后才能写入其任何输出。其他程序偶尔支持覆盖其输入文件之一。您可以在 Kernighan & Pike 的 'The UNIX Programming Environment' 中找到一个通用程序(脚本)来避免问题。 第三个选项对我有用,谢谢。我确实使用了 -i 选项:sed -i $'s/\r$//' filename - 就地编辑。我在一台无法访问互联网的机器上工作,所以软件安装是个问题。 @JonathanLeffler 通用程序名为sponge,可以在moreutils:tr -d '\015' &lt; original_file | sponge original_file中找到。我每天都用它。【参考方案2】:

用途:

tr -d "\r" < file

查看here 使用sed 的示例:

# In a 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 a 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

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

【讨论】:

我使用了一个变体,因为我的文件只有 \r : tr "\r" "\n" &lt; infile &gt; outfile @MattTodd 你能把这个作为答案发布吗? -d 的出现频率更高,在“仅\r”的情况下无济于事。 请注意,建议的\r\n 的映射具有文件双倍行距的效果;以 DOS 结尾的每个 CRLF 行在 Unix 中都变为 \n\n 我可以递归执行此操作吗?【参考方案3】:

您可以通过选项-c command 以编程方式使用 Vim:

DOS 到 Unix:

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

Unix 到 DOS:

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

“set ff=unix/dos”表示将文件的文件格式(ff)改为Unix/DOS行尾格式。

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

【讨论】:

这似乎是最优雅的解决方案,但不幸的是缺乏对 wq 含义的解释。 任何使用vi 的人都会知道:wq 的含义。对于那些不是 3 个字符的意思 1) 打开 vi 命令区域,2) 写入和 3) 退出。 我不知道您可以从 CLI 以交互方式向 vim 添加命令 你可以用“:x”代替“:wq” @DavidNewcomb : 使用 vim 时,键入 column 开始给出命令 w 表示写入文件 q 表示退出 而 vi 允许您将它们并排放置。 【参考方案4】:

安装dos2unix,然后就地转换文件

dos2unix <filename>

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

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

你可以在 Ubuntu 或 Debian 上安装它

sudo apt install dos2unix

或在 macOS 上使用 Homebrew

brew install dos2unix

【讨论】:

我知道这个问题要求寻找 dos2unix 的替代品,但这是谷歌的第一个结果。【参考方案5】:

使用 AWK 你可以做到:

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

使用 Perl 你可以做到:

perl -pe 's/\r$//' < dos.txt > unix.txt

【讨论】:

一个不错的便携 awk 解决方案。【参考方案6】:

这个问题可以用标准工具解决,但是对于粗心的人来说,陷阱太多了,我建议您安装flip 命令,该命令由zoo 的作者Rahul Dhesi 在20 多年前编写。 它在转换文件格式方面做得非常出色,例如,避免意外破坏二进制文件,如果您只是竞相更改您看到的每个 CRLF,这有点太容易了......

【讨论】:

有什么方法可以在不修改原始文件的情况下以流媒体方式执行此操作? @augurar 你可以查看“类似包”packages.debian.org/wheezy/flip 我有过使用错误标志运行 texxto 来破坏一半操作系统的经历。如果您想对整个文件夹执行此操作,请特别小心。 链接似乎已损坏(超时 - "504 Gateway Time-out")。【参考方案7】:

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

#!/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))

(交叉发布from Super User。)

【讨论】:

这种用法具有误导性。真正的dos2unix 默认转换all 输入文件。您的用法暗示 -n 参数。而真正的dos2unix 是一个过滤器,它从标准输入读取,如果文件没有给出,则写入标准输出。 另外,这在某些平台上不起作用,因为没有 python - 他们显然不会被向后兼容性所困扰,所以它是 python2python3 或 。 ..【参考方案8】:

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

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

请注意,这会将文件的 Unix 化版本发送到标准输出。如果您想用 Unix 化版本替换文件,请添加 perl 的 -i 标志。

【讨论】:

@LudovicZenohateLagouardette 是纯文本文件(即 csv 或制表符分隔的文本)还是其他文件?如果它是某种类似于数据库的格式,则将其当作文本进行操作很可能会破坏其内部结构。 纯文本 csv,但我认为编码很奇怪。我认为它因此而搞砸了。不过不用担心。我一直在收集备份,这甚至不是真正的数据集,只是一个 1gb 的数据集。实际是 26GB。【参考方案9】:

PCRE 超级简单;

作为脚本,或将$@ 替换为您的文件。

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

这将覆盖您的文件!

我建议仅使用备份(版本控制或其他方式)进行此操作

【讨论】:

谢谢!这可行,尽管我正在写文件名并且没有--。我选择了这个解决方案,因为它很容易理解和适应我。仅供参考,这就是开关的作用:-p 假设一个“while 输入”循环,-i 就地编辑输入文件,-e 执行以下命令 严格来说,PCRE 是 Perl 的正则表达式引擎的重新实现,而不是 Perl 的正则表达式引擎。尽管名称有暗示,但它们都具有这种能力,尽管也存在差异。【参考方案10】:

没有程序的更简单的AWK 解决方案:

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

从技术上讲,“1”是您的程序,因为 AWK 在给定选项时需要一个。

或者,内部解决方案是:

while IFS= read -r line;
do printf '%s\n' "$line%$'\r'";
done < dos.txt > unix.txt

【讨论】:

这很方便,但要明确一点:这会翻译 Unix -> Windows/DOS,这是 OP 要求的相反方向 这是特意做的,留给作者做练习。 翻白眼 awk -v RS='\r\n' '1' dos.txt &gt; unix.txt 太棒了(感谢你的教学技巧)。 "b/c awk 在给定选项时需要一个。" - awk 总是 需要一个程序,无论是否指定选项。 纯 bash 解决方案很有趣,但比等效的 awksed 解决方案慢得多。此外,您必须使用while IFS= read -r line 忠实地保留输入行,否则会修剪前导和尾随空格(或者,在read 命令中不使用变量名并使用$REPLY)。【参考方案11】:

有趣的是,在我在 Windows 上的 Git Bash 中,sed "" 已经成功了:

$ 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

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

【讨论】:

在像 GNU/Linux 这样的 LF 类型系统上,sed "" 并不能解决问题。【参考方案12】:

我只需要思考同样的问题(在 Windows 端,但同样适用于 Linux)。

令人惊讶的是,没有人提到使用旧的 zip -ll 选项 (Info-ZIP) 对文本文件进行 CRLF LF 转换的非常自动化的方法:

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

注意:这将创建一个 ZIP 文件,保留原始文件名,但将行尾转换为 LF。然后unzip 会将文件解压缩为压缩文件,即使用它们的原始名称(但带有 L​​F 结尾),从而提示覆盖本地原始文件(如果有)。

来自zip --help的相关摘录:

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

【讨论】:

最佳答案,据我说,因为它可以处理整个目录和子目录。我很高兴我挖得那么深。【参考方案13】:
sed -i.bak --expression='s/\r\n/\n/g' <file_path>

由于问题提到了 sed,这是使用 sed 实现此目的最直接的方法。该表达式表示仅用换行符替换所有回车符和换行符。当您从 Windows 转到 Unix 时,这就是您所需要的。我验证它有效。

【讨论】:

嘿 John Paul——这个答案被标记为删除,所以出现在我的审核队列中。一般而言,当您遇到这样一个 8 年前有 22 个答案的问题时,您会想以其他现有答案没有的方式解释您的答案如何有用。 --in-place mydosfile.txt 添加到末尾(或通过管道传输到文件)时,我无法使其正常工作。最终结果是文件仍然有 CRLF。我在 Graviton (AArch64) EC2 实例上进行测试。 @NeilC.Obremski 我更新了完整的命令行,请尝试一下。它还会在更改之前进行备份。 sed 's/\r\n/\n/g' 不匹配任何内容。参考can-sed-replace-new-line-characters 它对我有用。【参考方案14】:

这对我有用

tr "\r" "\n" < sampledata.csv > sampledata2.csv 

【讨论】:

这会将每个 single DOS-newline 转换为 two UNIX-newlines。【参考方案15】:

对于 Mac OS X,如果您安装了 Homebrew (http://brew.sh/):

brew install dos2unix

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

确保您已经制作了文件的副本,因为此命令将修改文件。 -c mac 选项使开关与 OS X 兼容。

【讨论】:

这个答案真的不是原发帖人的问题。 OS X 用户不应使用 -c mac,它用于转换 pre-OS X CR-only 换行符。您只想将该模式用于 Mac OS 9 或更早版本的文件。【参考方案16】:

TIMTOWTDI!

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

基于Gordon Davisson's answer。

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

【讨论】:

【参考方案17】:

您可以使用AWK。将记录分隔符 (RS) 设置为匹配所有可能的换行符或字符的正则表达式。并将输出记录分隔符 (ORS) 设置为 Unix 风格的换行符。

awk 'BEGINRS="\r|\n|\r\n|\n\r";ORS="\n"print' windows_or_macos.txt > unix.txt

【讨论】:

这对我有用(MacOS,git diff 显示 ^M,在 vim 中编辑) 在转换 DOS 文件时,您的命令在每行之间添加了一个额外的空白行。这样做awk 'BEGINRS="\r\n";ORS=""print' dosfile &gt; unixfile 解决了这个问题,但它仍然不能解决最后一行缺少的 EOL。【参考方案18】:

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

在 CLI 上会出现这样的情况,文本中实际上会有一个换行符。但是,\^J 传递给 sed:

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

你可以使用 ^V (Ctrl + V), ^M (Ctrl + M ) 和 \(反斜杠)在您键入时:

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

【讨论】:

【参考方案19】:

作为Jonathan Leffler's Unix to DOS solution 的扩展,在您不确定文件的当前行结尾时安全地转换为 DOS:

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

这会在转换为 CRLF 之前检查该行是否尚未以 CRLF 结尾。

【讨论】:

【参考方案20】:

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

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

只要确定您是否有像“file1.txt”这样的文件“file1.txt2”不存在,否则它将被覆盖。我将其用作存储文件的临时位置。

【讨论】:

【参考方案21】:

在 Bash 4.2 和更新版本中,您可以使用类似这样的东西来去除尾随 CR,它只使用 Bash 内置函数:

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

【讨论】:

【参考方案22】:

我试过了

sed 's/^M$//' file.txt

关于OS X 以及其他几种方法(Fixing Dos Line Endings 或http://hintsforums.macworld.com/archive/index.php/t-125.html)。没有工作,文件保持不变(顺便说一下,Ctrl + V, Enter 需要重现^M)。最后我使用了TextWrangler。它不是严格意义上的命令行,但它可以工作并且不会抱怨。

【讨论】:

hintsforums.macworld.com 链接(实际上)已损坏 - 它重定向到主页“hints.macworld.com”

以上是关于如何在 Bash 脚本中将 DOS/Windows 换行符 (CRLF) 转换为 Unix 换行符 (LF)的主要内容,如果未能解决你的问题,请参考以下文章

sh 如何在shell脚本/ bash中将输出附加到文本文件的末尾?

在 Bash 脚本中将字符串作为命令运行

如何在Python中将变量传递给bash命令

在 Bash 中将文本文件作为命令运行

如何在 Bash 中将时间戳转换为日期?

在 bash 脚本中将 SSH 输出捕获为变量