我需要使用 bash 脚本从一个巨大的文本文件中反复删除第一行。
现在我正在使用sed -i -e "1d" $FILE
- 但删除大约需要一分钟。
有没有更有效的方法来实现这一点?
试试尾巴:
tail -n +2 "$FILE"
-n x
: 只打印最后x
几行。tail -n 5
会给你输入的最后 5 行。+
符号类型反转参数并tail
打印除第一x-1
行以外的任何内容。tail -n +1
将打印整个文件,tail -n +2
除第一行外的所有内容等。
GNUtail
比sed
. tail
在 BSD 上也可以使用,并且-n +2
标志在两个工具中是一致的。查看FreeBSD或OS X手册页以获取更多信息。
不过,BSD 版本可能比sed
. 我想知道他们是如何做到的;tail
应该只逐行读取文件,同时sed
执行非常复杂的操作,包括解释脚本、应用正则表达式等。
注意:您可能很想使用
# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"
但这会给你一个空文件。原因是重定向 ( >
) 发生在tail
shell 调用之前:
$FILE
tail
tail
进程的标准输出重定向到$FILE
tail
从现在空的读取$FILE
如果要删除文件中的第一行,应使用:
tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
这&&
将确保文件在出现问题时不会被覆盖。
您可以使用 -i 来更新文件,而无需使用 '>' 运算符。以下命令将从文件中删除第一行并将其保存到文件中(在后台使用临时文件)。
sed -i '1d' filename
对于那些使用非 GNU 的 SunOS 的人,以下代码将有所帮助:
sed '1d' test.dat > tmp.dat
您可以通过以下方式轻松做到这一点:
cat filename | sed 1d > filename_without_first_line
在命令行上;-i
或者要永久删除文件的第一行,请使用带有标志的 sed 就地模式:
sed -i 1d <filename>
不,这与您将获得的效率差不多。您可以编写一个 C 程序,它可以更快地完成这项工作(更少的启动时间和处理参数),但随着文件变大,它可能会趋向与 sed 相同的速度(如果需要一分钟,我假设它们很大) )。
但是您的问题与许多其他问题存在相同的问题,因为它预先假定了解决方案。如果您要详细告诉我们您要做什么而不是如何做,我们可能会提出更好的选择。
例如,如果这是其他程序 B 处理的文件 A,则一种解决方案是不删除第一行,而是修改程序 B 以不同方式处理它。
假设您的所有程序都附加到此文件 A 和程序 B 当前读取并处理第一行,然后再删除它。
您可以重新设计程序 B,以便它不会尝试删除第一行,而是在文件 A 中维护一个持久的(可能基于文件的)偏移量,以便下次运行时,它可以寻找该偏移量,处理那里的线,并更新偏移量。
然后,在安静的时间(午夜?),它可以对文件 A 进行特殊处理,以删除当前处理的所有行并将偏移量设置回 0。
程序打开和查找文件肯定比打开和重写更快。当然,此讨论假设您可以控制程序 B。我不知道是否是这种情况,但如果您提供更多信息,可能还有其他可能的解决方案。
该sponge
实用程序避免了处理临时文件的需要:
tail -n +2 "$FILE" | sponge "$FILE"
如果你想修改文件,你总是可以使用原始文件ed
而不是它的 s处理后继sed
:
ed "$FILE" <<<$'1d\nwq\n'
该ed
命令是最初的 UNIX 文本编辑器,甚至还没有全屏终端,更不用说图形工作站了。编辑器,最广为人知的ex
是您在冒号提示符下键入时使用的内容vi
,它是 的扩展版本ed
,因此许多相同的命令都可以工作。虽然ed
旨在以交互方式使用,但它也可以通过向其发送一串命令以批处理模式使用,这就是此解决方案的作用。
该序列<<<$'1d\nwq\n'
利用现代 shell 对 here-strings ( )<<<
和 ANSI 引号 ( $'
... ) 的支持,'
将输入提供给ed
由两行组成的命令:删除第1行,然后将文件写回输出到磁盘,然后退出编辑会话。1d
wq
正如 Pax 所说,您可能不会比这更快。原因是几乎没有文件系统支持从文件开头截断,所以这将是一个 O( n
) 操作,其中n
文件的大小是多少。您可以更快地做的是用相同数量的字节(可能带有空格或注释)覆盖第一行,这可能对您有用,具体取决于您正在尝试做什么(顺便说一句?)。
您可以就地编辑文件:只需使用 perl 的-i
标志,如下所示:
perl -ni -e 'print unless $. == 1' filename.txt
如您所问,这会使第一行消失。Perl 将需要读取和复制整个文件,但它会安排将输出保存在原始文件的名称下。
应该显示除第一行以外的行:
cat textfile.txt | tail -n +2
可以使用 vim 来做到这一点:
vim -u NONE +'1d' +'wq!' /tmp/test.txt
这应该更快,因为 vim 在处理时不会读取整个文件。
使用 csplit 怎么样?
man csplit
csplit -k file 1 '{1}'
这一个班轮将做:
echo "$(tail -n +2 "$FILE")" > "$FILE"
它可以工作,因为tail
在之前执行echo
然后文件被解锁,因此不需要临时文件。
由于听起来我无法加快删除速度,我认为一个好的方法可能是像这样批量处理文件:
While file1 not empty
file2 = head -n1000 file1
process file2
sed -i -e "1000d" file1
end
这样做的缺点是,如果程序在中间被杀死(或者如果那里有一些错误的 sql - 导致“进程”部分死亡或锁定),则会有一些行被跳过或处理两次.
(file1 包含几行 sql 代码)
如果您要做的是在失败后恢复,您可以构建一个包含您迄今为止所做的文件的文件。
if [[ -f $tmpf ]] ; then
rm -f $tmpf
fi
cat $srcf |
while read line ; do
# process line
echo "$line" >> $tmpf
done
基于其他 3 个答案,我想出了在我的 Mac OSx bash shell 中完美运行的语法:
line=$(head -n1 list.txt && echo "$(tail -n +2 list.txt)" > list.txt)
测试用例:
~> printf "Line #%2d\n" {1..3} > list.txt
~> cat list.txt
Line # 1
Line # 2
Line # 3
~> line=$(head -n1 list.txt && echo "$(tail -n +2 list.txt)" > list.txt)
~> echo $line
Line # 1
~> cat list.txt
Line # 2
Line # 3
是否会在 N-1 行上使用 tail 并将其定向到文件中,然后删除旧文件,并将新文件重命名为旧名称来完成这项工作?
如果我以编程方式执行此操作,我将在读取每一行后通读文件并记住文件偏移量,因此我可以回到该位置以读取文件中少一行。