我需要使用bash脚本从文件中删除特定的行号。
我使用-n选项从grep命令获取行号。
我出于各种原因无法使用sed,其中最不重要的是它没有安装在这个脚本需要运行的所有系统上,并且安装它不是一种选择。
awk是不可能的,因为在测试中,在具有不同UNIX / Linux操作系统(RHEL,SunOS,Solaris,Ubuntu等)的不同机器上,它会在每个机器上给出(有时是非常不同的)不同的结果。所以,没有awk。
有问题的文件只是一个平面文本文件,每行一条记录,所以除了删除数字之外,不需要做任何花哨的事情。
如果可能的话,我需要避免做一些事情,比如提取文件的内容,不包括我想要的行,然后覆盖原始文件。
答案 0 :(得分:6)
由于你有grep
,显而易见的事情是:
$ grep -v "line to remove" file.txt > /tmp/tmp
$ mv /tmp/tmp file.txt
$
但听起来你不想使用任何临时文件 - 我认为输入文件很大,这是一个内存和存储空间不足的嵌入式系统。我认为你理想情况下需要一个可以编辑文件的解决方案。我认为dd
可能会出现这种情况,但尚未弄明白:(
更新 - 我想出了如何使用dd编辑文件。此外,还需要grep
,head
和cut
。如果这些不可用,那么它们可能在大多数情况下可以解决:
#!/bin/bash
# get the line number to remove
rline=$(grep -n "$1" "$2" | head -n1 | cut -d: -f1)
# number of bytes before the line to be removed
hbytes=$(head -n$((rline-1)) "$2" | wc -c)
# number of bytes to remove
rbytes=$(grep "$1" "$2" | wc -c)
# original file size
fsize=$(cat "$2" | wc -c)
# dd will start reading the file after the line to be removed
ddskip=$((hbytes + rbytes))
# dd will start writing at the beginning of the line to be removed
ddseek=$hbytes
# dd will move this many bytes
ddcount=$((fsize - hbytes - rbytes))
# the expected new file size
newsize=$((fsize - rbytes))
# move the bytes with dd. strace confirms the file is edited in place
dd bs=1 if="$2" skip=$ddskip seek=$ddseek conv=notrunc count=$ddcount of="$2"
# truncate the remainder bytes of the end of the file
dd bs=1 if="$2" skip=$newsize seek=$newsize count=0 of="$2"
如此运行:
$ cat > file.txt
line 1
line two
line 3
$ ./grepremove "tw" file.txt
7+0 records in
7+0 records out
0+0 records in
0+0 records out
$ cat file.txt
line 1
line 3
$
可以说dd
是非常危险的工具。您可以轻松地无意中覆盖文件或整个磁盘。要小心!
答案 1 :(得分:4)
答案 2 :(得分:2)
如果n
是您要忽略的行:
{
head -n $(( n-1 )) file
tail +$(( n+1 )) file
} > newfile
答案 3 :(得分:2)
你可以在没有grep的情况下使用posix shell builtins来实现,它应该在任何* nix上。
while read LINE || [ "$LINE" ];do
case "$LINE" in
*thing_you_are_grepping_for*)continue;;
*)echo "$LINE";;
esac
done <infile >outfile
答案 4 :(得分:2)
鉴于dd
被认为对于此就地行删除而言太危险,我们需要一些其他方法来对文件系统调用进行相当细粒度的控制。我最初的冲动是用c写一些东西,但尽管可能,我认为这有点过分。相反,值得研究通用脚本(而不是shell脚本)语言,因为这些语言通常具有相当低级别的文件API,这些API以相当简单的方式映射到文件系统调用。我猜这可以使用python,perl,Tcl或许多其他可能的脚本语言来完成。我对Tcl最熟悉,所以我们走了:
#!/bin/sh
# \
exec tclsh "$0" "$@"
package require Tclx
set removeline [lindex $argv 0]
set filename [lindex $argv 1]
set infile [open $filename RDONLY]
for {set lineNumber 1} {$lineNumber < $removeline} {incr lineNumber} {
if {[eof $infile]} {
close $infile
puts "EOF at line $lineNumber"
exit
}
gets $infile line
}
set bytecount [tell $infile]
gets $infile rmline
set outfile [open $filename RDWR]
seek $outfile $bytecount start
while {[gets $infile line] >= 0} {
puts $outfile $line
}
ftruncate -fileid $outfile [tell $outfile]
close $infile
close $outfile
注意我的特定框我有Tcl 8.4,所以我必须加载Tclx包才能使用ftruncate命令。在Tcl 8.5中,可以使用chan truncate
代替。
您可以将要删除的行号和文件名传递给此脚本。
简而言之,脚本执行此操作:
准确编辑文件。没有使用临时文件。
我很确定这可以用python或perl重写,或者......如果有必要的话。
的更新强> 的
好的,所以使用与上面的Tcl脚本类似的技术,可以在几乎纯粹的bash中完成就地删除行。但最重要的警告是你需要truncate
命令。我确实在我的Ubuntu 12.04 VM上有它,但不是在我的旧的基于Redhat的盒子上。这是脚本:
#!/bin/bash
n=$1
filename=$2
exec 3<> $filename
exec 4<> $filename
linecount=1
bytecount=0
while IFS="" read -r line <&3 ; do
if [[ $linecount == $n ]]; then
echo "omitting line $linecount: $line"
else
echo "$line" >&4
((bytecount += ${#line} + 1))
fi
((linecount++))
done
exec 3>&-
exec 4>&-
truncate -s $bytecount $filename
#### or if you can tolerate dd, just to do the truncate:
# dd of="$filename" bs=1 seek=$bytecount count=0
#### or if you have python
# python -c "open(\"$filename\", \"ab\").truncate($bytecount)"
我希望听到一种更通用的(仅限bash?)方式来完成部分截断并完成此答案。当然截断也可以用dd
完成,但我认为已经排除了我之前的答案。
记录this site列出了如何在许多不同语言中进行就地文件截断 - 以防在您的环境中使用这些文件。
答案 5 :(得分:1)
如果您可以指出最明显的Awk脚本在哪个平台上失败的情况,也许我们可以设计一个解决方法。
awk "NR!=$N" infile >outfile
如果当然,只需将$N
与grep
一起提供给Awk就可以获得很好的低音效果。这将删除包含第一次出现的foo
:
awk '/foo/ { if (!p++) next } 1' infile >outfile
答案 6 :(得分:-1)
基于Digital Trauma的回答,我发现了一个只需要grep和echo的改进,但是没有tempfile:
echo $(grep -v PATTERN file.txt) > file.txt
根据文件所包含的行类型以及模式是否需要更复杂的语法,您可以使用双引号包含grep命令:
echo "$(grep -v PATTERN file.txt)" > file.txt
(从crontab中删除时很有用)