【问题标题】:How do I find files that do not end with a newline/linefeed?如何查找不以换行符/换行符结尾的文件?
【发布时间】:2011-01-07 23:01:19
【问题描述】:

如何列出不以换行符结尾的普通文本 (.txt) 文件名?

例如:列出(输出)这个文件名:

$ cat a.txt
asdfasdlsad4randomcharsf
asdfasdfaasdf43randomcharssdf
$ 

并且不要列出(输出)这个文件名:

$ cat b.txt
asdfasdlsad4randomcharsf
asdfasdfaasdf43randomcharssdf

$

【问题讨论】:

  • 您是否只是在寻找从文件夹向下显示的大量文件?上面的例子你的问题不是很清楚..
  • “普通txt”是什么意思?您是在谈论以 blank 行 (\n\n) 结尾的文件还是仅以换行符结尾的文件?您可以使用od -c filename 打印文件的明确表示。
  • 只是强调一下:换行blank行相同。换行符是单个字符 - 它界定了我们所看到的“行”。空行只是没有字符的“行”,通常是两个连续的换行符,中间没有任何内容,或者文件中以换行符开头的第一行。有些人也称仅由空格组成的行为“空白”行,并将术语“空行”保留为 2 个连续的换行符。你应该清楚自己想要什么。
  • 请注意,在您发布的示例中,第一个文件确实以换行符结尾,第二个文件以 two 换行符结尾。
  • 普通文本文件(根据 POSIX)总是以换行符结尾。还要考虑上面的两个 cmets

标签: language-agnostic


【解决方案1】:

如果您安装了 ripgrep:

rg -l '[^\n]\z'

该正则表达式匹配任何不是换行符的字符,然后是the end of the file

【讨论】:

    【解决方案2】:

    好,轮到我了,我试试看:

    find . -type f -print0 | xargs -0 -L1 bash -c 'test "$(tail -c 1 "$0")" && echo "No new line at end of $0"'
    

    【讨论】:

    • 编辑们,我不喜欢你在我的回答中添加的这个点。我在这里使用 GNU 查找。 2020 年不支持哪个 find 实现不提供路径?
    • BSD 查找,也就是 macOS 中的,需要指定路径。
    • 遗憾的是发行版落后:,-( 同时我敢打赌有些发行版根本没有find :,-(
    【解决方案3】:

    使用 pcregrep,这是 grep 的 Perl 兼容正则表达式版本,它支持使用 -M 标志的多行模式,如果最后一行有换行符,可用于匹配(或不匹配):

    pcregrep -LMr '\n\Z' .
    

    在上面的例子中,我们说在当前目录 (.) 中递归搜索 (-r) 列出不匹配的文件 (-L) 我们的多行 (-M) 正则表达式查找文件末尾的换行符 ('\n\Z')

    -L 更改为-l 将列出确实在其中包含换行符的文件。

    pcregrep 可以使用自制软件pcre 包安装在 MacOS 上:brew install pcre

    【讨论】:

    • 我应该指出,@dennis-williamson 给出的答案对于其中包含空格的文件也失败了。至少它对我有用。
    • 我在答案中添加了一组缺失的引号,应该可以解决这个问题。
    • 只是给未来读者的一个提示:这个 pcregrep 命令对于 not 包含空行的文件是正确的。反例:printf "a\n\nb" | pcregrep -M '\n$' - 将打印 a(因此与 -L 一起运行将不会打印任何内容)。
    • 使用\Z 而不是$(即pcregrep -LMr '\n\Z' .)来避免@maverickwoo 提到的问题。
    • 如果您需要为它们添加换行符:pcregrep -LMr '\n\Z' . | xargs sed -i -e '$a\'
    【解决方案4】:

    我能想到的最好的 oneliner 是:

    git grep --cached -Il '' | xargs -L1 bash -c 'if test "$(tail -c 1 "$0")"; then echo "No new line at end of $0"; exit 1; fi'
    

    这使用git grep,因为在我的用例中,我想确保提交到 git 分支的文件以换行符结尾。

    如果在 git repo 之外需要这样做,您当然可以使用 grep 代替。

    grep -RIl '' . | xargs -L1 bash -c 'if test "$(tail -c 1 "$0")"; then echo "No new line at end of $0"; exit 1; fi'
    

    为什么我使用 grep?因为使用-I 可以轻松过滤掉二进制文件。

    然后在其他答案中找到通常的 xargs/tail 东西,如果文件没有换行符,则添加 exit with 1。所以这可以在预提交 gitook 或 CI 中使用。

    【讨论】:

      【解决方案5】:

      试试这个:

      find . -type f -exec sh -c '[ -z "$(sed -n "\$p" "$1")" ]' _ {} \; -print
      

      它将打印以空行结尾的文件的文件名。要打印不以空行结尾的文件,请将-z 更改为-n

      【讨论】:

      【解决方案6】:

      这个例子

      • 适用于 macOS (BSD) 和 GNU/Linux
      • 使用标准工具:find、grep、sh、file、tail、od、tr
      • 支持带空格的路径

      Oneliner:

      find . -type f -exec sh -c 'file -b "{}" | grep -q text' \; -exec sh -c '[ "$(tail -c 1 "{}" | od -An -a | tr -d "[:space:]")" != "nl" ]' \; -print
      

      更易读的版本

      • 在当前目录下查找
        • 常规文件
        • “文件”(简要模式)考虑文本
        • 谁的最后一个字节(tail -c 1)没有由 od 的命名字符“nl”表示
        • 并打印他们的路径
      #!/bin/sh
      find . \
          -type f \
          -exec sh -c 'file -b "{}" | grep -q text' \; \
          -exec sh -c '[ "$(tail -c 1 "{}" | od -An -a | tr -d "[:space:]")" != "nl" ]' \; \
          -print
      

      最后,一个带有 -f 标志的版本来修复有问题的文件(需要 bash)。

      #!/bin/bash
      # Finds files without final newlines
      # Pass "-f" to also fix those files
      fix_flag="$([ "$1" == "-f" ] && echo -true || echo -false)"
      find . \
          -type f \
          -exec sh -c 'file -b "{}" | grep -q text' \; \
          -exec sh -c '[ "$(tail -c 1 "{}" | od -An -a | tr -d "[:space:]")" != "nl" ]' \; \
          -print \
          $fix_flag \
          -exec sh -c 'echo >> "{}"' \;
      

      【讨论】:

        【解决方案7】:

        如果您使用 'ack' (http://beyondgrep.com) 作为 grep 的替代方法,则只需运行以下命令:

        ack -v '\n$'
        

        它实际上搜索所有不匹配的行 (-v) 在行尾换行。

        【讨论】:

        • 简单的解决方案。添加“-l”以获取匹配的文件而不是行。
        【解决方案8】:

        此页面上的大多数解决方案都不适合我(FreeBSD 10.3 amd64)。伊恩·威尔 OSX 解决方案几乎总是有效,但很难遵循:- (

        有一个简单的解决方案几乎总是有效:(如果 $f 是文件):

        sed -i '' -e '$a\' "$f"

        sed 解决方案存在一个主要问题:它永远不会为您提供 有机会检查(而不是附加换行符)。

        对于 DOS 文件,上述两种解决方案均失败。我觉得最 便携式/可编写脚本的解决方案可能是最简单的解决方案, 我自己开发的:-)

        这是结合 file/unix2dos/tail 的基本 sh 脚本。在 生产,您可能需要在引号中使用“$f”并获取尾部输出 (嵌入到名为 last 的 shell 变量中)为 \"$f\"

        if file $f | grep 'ASCII text' > /dev/null; then
            if file $f | grep 'CRLF' > /dev/null; then
                type unix2dos > /dev/null || exit 1
                dos2unix $f
                last="`tail -c1 $f`"
                [ -n "$last" ] && echo >> $f
                unix2dos $f
            else
                last="`tail -c1 $f`"
                [ -n "$last" ] && echo >> $f
            fi
        fi
        

        希望这对某人有所帮助。

        【讨论】:

          【解决方案9】:

          由于您的问题有 perl 标签,我将发布一个使用它的答案:

          find . -type f -name '*.txt' -exec perl check.pl {} +
          

          其中 check.pl 如下:

          #!/bin/perl 
          
          use strict;
          use warnings;
          
          foreach (@ARGV) {
              open(FILE, $_);
          
              seek(FILE, -2, 2);
          
              my $c;
          
              read(FILE,$c,1);
              if ( $c ne "\n" ) {
                  print "$_\n";
              }
              close(FILE);
          }
          

          这个 perl 脚本每次打开一个,文件作为参数传递并且只读取倒数第二个字符;如果它不是换行符,它只是打印出文件名,否则它什么也不做。

          【讨论】:

          • 如果最后一个字符不是换行符(当然不是有效的文本文件)怎么办?
          【解决方案10】:

          另一种选择:

          $ find . -name "*.txt" -print0 | xargs -0I {} bash -c '[ -z "$(tail -n 1 {})" ] && echo {}'
          

          【讨论】:

          • 非常感谢,这是这个线程中唯一有效的示例(在 OSX 上)
          • ...其实这个好像没有找到合适的文件
          【解决方案11】:

          这应该可以解决问题:

          #!/bin/bash
          
          for file in `find $1 -type f -name "*.txt"`;
          do
                  nlines=`tail -n 1 $file | grep '^$' | wc -l`
                  if [ $nlines -eq 1 ]
                          then echo $file
                  fi
          done;
          

          这样称呼它:./script dir

          例如./script /home/user/Documents/ -> 列出/home/user/Documents 中以\n 结尾的所有文本文件。

          【讨论】:

          • 第一个改进是把IFS=$'\n'放在for前面。它允许处理带有空格的文件。第二个改进是将$nlines -eq 1 替换为$nlines -eq 0,因为作者需要“文件名,不以换行符结尾”。
          【解决方案12】:

          这很笨拙;肯定有人可以做得更好:

          for f in `find . -name '*.txt' -type f`; do
              if test `tail -c 1 "$f" | od -c | head -n 1 | tail -c 3` != \\n; then
                  echo $f;
              fi
          done
          

          注意这回答了标题中的问题,这与正文中的问题不同(我认为它正在寻找以 \n\n 结尾的文件)。

          【讨论】:

            【解决方案13】:

            这个例子在 OSX 上适用于我(上面的许多解决方案都没有)

            for file in `find . -name "*.java"`
            do
              result=`od -An -tc -j $(( $(ls -l $file  | awk '{print $5}') - 1 )) $file`
              last_char=`echo $result | sed 's/ *//'`
              if [ "$last_char" != "\n" ]
              then
                #echo "Last char is .$last_char."
                echo $file
              fi
            done
            

            【讨论】:

              【解决方案14】:

              这是另一个使用小 bash 内置命令的示例,其中:

              • 允许您过滤扩展名(例如,| grep '\.md$' 仅过滤 md 文件)
              • 管道更多grep命令用于扩展过滤器(如排除| grep -v '\.git'以排除.git下的文件
              • 使用 grep 参数的全部功能来获得更多过滤器或包含项

              代码基本上迭代 (for) 所有文件(匹配您选择的标准 grep),如果文件的最后 1 个字符 (-n "$(tail -c -1 "$file")") 不是空行,它将打印文件名 (echo "$file")。

              详细代码:

              for file in $(find . | grep '\.md$')
              do
                  if [ -n "$(tail -c -1 "$file")" ]
                  then
                      echo "$file"
                  fi
              done
              

              更紧凑一点:

              for file in $(find . | grep '\.md$')
              do
                  [ -n "$(tail -c -1 "$file")" ] && echo "$file"
              done
              

              当然还有它的 1-liner:

              for file in $(find . | grep '\.md$'); do [ -n "$(tail -c -1 "$file")" ] && echo "$file"; done
              

              【讨论】:

                猜你喜欢
                • 2017-10-02
                • 2010-09-07
                • 2010-11-03
                • 2011-03-21
                • 1970-01-01
                • 2015-10-26
                • 1970-01-01
                • 2017-03-01
                • 1970-01-01
                相关资源
                最近更新 更多