sed 在文件名中使用通配符时出现问题

sed 在文件名中使用通配符时出现问题

这有效:

sudo sed 's/good times/bad times/' Chapter1.html > output/Chapter1.html

这不起作用:

sudo sed 's/good times/bad times/' Chapter*.html > output/Chapter*.html

这也不起作用:

sudo sed 's/good times/bad times/' *.html > output/*.html

由于有五十章,我可以让 sed 使用通配符吗?

答案1

正如其他人评论的那样,这需要一个循环,而不仅仅是通配符。

例如,要使用 shellfor循环执行此操作:

for f in ./*.html; do
  sed 's/good times/bad times/' "$f" > "output/$f"
done

这依次将变量设置f为每个 .html 文件名,为循环的每次迭代执行循环内的代码。请参阅下文了解为什么我使用./*.html而不是仅使用*.html.

f请注意它在语句本身中如何具有裸字for(因为这是它设置值的地方),但是$f当变量在循环内部使用时(因为这是它被扩展的地方)。

变量扩展也用双引号引起来,以确保它们不会破坏脚本(或更糟),如果它们碰巧包含空白字符或对 shell 有特殊含义的其他字符(例如;&>和许多其他的)。使用变量时未能引用变量可能是 shell 脚本错误的首要原因。看为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?$VAR 与 ${VAR} 以及引用或不引用了解原因。

您可以使用您喜欢的任何变量名称,例如

for Chapter in ./*.html; do
  sed 's/good times/bad times/' "$Chapter" > "output/$Chapter"
done

另外值得注意的是:如果目录中没有 .html 文件,shell 会将f(或Chapter) 设置为文字字符串,*.html除非您首先使用 开启该nullglob选项shopt -s nullglob。从man bash

nullglob

如果设置,bash 允许不匹配任何文件的模式(请参阅
上面的路径名扩展)扩展为空字符串,而不是它们本身。


顺便说一句,我./*.htmlfor循环一起使用,而不仅仅是*.html为了防止文件名sed被解释为命令行选项之一。

正如 @StéphaneChazelas 在评论中提到的,如果目录中存在以 开头-e和结尾的文件名,sed 会将其解释为要执行的 sed 脚本。#.html这种情况不太可能发生(但是排泄物会发生,恶意也会发生),但最好尽可能地进行防御性编程。

通过使用./*.html,而不是 sed 看到一个参数,例如,-e1,$d由于名为-e1,$d#.html(这是一个完全有效的文件名)的文件,它看到的参数./-e1,$d不会被解释为 sed 的命令行选项之一...sed 的选项不以./.

另外:因为$f以 开头./,因此文件名之类的输出foo.html将被重定向到output/./foo.html.这完全没问题,./路径中有额外的元素仍然解析到相同的目的地。即使是荒谬的事情也output/./././[a million more ./s]/foo.html只是output/foo.html

如果您使用 GNU sed(这是 linux 上的标准 sed)或(几乎?)任何现代版本的 sed,您可以使用 来--指示选项参数的结束:

for f in *.html; do
  sed 's/good times/bad times/' -- "$f" > "output/$f"
done

或者两者都做:

for f in ./*.html; do
  sed 's/good times/bad times/' -- "$f" > "output/$f"
done

答案2

shell 在命令行上扩展通配符,而正在运行的命令既看不到它们也不知道如何处理它们。

所以如果你有文件a.html b.html并且output/b.html output/c.html 你运行了

sed ... *.html > output/*.html

实际运行的命令是

sed ... a.html b.html > output/b.html output/c.html

这是一个语法错误(无法重定向到两个文件)并且与您可能想要的完全不一样。

这里的解决方案是使用 for 循环并将 * 替换为循环的索引变量。如果任何文件名中有空格,则需要进行一些引用。在这个问题的副本中,有很多关于如何正确执行此操作的示例。

答案3

假设您位于包含文件的工作目录中,您可以使用find

$ find . -name 'Chapter*' -exec sed 's/good times/bad times/woutput/{}' {} 2> /dev/null \;

相关内容