如何测试文件是否使用CRLF或LF而无需对其进行修改?


48

我需要定期运行一个命令,以确保某些文本文件保持在Linux模式下。不幸的是,dos2unix总是修改文件,这会弄乱文件和文件夹的时间戳,并导致不必要的写入。

我编写的脚本是在Bash中编写的,因此我希望基于Bash给出答案。

Answers:


41

您可以dos2unix用作过滤器并将其输出与原始文件进行比较:

dos2unix < myfile.txt | cmp -s - myfile.txt

2
非常聪明和有用,因为它可以测试整个文件,而不仅仅是第一行或几行。
halloleo 2015年

2
也许你可以取代test通过myfile.txt在你的榜样,以避免混乱的两倍/usr/bin/test
彼得诺

1
注意,您将需要删除该-s标志以查看输出。从手册页: -s, --quiet, --silent suppress all normal output
tobalr,

24

如果目标只是为了避免影响时间戳,请dos2unix使用-k--keepdate选项将时间戳保持不变。它仍然需要进行写操作以制作临时文件并将其重命名,但是您的时间戳不会受到影响。

如果不接受文件的任何修改,则可以从此答案中使用以下解决方案。

find . -not -type d -exec file "{}" ";" | grep CRLF

1
您是说按字面意思将CRLF写为4个字符C,R,L和F吗?
bodacydo

7
您是否还意味着grep可以像这样接受CR和LF?
bodacydo

@bodacydo在他链接的答案中有解释,现在在Scott对BertS的答案的编辑中,这里是unix.stackexchange.com/a/79708/59699
dave_thompson_085

@ dave_thompson_085我看不到解释。它仅提及CRLF,但未解释其含义。
bodacydo

1
@bodacydo stackoverflow.com/questions/73833/... 说,find ... -exec file ... | grep CRLF与DOS行结束一个文件(即字节0D 0A)“将让你这样的:./1/dos1.txt: ASCII text, with CRLF line terminators 正如你可以看到这个包含实际的字符串CRLF,因此通过匹配grep寻找简单的字符串CRLF
dave_thompson_085,2015年

22

您可以尝试grep输入八进制的CRLF代码:

grep -U $'\015' myfile.txt

或十六进制:

grep -U $'\x0D' myfile.txt

当然,假设是这是一个文本文件。
mdpc

2
我喜欢这种grep用法,因为它使我可以轻松地使用目录列出所有此类文件,grep -lU $'\x0D' *并将输出传递给xargs
Melebius

搜索模式之前$的含义是什么?@don_crissti
fersarr

1
@fersarr-unix.stackexchange.com/ a
don_crissti


13

第一种方法(grep):

计算包含回车符的行:

[[ $(grep -c $'\r' myfile.txt) -gt 0 ]] && echo dos

计算以回车结尾的行:

[[ $(grep -c $'\r$' myfile.txt) -gt 0 ]] && echo dos

这些通常是等效的;在行的内部(即不在末尾)回车的情况很少。

更高效:

grep -q $'\r' myfile.txt && echo dos

这样更有效

  1. 因为它不需要将计数转换为ASCII字符串,然后将该字符串转换回整数,然后将其与零进行比较,并且
  2. 因为grep -c需要读取整个文件,以计算模式的所有出现次数,同时grep -q可以在看到模式的第一次出现时退出。

笔记:

  • 贯穿以上,您可能需要添加-U选项(即use -cU-qU),因为GNU会grep猜测该文件是否为文本文件。如果它认为文件是文本,则它会忽略行尾的回车符,以使$正则表达式“正确”工作,即使正则表达式为\r$!指定-U(或--binary)会否定此猜测,导致grep将文件视为二进制文件,并将数据原样传递给匹配机制,且CR末尾保持完整。
  • 不要这样做grep … $'\r\n' myfile.txt,因为grep将其\n视为模式定界符。就像grep -E 'foo|'查找包含foo或为空字符串的行一样, grep $'\r\n'查找包含\r或为空字符串的行,并且每行都匹配一个空字符串。

第二种方法(file):

[[ $(file myfile.txt) =~ CRLF ]] && echo dos

因为file报告如下:

myfile.txt: UTF-8 Unicode text, with CRLF line terminators

更安全的变体:

[[ $(file -b - < myfile.txt) =~ CRLF ]] && echo dos

哪里

  • file -b仅输出文件类型,而不输出文件名。没有这一点,一个文件,其名称包含字符CRLF 会触发误报。
  • file - < filename即使filename以开头也可以工作-。  请参阅Bash脚本:检查文件是否为文本文件

请注意,file 在非英语语言环境中检查来自的输出可能不起作用。


1
您可以"$(echo -e '\r')"用更简单的替换$'\r',尽管我个人会$'\r\n'减少误报的数量。
rici

@rici grep $'\r\n'似乎与我系统上的所有文件匹配...
2013年

@rici:好收获。我根据您的建议编辑了答案。— depquid:也许您在Windows上?:-) rici的技巧在这里起作用。
BertS

@depquid(和BertS):实际上,我认为正确的调用是grep -U $'\r$',以防止grep尝试猜测行尾。
rici

此外,-q如果找到匹配项,则可以使用它来设置返回码,而不是-c需要额外的检查。我个人喜欢您的第二个解决方案,尽管它高度依赖于异想天开,file并且可能不适用于非英语语言环境。
rici

11

采用 cat -A

$ cat file
hello
hello

现在,如果此文件是在* NIX系统中制作的,它将显示

$ cat -A file
hello$
hello$

但是,如果此文件是在Windows中制作的,它将显示

$ cat -A file
hello^M$
hello

^M代表CR$代表LF。请注意,Windows没有使用以下命令保存最后一行CRLF

这也不会更改文件内容。


最好,最简单的解决方案!需要更多的投票。
user648026

1
+1迄今为止最好的答案。没有依赖关系,没有复杂的bash脚本。只是-A为了猫。cat -A file | less如果文件太大,可以使用一个技巧。我敢肯定,必须检查文件结尾以查找特别长的文件并不少见。(按一下q即可离开)
Nicholas Pipitone

4

一个bash功能为您服务:

# return 0 (true) if first line ends in CR
isDosFile() {
    [[ $(head -1 "$1") == *$'\r' ]]  
}

然后你可以做类似的事情

streamFile () {
    if isDosFile /tmp/foo.txt; then
        sed 's/\r$//' "$1"
    else
        cat "$1"
    fi
}

streamFile /tmp/foo.txt | process_lines_without_CR

3
您无需isDosFile()在示例中使用streamFile() { sed 's/\r$//' "$1" ; }

1
我认为这是最优雅的解决方案。它不读取整个文件,仅读取第一行。
亚当·里奇科夫斯基

4

如果文件具有DOS / Windows风格的CR-LF行尾,则使用基于Unix的工具查看文件时,您会在每行末尾看到CR('\ r')字符。

该命令:

grep -l '^M$' filename

将打印filename,如果该文件包含一个或多个行与Windows风格的行结束符,并且如果它不将打印什么。除了^M必须是原义的回车字符外,通常在终端中输入Ctrl+,V然后输入Enter (或Ctrl+ V,然后再Ctrl+ M)。bash shell使您可以将文字回车符写为$'\r'在此处记录),因此您可以编写:

grep -l $'\r$' filename

其他外壳可以提供类似的功能。

您可以改用其他工具:

awk '/\r$/ { exit(1) }' filename

如果文件包含任何Windows样式的行尾,它将以1(设置$?1)状态退出,如果文件不包含Windows样式的行尾,则退出状态0,使其在shell if语句中很有用(请注意缺少[方括号]):

if awk '/\r$/ { exit(1) }' filename ; then
    echo filename has Unix-style line endings
else
    echo filename has at least one Windows-style line ending
fi

文件可以包含Unix样式和Windows样式的行尾混合。我在这里假设您要检测具有任何 Windows样式的行尾的文件。


1
您可以通过键入$'\r',在命令行中使用bash(和其他一些shell)对回车编码,如对此问题的其他答案所述。
斯科特,

2

用途file

$ file README.md
README.md: ASCII text, with CRLF line terminators

$ dos2unix README.md
dos2unix: converting file README.md to Unix format...

$ file README.md
README.md: ASCII text

在前面的两个答案中已经更彻底地讨论了这个想法。
G-Man说'恢复莫妮卡'

1

我一直在用

cat -v filename.txt | diff - filename.txt

这似乎有效。我发现输出比

dos2unix < filename.txt | diff - filename.txt

如果dos2unix由于某种原因无法安装,它也很有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.