如果换行符是文件中的最后一个字符,我如何删除它?

我有一些文件,我想删除最后换行符,如果它是文件中的最后一个字符。od -c告诉我,我运行的命令确实用一行新的结尾来写文件:

0013600   n   t  >  \n

我已经尝试了一些诀窍与 sed,但我能想到的最好的是没有做到这一点:

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

有什么办法吗?

114880 次浏览
$  perl -e 'local $/; $_ = <>; s/\n$//; print' a-text-file.txt

参见 匹配 sed 中的任何字符(包括换行符)

perl -pe 'chomp if eof' filename >filename2

或者,在适当的位置编辑文件:

perl -pi -e 'chomp if eof' filename

[编者按: -pi -e最初是 -pie,但是,正如一些评论者所指出的,@hvd 解释说,后者不起作用。]

在我看到的 awk 网站上,这被描述为“对 perl 的亵渎”。

但是,在测试中,它起作用了。

head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

编辑2:

下面是 awk版本的 (更正),它不会积累一个潜在的巨大数组:

Awk’{ if (line) print line; line = $0} END { printf $0}’abc

发呆

awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file

还有一个 perl WTDI:

perl -i -p0777we's/\n\z//' filename

唯一一次我想这样做是为了代码高尔夫,然后我只是复制了我的代码文件,并粘贴到 echo -n 'content'>file语句。

如果你想做好,你需要这样的东西:

use autodie qw(open sysseek sysread truncate);


my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';


if($buf eq "\n"){
truncate $fh, $pos - 1;
}

我们打开文件进行读取和追加; 打开文件进行追加意味着我们已经 seeked 到文件的末尾。然后我们用 tell得到文件末尾的数值位置。我们用这个数字来找回一个字符,然后我们读出那个字符。如果是换行符,则将文件截断为该换行符之前的字符,否则不执行任何操作。

对于任何输入,它都在固定的时间和固定的空间中运行,并且不需要更多的磁盘空间。

这是一个很好的、整洁的 Python 解决方案。

这会就地修改文件,而不是制作文件的副本并从副本的最后一行删除换行符。如果文件很大,这将比选择 Perl 解决方案作为最佳答案快得多。

如果最后两个字节是 CR/LF,它将文件截断两个字节,如果最后一个字节是 LF,则截断一个字节。如果最后一个字节不是(CR) LF,则不会尝试修改文件。它处理错误。在 Python 2.6中测试。

将其放入名为“ striplast”和 chmod +x striplast的文件中。

#!/usr/bin/python


# strip newline from last line of a file




import sys


def trunc(filename, new_len):
try:
# open with mode "append" so we have permission to modify
# cannot open with mode "write" because that clobbers the file!
f = open(filename, "ab")
f.truncate(new_len)
f.close()
except IOError:
print "cannot write to file:", filename
sys.exit(2)


# get input argument
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
filename = "--help"  # wrong number of arguments so print help


if filename == "--help" or filename == "-h" or filename == "/?":
print "Usage: %s <filename>" % sys.argv[0]
print "Strips a newline off the last line of a file."
sys.exit(1)




try:
# must have mode "b" (binary) to allow f.seek() with negative offset
f = open(filename, "rb")
except IOError:
print "file does not exist:", filename
sys.exit(2)




SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file


end_pos = f.tell()


line = f.read()
f.close()


if line.endswith("\r\n"):
trunc(filename, end_pos)
elif line.endswith("\n"):
trunc(filename, end_pos + 1)

附言。本着“ Perl 高尔夫”的精神,下面是我的最短 Python 解决方案。它将整个文件从标准输入输出到内存中,去掉结尾的所有换行,并将结果写入标准输出。不像 Perl 那样简洁; 您只是无法在类似这样的小技巧性快速操作方面击败 Perl。

从对 .rstrip()的调用中删除“ n”,它将从文件末尾删除所有空白,包括多个空行。

将其放入“ slurp _ and _ chomp.py”中,然后运行 python slurp_and_chomp.py < inputfile > outputfile

import sys


sys.stdout.write(sys.stdin.read().rstrip("\n"))
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file

使用 dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
#printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1

假设 Unix 文件类型,并且您只希望最后的换行符可以正常工作。

sed -e '${/^$/d}'

它不会在多条新线路上工作..。

* < em > 只有在最后一行是空行时才有效。

另一个答案 FTR (也是我的最爱!): echo/cat 您希望通过反勾来剥离和捕获输出的内容。最后的换行将被删除。例如:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'


# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"


# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline

我也遇到过类似的问题,但当时正在处理一个 windows 文件,需要保留这些 CRLF ——我在 linux 上的解决方案:

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked

你可以利用 Shell < a href = “ http://pubs.opengroupp.org/onlinepubs/009695399/tools/xcu _ Chap02.html # tag _ 02 _ 06 _ 03”rel = “ noReferrer”> 命令替换 删除尾随的换行符:

在 bash、 ksh、 zsh 中使用的简单形式:

printf %s "$(< in.txt)" > out.txt

可移植(兼容 POSIX)替代品(效率稍低) :

printf %s "$(cat in.txt)" > out.txt

注:


返回文章页面 其他答案的指南:

  • 如果 Perl可用,那么使用 接受的答案-它是 简单且节省内存(不会一次读取整个输入文件)。

  • 否则,考虑 Ghostdog74的回答-它是 模糊不清,但也很节省内存; 更易读的等价物(兼容 POSIX)是:

  • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt

  • 打印被延迟了一行,这样最后一行可以在 END块中处理,由于将输出记录分隔符(OFS)设置为一个空字符串,所以打印时没有跟随 \n

  • 如果需要 详细,但是快速和健壮的解决方案,确实可以就地编辑(而不是创建一个临时文件,然后替换原始文件) ,可以考虑使用 Jrocway 的 < strong > Perl script

您可以使用来自 GNU coreutils 的 head来实现这一点,它支持相对于文件末尾的参数。因此,要停止使用最后一个字节:

head -c -1

要测试结束换行符,可以使用 tailwc。下面的示例将结果保存到一个临时文件,然后覆盖原始文件:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
head -c -1 file > file.tmp
mv file.tmp file
fi

你也可以使用 moreutils中的 sponge进行“就地”编辑:

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

你也可以通过在 .bashrc文件中填充这个函数来创建一个通用的可重用函数:

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
local file=$(mktemp)
cat > $file
if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
head -c -1 $file > $file.tmp
mv $file.tmp $file
fi
cat $file
}

更新

正如 卡尔 · 威尔伯在注释中指出的,并在 索伦塔的回答中使用,truncate --size=-1可以替代 head -c-1并支持就地编辑。

perl -pi -e 's/\n$// if(eof)' your_file
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

应删除文件中最后出现的 n。不能在大文件上工作(由于 sed 缓冲区限制)

红宝石:

ruby -ne 'print $stdin.eof ? $_.strip : $_'

或:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'

一个用于单行文件的非常简单的方法,需要来自 coreutils 的 GNU echo:

/bin/echo -n $(cat $file)

教育局局长:

“ ${/^ $/d }”

$ - match last line




{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.

一个快速的解决方案是使用 gnu 实用程序 truncate:

[ -z $(tail -c1 file) ] && truncate -s-1 file

如果文件后面有一行新行,那么测试将为 true。

删除非常快,真正的地方,没有新的文件是必要的,搜索也是从结束读取只有一个字节(tail -c1)。

如果您需要它来处理管道/重定向,而不是从文件读取/输出,那么这是一个很好的解决方案。这适用于单行或多行。不管是否有尾随的换行符,它都是有效的。

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1


# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1


# read from a file
sed '$s/$//' myfile.txt | head -c -1

详情:

  • 无论字符是什么,head -c -1都会截断字符串的最后一个字符。因此,如果字符串没有以换行结束,那么您将丢失一个字符。
  • 因此,为了解决这个问题,我们添加了另一个命令,如果没有的话,它将添加一个尾部换行符: sed '$s/$//'。第一个 $意味着只将命令应用到最后一行。s/$//的意思是用“无”代替“线的末端”,基本上就是什么也不做。但它有一个副作用,添加一个尾随换行是没有一个。

注意: Mac 默认的 head不支持 -c选项。你可以选择 brew install coreutils而使用 ghead

下面是一个使用 sed 的简单解决方案。 Sed 的版本需要支持 -z选项。

       -z, --null-data


separate lines by NUL characters

它可以在管道中使用,也可以使用 -i选项就地编辑文件

sed -ze 's/\n$//' file