在文件中查找和替换并覆盖文件无效,它会清空文件。

635
我希望能够通过命令行对HTML文件进行查找和替换。
我的命令大致如下:
sed -e s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html > index.html

当我运行这个程序并查看文件后,发现它是空的。它删除了我的文件内容。

在我再次恢复文件后运行此程序:

sed -e s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html

stdout是文件内容,查找和替换已经执行。

为什么会发生这种情况?


14
Perl 替代方案:perl -pi -w -e 's/STRING_TO_REPLACE/REPLACE_WITH/g;' index.html (将 index.html 文件中的所有 STRING_TO_REPLACE 替换为 REPLACE_WITH,并且会直接修改原文件) - Gjorgji Tashkovski
有关使用sed命令查找字符串并替换整行的相关内容,请参考以下链接:https://dev59.com/82gu5IYBdhLWcg3wj3r5 - cregox
请参阅Unix&Linux SO上的如何使在同一管道中读写同一文件始终“失败”? - codeforester
12个回答

981
shell在命令行中看到> index.html时,它将打开index.html文件以进行写入操作,并擦除其先前的所有内容。
为了解决这个问题,您需要向sed传递-i选项,使更改内联,并在进行原地更改之前创建原始文件的备份:
sed -i.bak s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html

没有 .bak 扩展名,这个命令在某些平台上会失败,例如 Mac OSX。


24
使用"截断文件"代替"打开文件"可能会更加清晰。 - Mikel
13
至少在我的Mac上,第一个建议不起作用...如果你要对文件进行就地替换,必须指定一个扩展名。你可以至少传递一个零长度的扩展名:sed -i '' s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html。 - Tom Lianza
5
针对变量,使用以下命令在index.html文件中将$search替换成$replace,并备份原文件为index.html.bak:sed -i.bak 's/'$search'/'$replace'/g' index.html - Fatima Zohra
42
在OSX中,将空字符串''作为-i的参数,例如:sed -i '' 's/blah/xx/g' - Pierre Houston
4
sed -i 后面的 .bak 是什么意思?(注:.bak 是备份文件的后缀名) - Patrizio Bertoni
显示剩余7条评论

221

另一种有用的模式是:

sed -e 'script script' index.html > index.html.tmp && mv index.html.tmp index.html

这样做的效果基本相同,无需使用 -i 选项,而且意味着如果 sed 脚本由于某种原因失败,输入文件不会被覆盖。此外,如果编辑成功,就没有备份文件留下来。在 Makefiles 中这种惯用语法很有用。

相当多的 sed 命令有 -i 选项,但并非所有的都有;其中 posix sed 就没有。因此,如果你希望实现可移植性,最好避免使用。


10
如果编辑失败,没有备份文件留下并且不会破坏输入文件,这是一个加分项。在 Mac 上运行得非常出色。 - Mike Grace
1
完美地为我工作。谢谢!(在 Mac 上) - interested
1
这对我来说完美地起作用,在Ubuntu Server 14.04上,sed -i会将文件清零。 - Chris Giddings
2
极小的改进:... && mv index.html{.tmp,} - EdwardG
5
@EdwardGarson确实,如果我在键入时,那可能就是我会使用的 - 我同意这更加简洁 - 但是如果我没记错的话,sh不支持 {...} 的扩展。在Makefile中你可能会使用sh而不是bash,所以如果你想要可移植性(或符合posix标准),那么你需要避免使用那种语法结构。 - Norman Gray
简化版:mv index.html $(echo index.html | sed -e 'script') - Timo

99
sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' index.html

这会在文件index.html上进行全局就地替换。引用字符串可以防止查询和替换中的空格问题。


60

使用sed的-i选项,例如:

sed -i bak -e s/STRING_TO_REPLACE/REPLACE_WITH/g index.html

这是什么意思? sed: -i 不能与 stdin 一起使用。 - sheetal
2
如果您的模式包含空格,请记得用引号将其括起来 - 's/需要替换的字符串/替换成的字符串/g' - Doug Thompson
@sheetal:-i 用于对文件进行原地编辑,因此将其与标准输入结合使用是没有意义的。 - mklement0
这可能在 macOS 上可行,但对我来说在 Arch Linux 上不行。 - xdevs23
没有 -e,接受的答案在MacOS Catalina上无法工作。有了 -e 就可以运行。 - cwhiii

19

要更改多个文件(并将每个文件另存为*.bak的备份):

perl -p -i -e "s/\|/x/g" *  

将目录中的所有文件,并将|替换为x。这被称为“Perl饼干”(如同吃馅饼一样简单)


2
很高兴看到有人愿意关注问题陈述,而不仅仅是标签。OP没有将sed指定为要求,只是将其作为已经尝试过的工具使用。 - Chindraba

14

你应该尝试使用选项-i进行原地编辑。


7
警告:这是一种危险的方法!它滥用了Linux中的输入/输出缓冲区,并通过特定的缓冲选项使其能够处理小文件。这是一个有趣的好奇心。但不要在真实情况下使用它! 除了sed-i选项外,您还可以使用tee实用程序
man中得知:

tee-从标准输入读取并写入标准输出和文件

因此,解决方案如下:
sed s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html | tee | tee index.html

-- 在这里, tee 被重复使用以确保管道被缓冲。然后,在管道中的所有命令都被阻塞,直到它们获得一些可用的输入。在管道中的每个命令在上游命令写入 1 个缓冲区字节(大小在某些地方被定义)到命令的输入之后开始。因此,最后一个命令tee index.html,打开文件以进行写操作并因此将其清空,在上游管道完成且输出位于管道内缓冲区之后运行。
-- 最有可能以下内容不起作用:
sed s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html | tee index.html

-- 它会同时运行管道的两个命令,而不会有任何阻塞。(不阻塞时,管道应逐行传递字节,而不是逐缓冲区传递。与运行cat | sed s/bar/GGG/ 时相同。没有阻塞,它更加交互式,并且通常只有2个命令的管道运行时没有缓冲和阻塞。更长的管道会被缓冲。)tee index.html将打开文件进行写入,并将其清空。但是,如果您始终打开缓冲,则第二个版本也可以正常工作。


3
tee的输出文件也会立即打开,导致整个命令生成了一个空的index.html文件。 - sjngm
4
这将“破坏”任何大于“管道缓冲区”(通常为64KB)的输入文件。 (@sjngm:与“>”截断文件不同,但重点是这是一个有缺陷的解决方案,可能会导致数据丢失)。 - mklement0

6
sed -i.bak "s#https.*\.com#$pub_url#g" MyHTMLFile.html

如果您需要添加链接,请尝试以下方法。按照上述方式搜索URL(以此为开头以.com结尾),并将其替换为URL字符串。我在这里使用了一个变量$pub_url 。这里的s表示搜索,g表示全局替换。
它有效!

5

命令存在的问题

sed 'code' file > file

这是因为在sed实际处理文件之前,shell会截断file文件。结果就是你得到了一个空文件。

解决这个问题的sed方法是使用-i选项进行原地编辑,正如其他答案所建议的那样。然而,这并不总是你想要的。 -i将创建一个临时文件,然后用它来替换原始文件。如果原始文件是链接,则会出现问题(链接将被替换为常规文件)。如果您需要保留链接,可以使用临时变量存储sed的输出,然后将其写回文件,像这样:

tmp=$(sed 'code' file); echo -n "$tmp" > file

更好的方法是使用printf而不是echo,因为在某些shell(例如dash)中,echo很可能会将\\处理为\
tmp=$(sed 'code' file); printf "%s" "$tmp" > file

2
它也可以使用临时文件:sed 'code' file > file.tmp; cat file.tmp > file; rm file.tmp - dashohoxha

3

而且 ed 的答案是:

printf "%s\n" '1,$s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' w q | ed index.html

重申一下codaddict的答案,shell先处理重定向操作,清空了"input.html"文件,然后再调用"sed"命令并传递一个现在为空的文件。


2
快速问题,为什么人们总是给出“seded版本”的答案?它执行得更快吗? - cregox
6
有些sed命令不支持使用-i选项来原地编辑文件。而ed则非常普遍,并且可以让你将修改后的结果保存到原文件中。此外,拥有多种工具也总是有益的。 - glenn jackman
好的,很酷。就性能而言,我想它们是一样的。谢谢! - cregox
嗨,我知道有点晚了,但是我无法在这段代码中传递任何变量。例如:printf "%s\n" '1,$s/^STRING_TO_REPLACE.*/$MODPATH/g' w q | ed $SERVICESH > /dev/null 2>&1。我想将$MODPATH作为替换字符串传递,但无法使其正常工作。 - Some53

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