【问题标题】:How to fix “No newline at end of file” warning for lots of files?如何修复大量文件的“文件末尾没有换行符”警告?
【发布时间】:2011-03-16 18:30:25
【问题描述】:

我有大量的源文件,最后都缺少换行符。

我如何自动在它们的末尾添加一个换行符?

有些可能已经有了换行符,所以只应在必要时添加。

我本身可能不是在寻找代码,而只是我可以在终端中运行以添加必要的换行符(或某种编程或开发工具)的东西。

【问题讨论】:

    标签: shell scripting newline


    【解决方案1】:

    为方便起见,将 Norman 的答案转换为拆分单行。

    for i in * ; do  echo $i; \
     if diff /dev/null "$i" | tail -1 | \
      grep '^\\ No newline' > /dev/null; then echo >> "$i"; \
     fi; done
    

    用你想要的任何文件模式替换*,例如*.c

    另一个只是告诉您哪些文件已损坏:

    for i in * ; do \
     if diff /dev/null "$i" | tail -1 | \
      grep '^\\ No newline' > /dev/null; then  echo $i; \
     fi; done
    

    【讨论】:

    • 如果你想让它递归,你可以把*换成$(find . -type f)或者$(find <dirname> -type f -name <filepattern>)
    【解决方案2】:

    如果您可以使用 Unix 工具,则可以运行 diff 来找出哪些文件缺少换行符,然后追加:

    #!/bin/sh
    for i
    do
      if diff /dev/null "$i" | tail -1 | grep '^\\ No newline' > /dev/null
      then 
        echo >> "$i"
      fi
    done
    

    我依靠diff 生成第一列中带有\ 的消息,tail 给我diff 输出的最后一行,grep 告诉我是否最后一行是我正在寻找的消息。如果一切正常,那么echo 会生成一个换行符,>> 会将其附加到文件"$i""$i" 周围的引号确保如果文件名中有空格,一切仍然有效。

    【讨论】:

    • 不错,但 grep 返回本地化消息,如“\Brak znaku nowej linii (etc.)”。此外,差异输出整个文件。我会使用tail -1 $f | grep '\n' 作为条件(适用于我的盒子)。
    • @TomaszGandor : 'tail -1 文件名 | grep '\n' 似乎总是在我的 Mac 上返回错误结果,无论是否有尾随换行符..
    【解决方案3】:

    对文件末尾“缺少”换行符的文件的简单修复是简单的 sed;以下修复了文件“就地”(使用“-i”选项):

    find . -type f -exec sed -i -e '$a\' {} \; -print 
    

    解释:找到所有文件(-type f),运行sed,就地更改文件(-i),给定以下(-e)脚本/表达式,匹配文件末尾($),并执行“追加”操作 (a\),但实际上并没有指定要追加的任何文本(\ 之后没有任何内容),这将在文件末尾添加一个换行符,但前提是它丢失了。打印找到的所有文件(修复或未修复),这可能是不必要的。

    主要警告是sed 的功能因平台而异,因此-i-e 可能支持也可能不支持/相同;例如较旧的 Unix 或 MacOS 的古怪可能需要稍微不同的语法。

    【讨论】:

    • 警告: 不要在 Git 存储库的根目录中不要执行此操作而不添加 -prune 或 @ 987654333@ 到find 命令省略.git/ 目录。否则,你会破坏它。
    • 没错,一般来说,不要在任何二进制文件上运行sed。由于.git 是(通常)仅在根目录中找到的点文件,一个简单的解决方案是将find . -type f... 更改为find * -type f ...。但无论如何,如果有任何二进制文件的机会,要么根据需要定制find,要么根本不使用find
    【解决方案4】:

    好的,在cmets中抱怨之后,有我更好的解决方案。 首先,您想知道,哪些文件缺少换行符:

    find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -v 0a$" ';' -print
    

    不是超级快(为每个文件调用几个进程),但实际使用还可以。

    现在,当你拥有它时,你也可以添加换行符,再加上另一个-exec

    find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -v 0a$" ';' -exec sh -c "echo >> {}" ';'
    

    可能的陷阱:

    • 如果文件名错误,例如他们有空格,你可能需要tail -1 \"{}\"。 还是 find 做对了?

    • 您可能想要添加更多过滤来查找,例如-name \*py 等。

    • 在使用前考虑可能的 DOS/Unix 换行符混乱(先解决这个问题)。

    编辑:

    如果您不喜欢这些命令的输出(回显一些十六进制),请将 -q 添加到 grep:

    find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -q -v 0a$" ';' -print
    find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -q -v 0a$" ';' -exec sh -c "echo >> {}" ';'
    

    【讨论】:

    • 这是巨大矫枉过正。
    【解决方案5】:

    尝试前路:

    ex -s +"bufdo wq" *.c
    

    并递归(启用a new globbing option):

    ex -s +"bufdo wq" **/*.c
    

    这相当于vi -es。将*.c 更改为您感兴趣的扩展名。

    如果ex/vi 不存在,则会在保存时自动附加换行符。

    【讨论】:

      【解决方案6】:

      我很惊讶没有人提到像 Awk 这样的许多简单的文本处理工具会添加换行符作为副作用。这是一个简单的循环,只有在实际添加了换行符时才会覆盖文件。

      for f in *; do
          awk 1 "$f" >tmp
          cmp -s tmp "$f" || mv tmp "$f"
      done
      rm -f tmp
      

      (这个临时文件显然有点小问题。)

      IDEone 演示:http://ideone.com/HpRHcx

      【讨论】:

        【解决方案7】:

        find -type f | while read f; do [[ `tail -c1 "$f"` ]] && echo >> "$f"; done

        我使用find 而不是for f in *,因为它是递归的,问题是关于“大量源文件”。

        出于性能原因,我使用while read 而不是find -execxargs,它每次都节省了生成shell 进程。

        我正在利用反引号运算符正在返回“删除任何尾随换行符”man bash 的命令输出这一事实,因此对于正确终止的文件,反引号将为空并且将跳过回显。

        find | read 对包含换行符的文件名将失败,但如果需要,很容易修复:

        find -type f -print0 | while read -d $'\0' f; do [[ `tail -c1 "$f"` ]] && echo >> "$f"; done

        【讨论】:

          【解决方案8】:

          以下是我的 bash 脚本解决方案。它首先检查文件是否为文本文件。然后,如果它是一个文本文件,它使用 tail 和 od(八进制转储)来查看最后一个字符是否是换行符。如果不是,则使用 echo 追加一个换行符:

          item="$1"
          
          if file "$item" | egrep '\btext\b' > /dev/null
          then
              if ! tail -c 1 "$item" | od -b -A n | egrep '\b012\b' > /dev/null
              then
                  echo "(appending final newline to ${item})"
                  echo >> "$item"
              fi
          fi
          

          【讨论】:

            【解决方案9】:

            由于命令本地化,Tim 和 Norman 的回答应使用“LANG=C”前缀进行改进,以便有机会与具有任何区域参数的每个系统匹配“无换行符”模式

            这确保了该脚本命令行中每个文件的结尾都是空行:

             #!/bin/sh -f
             for i in $* ; do  echo $i; \
             if LANG=C diff /dev/null "$i" | tail -1 | \
              grep '^\\ No newline' > /dev/null; then echo >> "$i"; \
             fi; done
            

            这个脚本会检测到缺少它的文件:

             #!/bin/sh -f
             for i in $* ; do \
             if LANG=C diff /dev/null "$i" | tail -1 | \
              grep '^\\ No newline' > /dev/null; then  echo $i; \
             fi; done
            

            【讨论】:

              【解决方案10】:

              找到工具后,没有运气就完成这项工作。我决定自己写

              这是我的 python 脚本来完成这项工作

              它只在文件末尾附加(\r\n)而不包含(\n)

              https://github.com/tranhuanltv/append_newline

              用法:append_newline.py .c ./projects ./result_dir

              如果你愿意,可以提出拉取请求

              【讨论】:

              • 这是非常有问题的 - 从 END 到 -1 的搜索是可以的,但是您可以通过这种方式轻松混合 Unix 和 DOS 换行符...
              【解决方案11】:
              pcregrep --recursive --exclude-dir=.git \
                --files-without-match --multiline '\n\z' . |
                while read k ; do echo >> "$k"; done
              

              这里涉及几个步骤:

              1. 递归查找文件
              2. 检测哪些文件缺少尾随新行
              3. 遍历每个文件
              4. 添加换行符

              步骤 1 传统上使用 find 完成(遵循 Unix 的传统 “每个工具都做一件事并且做得很好”),但是由于 pcregrep 具有内置支持,所以我很乐意使用它。我小心避免弄乱 .git 文件夹。

              步骤 2 使用多行正则表达式匹配 有最后一个换行符的文件,并打印 匹配的文件的名称。

              第 3 步是使用 while/read 循环而不是 for/in 完成的,因为后者对于带有空格的文件名和极长的文件列表会失败。

              第 4 步是一个简单的回声,遵循@norman-ramsey 的方法。

              h/t @anthony-bush https://stackoverflow.com/a/20687956/577438 用于 pcregrep 建议。

              【讨论】:

                猜你喜欢
                • 2010-09-09
                • 1970-01-01
                • 2012-10-24
                • 2011-12-31
                • 2011-10-12
                • 1970-01-01
                • 2020-03-06
                • 2014-09-29
                • 1970-01-01
                相关资源
                最近更新 更多