如何删除bash中的尾随换行符?


10

我正在寻找行为类似于Perl的产品chomp。我正在寻找一个仅打印其输入的命令,如果它是换行符,则减去最后一个字符:

$ printf "one\ntwo\n" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done

(Bash和Zsh中的命令替换会删除所有尾随的新行,但是我正在寻找一种最多删除一个尾随的新行的东西。)

Answers:


9

这应该工作:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

该脚本始终打印前一行而不是当前行,并且最后一行的处理方式有所不同。

它的作用是什么:

  1. NR>1{print PREV} 打印前一行(第一次除外)。
  2. {PREV=$0}将当前行存储在PREV变量中。
  3. END{printf("%s",$0)} 最后,在不换行的情况下打印最后一行。

另请注意,这将最后删除最多一个空行(不支持删除"one\ntwo\n\n\n")。


15

您可以perl不使用chomp

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

但是为什么不使用chomp自己:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"

4

如果您想要一个精确的等效chomp方法,我想到的第一个方法是LatinSuD已经发布awk解决方案。我将添加一些其他方法,这些方法不会实现,chomp但会实现一些chomp经常用于的常见任务。

当您将一些文本填充到变量中时,末尾的所有换行符都会被删除。因此,所有这些命令都会产生相同的单行输出:

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

如果要在文件或命令输出的最后一行附加一些文本,sed可能会很方便。使用GNU sed和大多数其他现代实现,即使输入不以换行符结尾,此方法也可以使用¹;但是,如果还没有换行符,则不会添加换行符。

sed '$ s/$/ done/'

¹ 但是,这不适用于所有sed实现:sed是文本处理工具,不为空并且不以换行符结尾的文件也不是文本文件。


这并不完全等同于chomp,因为chomp最多只能删除一个尾随的换行符。
Flimm

@Flimm是的,最明显的等效项chomp是LatinSuD已经发布的awk解决方案。但是在许多情况下,chomp这只是完成工作的工具,我提供了完成一些常见任务的方法。让我更新我的答案以澄清这一点。
吉尔斯(Gillles)“所以-别再邪恶了”

1

另一种perl方法。这将整个输入读取到内存中,因此对于大量数据(使用cuonglm或该awk方法)可能不是一个好主意:

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done

谢谢,@StéphaneChazelas,已修复。由于某些原因,此开关始终使我感到困惑
terdon

0

我从某个地方的github仓库中抢了这个东西,但是找不到哪里

删除跟踪空白行

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'

0

抽象

打印不带换行符的行,仅在有另一行要打印时才添加换行符。

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

其他解决方案

如果我们正在处理文件,则可以截断文件中的一个字符(如果文件以换行符结尾):

removeTrailNewline(){[[$(tail -c 1“ $ 1”)]] || 截断-s-1“ $ 1”; }

这是一种快速的解决方案,因为它只需要从文件中读取一个字符,然后直接将其删除(truncate),而无需读取整个文件。

但是,在使用来自stdin(流)的数据时,必须读取所有数据。并且,读取后立即“消耗”它。没有回溯(与截断一样)。要找到流的末尾,我们需要阅读流的末尾。那时,无法返回输入流,因为数据已经“消耗”了。这意味着数据必须以某种形式的缓冲区存储,直到我们匹配流的末尾,然后对缓冲区中的数据进行处理。

解决方案中最明显的方法是将流转换为文件并处理该文件。但问题是需要某种流过滤器。与使用其他文件无关。

变量

天真的解决方案是将整个输入捕获到一个变量中:

FilterOne(){ filecontents=$(cat; echo "x");        # capture the whole input
             filecontents=${filecontents%x};       # Remove the "x" added above.
             nl=$'\n';                             # use a variable for newline.
             printf '%s' "${filecontents%"$nl"}";  # Remove newline (if it exists).
       }

printf 'one\ntwo'     | FilterOne ; echo 1done
printf 'one\ntwo\n'   | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done

记忆

可以使用sed将整个文件加载到内存中。在sed中,无法避免在最后一行尾随换行符。GNU sed可能会避免打印尾随换行符,但前提是源文件已经丢失了换行符。因此,不,简单的sed无法帮助。

GNU awk除外,带有以下-z选项:

sed -z 's/\(.*\)\n$/\1/'

使用awk(任何awk),可以对整个流进行处理,而printf不必在末尾添加换行符。

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

将整个文件加载到内存中可能不是一个好主意,因为它可能会消耗大量内存。

内存中有两行

在awk中,我们可以通过将前一行存储在变量中并打印出当前行来处理每个循环两行:

awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'

直接加工

但是我们可以做得更好。

如果我们在没有换行符的情况下打印当前行,并且仅在下一行存在时才打印换行符,则我们一次处理一行,最后一行将没有尾随换行符:

awk'NR == 1 {printf(“%s”,$ 0); next}; {printf(“ \ n%s”,$ 0)}'

或者,以其他方式编写:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

要么:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

所以:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
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.