[英]How can I delete a newline if it is the last character in a file?
我有一些文件,如果它是文件中的最后一个字符,我想删除最后一个换行符。 od -c
向我展示了我运行的命令确实在文件中写入了一个尾随的新行:
0013600 n t > \n
我已经用 sed 尝试了一些技巧,但我能想到的最好的方法是没有做到这一点:
sed -e '$s/\(.*\)\n$/\1/' abc
任何想法如何做到这一点?
perl -pe 'chomp if eof' filename >filename2
或者,就地编辑文件:
perl -pi -e 'chomp if eof' filename
[编者注: -pi -e
最初是-pie
,但是,正如一些评论者所指出的和@hvd 所解释的,后者不起作用。]
在我看到的 awk 网站上,这被描述为“perl 亵渎”。
但是,在测试中,它奏效了。
您可以利用shell 命令替换删除尾随换行符这一事实:
适用于 bash、ksh、zsh 的简单形式:
printf %s "$(< in.txt)" > out.txt
便携式(符合 POSIX 标准)替代方案(效率稍低):
printf %s "$(cat in.txt)" > out.txt
笔记:
in.txt
以多个换行符结尾,则命令替换将删除所有换行符。 谢谢, Sparhawk (它不会删除尾随换行符以外的空白字符。)printf %s
确保不会在输出中附加换行符(它是非标准echo -n
的符合 POSIX 标准的替代品;请参阅http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html和https: //unix.stackexchange.com/a/65819 )其他答案的指南:
如果Perl可用,请寻找公认的答案- 它简单且节省内存(不会一次读取整个输入文件)。
否则,请考虑ghostdog74 的Awk答案- 它晦涩难懂,但也节省了内存; 一个更易读的等价物(符合 POSIX 标准)是:
awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
打印延迟一行,以便可以在END
块中处理最后一行,由于将输出记录分隔符 ( OFS
) 设置为空字符串,因此打印时没有尾随\\n
。
如果您想要一个真正就地编辑的冗长但快速且强大的解决方案(而不是创建一个临时文件然后替换原始文件),请考虑jrockway 的Perl script 。
您可以使用来自 GNU coreutils 的head
来执行此操作,它支持与文件末尾相关的参数。 所以不要使用最后一个字节:
head -c -1
要测试结束换行符,您可以使用tail
和wc
。 以下示例将结果保存到临时文件并随后覆盖原始文件:
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
}
正如KarlWilbur在评论中指出并在Sorentar 的回答中使用的那样, truncate --size=-1
可以替换head -c-1
并支持就地编辑。
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile
编辑2:
这是一个awk
版本(已更正) ,它不会累积潜在的巨大数组:
awk '{if (line) 打印行; line=$0} END {printf $0}' abc
呆呆的
awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
一个快速的解决方案是使用 gnu 实用程序truncate
:
[ -z $(tail -c1 file) ] && truncate -s-1 file
如果文件有尾随换行符,则测试为真。
删除非常快,真正到位,不需要新文件,搜索也从末尾读取一个字节( tail -c1
)。
单行文件的一种非常简单的方法,需要来自 coreutils 的 GNU echo:
/bin/echo -n $(cat $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;
}
我们打开文件进行读取和追加; open for append 意味着我们已经seek
到文件的末尾。 然后我们使用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)
PS 本着“Perl 高尔夫”的精神,这是我最短的 Python 解决方案。 它将整个文件从标准输入拖入内存,去掉末尾的所有换行符,并将结果写入标准输出。 不像 Perl 那样简洁; 你就是无法在像这样一些棘手的快速东西上击败 Perl。
从对.rstrip()
的调用中删除“\\n”, .rstrip()
文件末尾的所有空白,包括多个空行。
将其放入“slurp_and_chomp.py”,然后运行python slurp_and_chomp.py < inputfile > outputfile
。
import sys
sys.stdout.write(sys.stdin.read().rstrip("\n"))
另一个 perl WTDI:
perl -i -p0777we's/\n\z//' filename
$ perl -e 'local $/; $_ = <>; s/\n$//; print' a-text-file.txt
另请参阅匹配 sed 中的任何字符(包括换行符) 。
perl -pi -e 's/\n$// if(eof)' your_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}'
它不适用于多个换行符...
*仅当最后一行是空行时才有效。
如果您需要使用管道/重定向而不是从文件读取/输出文件,这是一个很好的解决方案。 这适用于单行或多行。 无论是否有尾随换行符,它都有效。
# 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/$//
表示将“行尾”替换为“nothing”,这基本上是什么都不做。 但是它具有添加尾随换行符的副作用,因为它没有。 注意:Mac 的默认head
不支持-c
选项。 您可以执行brew install coreutils
并使用ghead
代替。
另一个答案 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
红宝石:
ruby -ne 'print $stdin.eof ? $_.strip : $_'
要么:
ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
POSIX SED:
$ - 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.
我有一个类似的问题,但是正在处理一个 Windows 文件并且需要保留那些 CRLF——我在 linux 上的解决方案:
sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked
我唯一想要这样做的是代码高尔夫,然后我只是将我的代码从文件中复制出来并将其粘贴到echo -n 'content'>file
语句中。
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile
应该删除文件中最后一次出现的 \\n。 无法处理大文件(由于 sed 缓冲区限制)
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
这是一个使用 sed 的简单解决方案。 您的 sed 版本需要支持-z
选项。
-z, --null-data
separate lines by NUL characters
它可以用于 pipe 或用于使用-i
选项编辑文件
sed -ze 's/\n$//' file
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.