如何使用bash/ses脚本删除文本文件的第一行?

我需要使用bash脚本反复从一个巨大的文本文件中删除第一行。

现在我正在使用sed -i -e "1d" $FILE-但删除大约需要一分钟。

有没有更有效的方法来实现这一点?

717059 次浏览

不,这和你将要得到的一样有效。你可以编写一个C程序,它可以更快地完成这项工作(更少的启动时间和处理参数),但随着文件变大,它可能会趋向于与ses相同的速度(如果需要一分钟,我假设它们很大)。

但是你的问题和其他许多问题一样,它预先假设了解决方案。如果你能详细告诉我们你想做的什么而不是如何,我们也许能建议一个更好的选择。

例如,如果这是其他程序B处理的文件A,一种解决方案是不剥离第一行,而是修改程序B以不同的方式处理它。

假设您的所有程序都附加到此文件A,并且程序B当前在删除它之前读取并处理第一行。

您可以重新设计程序B,使其不会尝试删除第一行,而是在文件A中保留一个持久的(可能是基于文件的)偏移量,以便下次运行时,它可以查找该偏移量,在那里处理该行,并更新偏移量。

然后,在安静的时间(午夜?),它可以对文件A进行特殊处理以删除当前处理的所有行并将偏移量设置回0。

程序打开并查找文件肯定比打开并重写要快得多。本讨论当然假设您可以控制程序B。我不知道是否是这样,但如果您提供更多信息,可能还有其他可能的解决方案。

由于听起来我不能加快删除速度,我认为一个好的方法可能是像这样批量处理文件:

While file1 not empty
file2 = head -n1000 file1
process file2
sed -i -e "1000d" file1
end

这样做的缺点是,如果程序在中间被杀死(或者如果那里有一些坏的sql-导致“进程”部分死亡或锁定),则会有跳过或处理两次的行。

(file1包含几行sql代码)

正如Pax所说,你可能不会比这更快了。原因是几乎没有文件系统支持从文件开头截断,所以这将是一个O(n)操作,其中n是文件的大小。你可以做的更快的是用相同数量的字节覆盖第一行(可能使用空格或注释),这可能适合你,这取决于你正在尝试做什么(顺便说一下那是什么?)。

在N-1行上使用尾巴并将其引导到文件中,然后删除旧文件并将新文件重命名为旧名称是否可以完成这项工作?

如果我以编程方式执行此操作,我会读取文件,并在读取每行后记住文件偏移量,因此我可以返回该位置以少一行读取文件。

尝试尾巴

tail -n +2 "$FILE"

-n x:只需打印最后x行。tail -n 5将为您提供输入的最后5行。+符号有点反转参数并使tail打印除前x-1行之外的任何内容。tail -n +1将打印整个文件,tail -n +2将打印除第一行之外的所有内容,等等。

GNUtailsed快得多。tail也可在BSD上使用,-n +2标志在两个工具中是一致的。查看FreeBSDosx手册页了解更多信息。

不过,BSD版本可能比sed慢得多。我想知道他们是如何做到的;tail应该逐行读取文件,而sed执行相当复杂的操作,包括解释脚本、应用正则表达式等。

注意:您可能会使用

# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"

但这会给你一个空文件。原因是重定向(>)发生在shell调用tail之前:

  1. Shell截断文件$FILE
  2. Shell为tail创建一个新进程
  3. Shell将tail进程的标准输出重定向到$FILE
  4. tail从现在为空的$FILE读取

如果您想删除文件中的第一行,您应该使用:

tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"

&&将确保文件在出现问题时不会被覆盖。

如何使用cplit?

man csplit
csplit -k file 1 '{1}'

如果你想要做的是在失败后恢复,你可以建立一个文件,其中包含你到目前为止所做的事情。

if [[ -f $tmpf ]] ; then
rm -f $tmpf
fi
cat $srcf |
while read line ; do
# process line
echo "$line" >> $tmpf
done

可以编辑文件:只需使用perl的-i标志,如下所示:

perl -ni -e 'print unless $. == 1' filename.txt

如您所问,这会使第一行消失。Perl需要读取和复制整个文件,但它安排将输出保存在原始文件的名称下。

对于那些使用非GNU的SunOS的人,以下代码将有所帮助:

sed '1d' test.dat > tmp.dat

您可以使用-i更新文件,而无需使用“>”运算符。以下命令将从文件中删除第一行并将其保存到文件中(在幕后使用临时文件)。

sed -i '1d' filename

spongeutil源码避免了杂耍临时文件的需要:

tail -n +2 "$FILE" | sponge "$FILE"

应该显示除第一行之外的行:

cat textfile.txt | tail -n +2

可以使用vim来执行此操作:

vim -u NONE +'1d' +'wq!' /tmp/test.txt

这应该更快,因为vim在处理时不会读取整个文件。

如果您想修改文件,您可以始终使用原始的ed而不是其<的trong>的treaming后继者sed

ed "$FILE" <<<$'1d\nwq\n'

ed命令是最初的UNIX文本编辑器,在全屏终端出现之前,更不用说图形工作站了。ex编辑器是ed版本,因此许多相同的命令都可以工作。虽然ed旨在交互式使用,但它也可以通过向它发送一串命令来以批处理模式使用,这就是这个解决方案的作用。

序列<<<$'1d\nwq\n'利用了现代shell对here字符串(<<<)和ANSI引号($'')的支持,将输入提供给ed命令,该命令由两行组成:1d,其中d选择1行,然后wq,其中w将文件返回磁盘,然后<<<0uits编辑会话。

你可以很容易地做到这一点:

cat filename | sed 1d > filename_without_first_line

在命令行上;或者要永久删除文件的第一行,请使用带有-i标志的se的就地模式:

sed -i 1d <filename>

这一个班轮将做:

echo "$(tail -n +2 "$FILE")" > "$FILE"

它可以工作,因为tailecho之前执行,然后文件被解锁,因此不需要临时文件。

基于其他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
tail +2 path/to/your/file

适用于我,无需指定-n标志。原因请参阅亚伦的回答

您可以使用sed命令按行号删除任意行

# create multi line txt file
echo """1. first
2. second
3. third""" > file.txt

删除行并打印到标准输出

$ sed '1d' file.txt
2. second
3. third


$ sed '2d' file.txt
1. first
3. third


$ sed '3d' file.txt
1. first
2. second


# delete multi lines
$ sed '1,2d' file.txt
3. third


# delete the last line
sed '$d' file.txt
1. first
2. second

使用-i选项就地编辑文件

$ cat file.txt
1. first
2. second
3. third


$ sed -i '1d' file.txt


$cat file.txt
2. second
3. third