我有一些文件要删除最后一个换行符(如果它是文件中的最后一个字符)。 od -c
告诉我,我运行的命令确实以尾随新行写入文件:
0013600 n t > \n
我用sed尝试了一些技巧,但我能想到的最好的方法不是:
sed -e '$s/\(.*\)\n$/\1/' abc
任何想法如何做到这一点?
\n
,但是在Linux中是一个字符
我有一些文件要删除最后一个换行符(如果它是文件中的最后一个字符)。 od -c
告诉我,我运行的命令确实以尾随新行写入文件:
0013600 n t > \n
我用sed尝试了一些技巧,但我能想到的最好的方法不是:
sed -e '$s/\(.*\)\n$/\1/' abc
任何想法如何做到这一点?
\n
,但是在Linux中是一个字符
Answers:
perl -pe 'chomp if eof' filename >filename2
或者,就地编辑文件:
perl -pi -e 'chomp if eof' filename
[编者注:-pi -e
最初是-pie
,但正如一些评论者所指出并由@hvd解释,后者不起作用。]
在我看到的awk网站上,这被描述为“ perl亵渎”。
但是,在测试中,它奏效了。
chomp
。并且击败文件。
perl -pi -e 'chomp if eof' filename
来就地编辑文件,而不是创建临时文件
perl -pie 'chomp if eof' filename
->无法打开perl脚本“如果eof,则显示chomp”:没有这样的文件或目录;perl -pi -e 'chomp if eof' filename
->
您可以利用shell 命令替换删除尾随换行符的事实:
适用于bash,ksh,zsh的简单形式:
printf %s "$(< in.txt)" > out.txt
便携式(兼容POSIX)(效率略低):
printf %s "$(cat in.txt)" > out.txt
注意:
in.txt
有端多个换行符,命令替换删除所有的人 -感谢,@Sparhawk。(除了尾随换行符,它不会删除空格字符。)printf %s
确保没有换行符附加到输出(它是非标准的POSIX兼容替代品echo -n
;请参阅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
块中进行处理,\n
由于将输出记录分隔符(OFS
)设置为空字符串,因此可以在该行中无尾随地进行打印。如果您想要一个冗长而又快速且强大的解决方案,以真正就地进行编辑(而不是创建一个临时文件来替换原始文件),请考虑使用jrockway的Perl脚本。
您可以head
通过GNU coreutils使用此功能,它支持相对于文件末尾的参数。因此,离开最后一个字节使用:
head -c -1
要测试结尾的换行符,可以使用tail
和wc
。下面的示例将结果保存到一个临时文件,然后覆盖原始文件:
if [[ $(tail -c1 file | wc -l) == 1 ]]; then
head -c -1 file > file.tmp
mv file.tmp file
fi
您还可以使用sponge
from moreutils
进行“就地”编辑:
[[ $(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
并支持就地编辑。
truncate --size=-1
而不是head -c -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
版本。它需要两个偏移量(和另一个测试),而我只使用了一个。但是,您可以使用printf
代替ORS
。
head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
高克
awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' file
这应该更容易阅读。
awk 'NR>1 {print p} {p=$0} END {printf $0}' file
。
printf
是format参数。因此,如果输入文件中的内容可以解释为格式说明符,例如%d
,则会出现错误。解决方法是将其更改为printf "%s" $0
一种单行文件的非常简单的方法,需要coreutils的GNU回显:
/bin/echo -n $(cat $file)
\n
存在时会出现问题。随着它转换为新行。
$(...)
被引用
/bin/echo -n "$(cat infile)"
另外,我不确定echo
os / shell版本/ distros 的最大len 或shell 的最大长度(我只是在搜索它,这是一个兔子洞),所以我不确定除小文件以外的其他东西的便携性(或性能),但是对于小文件来说,太好了。
如果您想做正确的事,则需要这样的东西:
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;
}
我们打开文件进行读取和追加;打开附加文件意味着我们已经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 golf”的精神,这是我最短的Python解决方案。它将整个文件从标准输入中提取到内存中,剥离所有换行符,然后将结果写入标准输出中。不如Perl简洁;您只是无法在像这样的快速技巧上击败Perl。
从调用中删除“ \ n”,.rstrip()
它将删除文件末尾的所有空白,包括多行空白。
将其放入“ slurp_and_chomp.py”,然后运行python slurp_and_chomp.py < inputfile > outputfile
。
import sys
sys.stdout.write(sys.stdin.read().rstrip("\n"))
一个快速的解决方案是使用gnu实用程序truncate
:
[ -z $(tail -c1 file) ] && truncate -s-1 file
如果文件的末尾有换行符,则测试为true。
删除速度非常快,确实就位,不需要任何新文件,并且搜索也从结尾仅读取了一个字节(tail -c1
)。
[ -z $(tail -c1 filename) ] && truncate -s -1 filename
(另外,作为对其他评论的答复,该truncate
命令不适用于stdin,必须使用文件名)
$ perl -e'local $ /; $ _ = <>; s / \ n $ //; 打印'a-text-file.txt
另请参见匹配sed中的任何字符(包括换行符)。
tr -d '\n'
perl -pi -e 's/\n$// if(eof)' your_file
g
或括号eof
:perl -pi -e 's/\n$// if eof' your_file
。
假设使用Unix文件类型,则只需要最后一个换行符即可。
sed -e '${/^$/d}'
它不能在多个换行符上工作...
* 仅在最后一行为空行时有效。
sed
即使对于最后一行非空白也有效的解决方案:stackoverflow.com/a/52047796
FTR的另一个答案(也是我的最爱!):回显/捕捉要剥离的内容,并通过反引号捕获输出。最后的换行符将被删除。例如:
# 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
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.
echo -en 'a\nb\n' | sed '${/^$/d}'
将不会删除任何内容。echo -en 'a\nb\n\n' | sed '${/^$/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/$//
意思是用“什么都不用”代替“行尾”,这实际上什么也没做。但是,如果没有尾随的换行符,则会产生副作用。注意:Mac的默认设置head
不支持该-c
选项。您可以代替brew install coreutils
使用ghead
。