使用Bash脚本循环删除多个文件的第一行

9

我刚开始学习Bash脚本和编程。我想自动删除目录中多个.data文件的第一行。我的脚本如下:

#!/bin/bash
for f in *.data ;
do tail -n +2 $f | echo "processing $f";
done

我收到了回应信息,但是当我查看文件时内容没有改变。有什么想法吗?
提前致谢。
4个回答

22

我收到了回显消息,但是当我查看文件时,文件内容并没有改变。

因为简单的使用tail命令并不能改变文件内容。

你可以使用sed命令来在原地修改文件,同时排除第一行。具体来说,可以这样做:

sed -i '1d' *.data

将从所有.data文件中删除第一行。


注:BSD sed(在OSX上)需要一个参数来使用-i,因此您可以指定一个扩展名以备份旧文件,或者直接编辑文件:

sed -i '' '1d' *.data

我在一个测试目录中尝试了这个(使用复制的文件),并得到了以下输出:sed: 1: "201001010000a.WAH.data": 命令 a 需要跟随文本后面的反斜杠 - Marko
@Marko 看起来你没有执行正确的命令。既然你已经将文件复制到一个新目录中,那么你可以简单地复制/粘贴上面提到的命令。 - devnull
1
还是没有成功,这可能是OSX上sed的问题吗? - Marko
@Marko 提到的命令应该可以在BSD的 sed 上工作(这个版本在 OSX 上可用)。 sed -i '1d' filename 是什么意思(其中 filename 指的是单个文件)? - devnull
@Marko 原来在OSX上(即BSD sed),sed命令需要将扩展名作为-i参数的一部分。你可以这样写:sed -i '' '1d' *.data。我也已经编辑了答案。 - devnull
6
@devnull这只是从目录中的一个文件中删除第一行,而非所有文件。 - soote

4
你没有改变文件本身。通过使用 tail 命令,你只是读取文件并将其部分内容输出到 stdout(终端),你需要将该输出重定向到一个临时文件中,然后用临时文件覆盖原始文件。
#!/usr/bin/env bash
for f in *.data; do
    tail -n +2 "$f" > "${f}".tmp && mv "${f}".tmp "$f"
    echo "Processing $f"
done

此外,您使用echo命令的目的并不清楚。为什么在那里使用管道符(|)? 将为您提供更简单的实现方式。请参阅devnull的答案。

谢谢,这个方法很有效,并且解释得非常清楚。我编写代码的原因是想删除带有“tail”的行,然后通过在终端上回显来获得通知,以便查看我完成了1000多个文件的进度。感谢您对使用“tail”的评论,我将来会正确地使用它。 - Marko

0
我会这样做:
#!/usr/bin/env bash
set -eu
for f in *.data; do
  echo "processing $f"
  tail -n +2 "$f" | sponge "$f"
done

如果您没有sponge,可以在moreutils软件包中获取它。

文件名周围的引号很重要 - 它们将使其适用于包含空格的文件名。而顶部的env是为了让人们通过他们的PATH设置想要使用的Bash解释器,以防有人有非默认的解释器。set -eu会使Bash在发生错误时退出,这通常更安全。


0

ed 是标准编辑器:

shopt -s nullglob
for f in *.data; do
    echo "Processing file \`$f'"
    ed -s -- "$f" < <( printf '%s\n' "1d" "wq" )
done

shopt -s nullglob在这里是因为在使用通配符时你应该总是使用它,特别是在脚本中:如果没有匹配项,它将使通配符扩展为无内容;你不希望用不受控制的参数运行命令。

接下来,我们循环遍历所有文件,并使用以下命令:ed

  • 1:到第一行
  • d:删除该行
  • wq:写入并退出

ed的选项:

  • -s:告诉ed保持安静!我们不想让ed在屏幕上打印垃圾信息。
  • --:选项结束:这将使你的脚本更加健壮,以防文件名以连字符开头:在这种情况下,连字符会混淆ed试图处理它作为一个选项。使用--ed知道在此之后没有更多选项,并将愉快地处理任何文件,即使文件名以连字符开头。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接