在“while read -r”中将“\r”视为无内容

在“while read -r”中将“\r”视为无内容

我有这行代码,可以逐行读取文本文件。

文本文件有时由 Windows 用户生成,有时由 Unix 用户生成。因此,有时我看到\r\n行尾,有时我只看到\n

我希望我的脚本能够处理这两种情况并分别到达每一行,无论换行符是\r, or \n, or \r\n, or \n\r

while read -r textFileLines; do ... something ...; done < text_file.txt

这段代码作品\n\r每行末尾带有(LF CR),但确实不行当我已经排到\r\n最后的时候!

测试

  • 使用创建一个新的文本文件Notepad++ v7.5.4

    在此输入图像描述

  • while read -r LINE; do echo "$LINE"; done < /cygdrive/d/test_text.txt

  • 终端输出:

    first_line
    second_line
    third_string
    

为什么没有fourth_output显示该行?

答案1

如果您有一些文件是 DOS 文本文件,另一些文件是 Unix 文本文件,则脚本可以通过以下方式传递所有数据dos2unix

dos2unix <filename |
while IFS= read stuff; do
   # do things with "$stuff"
done 

Unix 文本文件不会因此被修改。

为了另外处理 Mac 换行问题,我相信你应该能够做到

dos2unix <filename | mac2unix |
while IFS= read stuff; do
   # do things with "$stuff"
done 

最后一行不是由read循环输出的,因为它没有终止,因此根本不是一行。

要检测文件的最后一行是否没有终止换行符,如果没有则添加一个,在bash

if [ "$( tail -c 1 filename )" != $'\n' ]; then
    printf '\n' >>filename
fi

有关的:

答案2

为什么没有fourth_output显示该行?

在您的图像中,文件在最后一行末尾缺少换行符。read仅当它读取分隔符(换行符)时才返回 true,并且由于最后一行的末尾不存在该分隔符,因此read返回 false,循环结束,并且不打印最后一个不完整的行。

这与回车无关,如果最后一行缺少 NL,即使只有 NL,行为也是相同的。

这里file1有两行以 CRLF 行结尾:

$ cat -A file1
foo^M$
bar^M$
$ while read x ; do echo "<$x>"; done < file1
>foo
>bar

file2缺少第二行结尾的行:

$ cat -A file2 ; echo
foo^M$
bar
$ while read x ; do echo "<$x>"; done < file2
>foo

如果您想让循环也处理最后一行片段,则必须在变量返回失败read时检查变量是否包含任何数据:read

$ while read -r x || [ "$x" ] ; do echo "<$x>"; done < file2
>foo
<bar>

如果您想删除 CR,您可以在循环中删除它,例如(在 Bash/ksh/zsh 中),或者使用or等x=${x%$'\r'};​​预处理文件。tr -d '\r'dos2unix

答案3

执行:

$ [ -n "$(tail -c1 infile)" ] && echo >> infile
$ sed 's/\r$\|^\r//g;s/\r/\n/g' infile | while IFS= read -r line
> do echo "$line" ; done
DOS       line
second     DOS
old  mac   line
new  mac   line
end\n\rreverse
linux      line
new linux  line

所有问题都解决了。


描述:

要纠正丢失的最后一个换行符,请使用:

[ -n "$(tail -c1 infile)" ] && echo >> infile

仅在需要时才会添加尾随换行符(不会更改正确的文件)。

然后,你可以转换

  • \r\n(DOS风格)到\n(只需删除行尾的\r)
  • \n\r(无效的 DOS 风格?)到一\n(删除行首的 \r)
  • 然后(对更正后的)将\r(旧的 MAC)转换为\n

只需一次调用 (GNU) sed 即可:

sed 's/\r$\|^\r//g;s/\r/\n/g' infile

如果文本文件像这样的测试文件:

$ cat infile
DOS       line
second     DOS
new  mac   line
end\n\rreverse
linux      line
new linux  line
no  end   line

$ cat -A infile
DOS       line^M$
second     DOS^M$
old  mac   line^Mnew  mac   line$
end\n\rreverse$
^Mlinux      line$
new linux  line$
no  end   line

$  od -An -tc infile
   D   O   S                               l   i   n   e  \r  \n
   s   e   c   o   n   d                       D   O   S  \r  \n
   o   l   d           m   a   c               l   i   n   e  \r
   n   e   w           m   a   c               l   i   n   e  \n
   e   n   d   \   n   \   r   r   e   v   e   r   s   e  \n  \r
   l   i   n   u   x                           l   i   n   e  \n
   n   e   w       l   i   n   u   x           l   i   n   e  \n
   n   o           e   n   d               l   i   n   e

答案4

有明确的工具可用于执行此操作。可用于\r\n从文件中删除的更常见的称为dos2unix.

如果这在您的系统上不可用,您可以使用以下命令之一对您的textFileLines变量执行类似的操作:

awk
$ echo "$textFileLines" | awk 1 RS='\r\n' ORS=
sed 1
$ echo "$textFileLines" | sed -e 's/\r//g'
sed 2
$ echo $textFileLines | sed $'s/\r//'
t
$ echo "$textFileLines" | tr -d '\r'

当然还有很多其他方法可以做到这一点,这些只是其中一些较常见的方法。

参考

相关内容