在原文件中直接编辑

507
如何在一个单独的sed命令中编辑文件?目前,我必须手动将编辑后的内容流式传输到一个新文件中,然后将新文件重命名为原始文件名。
我尝试了sed -i,但我的Solaris系统显示-i是一个非法选项。有其他方法吗?

11
-i 是 gnu sed 中的一个选项,但不属于标准 sed。使用该选项会将内容流式传输到新文件,然后将文件重命名,这可能不是您想要的。 - William Pursell
3
实际上,这正是我想要的。我只是希望不必执行重命名新文件为原始名称这种乏味任务时暴露身份。 - amphibient
3
那么你需要重新陈述问题。 - William Pursell
3
@amphibient: 你介意在你的问题标题前加上“Solaris”这个词吗?这样做可以增加你问题的价值。请查看我回答下面的评论。谢谢。 - Steve
2
@Steve:我再次从标题中删除了Solaris前缀,因为这绝不仅限于Solaris。 - tripleee
显示剩余2条评论
15个回答

826

-i选项将编辑的内容流式传输到一个新文件中,然后在幕后重命名它。

示例:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

如果您使用的是 macOS,则需要执行以下操作:

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

6
至少对我来说它能做到,这样我就不必自己去做了。 - amphibient
4
请将文本从英语翻译成中文。仅返回已翻译的文本:例如:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file> - Thales Ceolin
4
可以,但可能需要在其他地方或更短的时间内实现吗? - choroba
4
在Yocto构建系统中,以下示例有效:sed -i -e 's,STRING with SPACES,STRING to REPLACE with,g' filename。也就是说,逗号,的使用很重要,而不是斜杠 /。请注意,我会尽力使翻译通俗易懂,但不会添加任何解释或其他内容。 - Oleg Kokorin
5
+1 for mac,这可能是在过去4年中第3次我忘记它需要额外的引号并不得不返回到这篇文章。但是为了辩护,95%的时间我只是使用我的mac来ssh进入一个linux盒子。 - Ali
显示剩余10条评论

89

如果系统上的 sed 没有原地编辑文件的功能,我认为更好的解决方案是使用 perl

perl -pi -e 's/foo/bar/g' file.txt

虽然这会创建一个临时文件,但由于提供了一个为空的原地后缀/扩展名,它会替换原始文件。


20
它不仅创建临时文件,还会破坏硬链接(例如,它替换具有相同名称的新文件而不是更改文件内容)。有时这是期望的行为,有时可以接受,但当有多个链接指向一个文件时,几乎总是错误的行为。 - William Pursell
4
我认为所有没有Perl的系统都是有缺陷的。当我们需要提升权限并必须使用“sudo”时,一个单一的命令胜过一长串命令。我的问题在于如何避免手动创建和重命名临时文件,而不必安装最新的GNU或BSD“sed”。只需使用Perl即可。 - Steve
5
@PetrPeller:真的吗?如果你仔细阅读问题,你就会明白OP试图避免像这样的东西:sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt。仅仅因为原地编辑使用临时文件进行重命名,并不意味着当选项不可用时,他/她必须执行手动重命名。还有其他工具可以做到他/她想要的事情,Perl是显而易见的选择。这不是对原始问题的答案吗? - Steve
3
@a_arias说得对,他确实这样做了。但是他无法通过Solaris的sed实现他想要的结果。实际上,这个问题非常好,但是由于一些人不能阅读手册或不能真正阅读Stack Overflow上的问题,它的价值已经被削弱了。 - Steve
4
据我所知,Solaris附带Perl但不包括Python或Ruby。 此外,就像我之前所说的那样,使用Solaris sed无法实现OP想要的结果。请注意,本次翻译可能不是逐字逐句的,但保证传达原意。 - Steve
显示剩余5条评论

83
注意,在OS X上运行此命令时可能会出现奇怪的错误,例如“无效的命令代码”或其他奇怪的错误。要解决此问题,请尝试:
sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
这是因为在OSX版本的sed上,-i选项需要一个extension参数,所以你的命令实际上被解释为extension参数,而文件路径则被解释为命令代码。来源:https://dev59.com/tmIk5IYBdhLWcg3wPL_R#19457213

5
这是OS X的另一个怪异之处。幸运的是,使用brew install gnu-sed以及PATH,您可以使用GNU版本的sed。谢谢您提到这点,这确实令人费解。 - Doug

34

以下内容在我的Mac上运行良好。

sed -i.bak 's/foo/bar/g' sample

我们正在样本文件中将“foo”替换为“bar”。原始文件的备份将保存在sample.bak中。

如需在不进行备份的情况下进行内联编辑,请使用以下命令

sed -i'' 's/foo/bar/g' sample

有什么方法可以防止保留备份文件吗? - Petr Peller
@PetrPeller,您可以使用相同的命令... sed -i'' 's/foo/bar/g' sample - minhas23
1
我喜欢这个答案“sed -i.bak”,因为它适用于OS X和GNU。 - Ranzo Taylor

20

需要注意的一点是,sed不能单独写入文件,因为它的唯一目的是作为“流”(即标准输入、标准输出、标准错误和其他>&n缓冲区、套接字等)的编辑器。有了这个想法,您可以使用另一个命令tee将输出写回文件。另一种选择是通过将内容传输到diff中创建补丁。

Tee方法

sed '/regex/' <file> | tee <file>

补丁方法

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

更新:

另外,请注意,补丁将使文件从差异输出的第1行发生更改:

补丁不需要知道要访问哪个文件,因为这在diff输出的第一行中找到:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar

3
“/dev/stdin 2014-03-15”,已回答于1月7日 - 你是时间旅行者吗? (翻译:"/dev/stdin 2014-03-15" 是一个命令行输入,而“已回答于1月7日 - 你是时间旅行者吗?”是对此命令的评论和问题。) - Adrian Frühwirth
在Windows上,使用msysgit时,/dev/stdin不存在,因此您必须用一个单破折号'-'替换/dev/stdin,以便以下命令可以正常工作:$ sed 's/oo/u/' fubar | diff -p fubar -$ sed 's/oo/u/' fubar | diff -p fubar - | patch - Jim Raden
@AdrianFrühwirth 我确实有一个蓝色的警察电话亭,而且出于某种原因他们在工作中叫我博士。但说实话,看起来系统时钟漂移非常严重。 - Dwight Spencer
4
这些解决方案似乎依赖于特定的缓冲行为。我怀疑对于更大的文件,这些方法可能会失效,因为当sed正在读取文件时,patch命令甚至更糟糕的是tee命令可能会截断文件。实际上,我不确定'patch'是否也会截断文件。我认为将输出保存到另一个文件中,然后再通过“cat”命令合并到原始文件中会更安全。 - akostadinov
@william-pursell 的真正原地解答 - akostadinov
请注意,在BSD上,-i选项还会改变处理多个输入文件时的行为:它们会被独立处理(例如$匹配每个文件的最后一行)。此外,inplace标志在BusyBox和Solaris上不起作用,在MacRelix/Cygwin下也不可靠。另外,inplace sed本身也依赖于缓冲行为。在所有情况下都没有文件锁,因此在sed、diff/patch或任何标准Unix工具解析并写入文件之前,文件可能会被更改。如果担心这种情况,那么应该使用fifo。至于patch,它根本不会截断文件。 - Dwight Spencer

17

支持使用-i选项在原地编辑文件的sed版本会先写入一个临时文件,然后再将文件重命名。

或者,您可以直接使用ed。例如,要将文件file.txt中所有出现的foo更改为bar,可以执行以下操作:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

语法类似于sed,但并不完全相同。

即使您没有支持sed -i的选项,您仍然可以轻松编写一个脚本来完成工作。您可以使用inline file sed 's/foo/bar/g'替代sed -i 's/foo/bar/g' file。这样的脚本很容易编写。例如:

#!/bin/sh
IN=$1
shift
trap 'rm -f "$tmp"' 0
tmp=$( mktemp )
<"$IN" "$@" >"$tmp" && cat "$tmp" > "$IN"  # preserve hard links

对于大多数情况来说应该是足够的。


1
那么例如,你会如何命名这个脚本并调用它? - amphibient
2
我会称之为 inline 并按照上述描述调用它:inline inputfile sed 's/foo/bar/g' - William Pursell
2
哇,只有 ed 的答案真正地做到了原地修改。这是我第一次需要使用 ed。谢谢。一个小优化是使用 echo -e ",s/foo/bar/g\012 w" 或者 echo $',s/foo/bar/g\012 w',如果 shell 允许的话,可以避免额外的 tr 调用。 - akostadinov
4
“ed”并没有真正地就地修改。从“strace”输出来看,GNU的“ed”会创建一个临时文件,将更改写入该临时文件,然后重新编写整个原始文件,保留硬链接。从“truss”输出来看,Solaris 11的“ed”也使用临时文件,但在使用“w”命令保存时将临时文件重命名为原始文件名,从而破坏任何硬链接。 - Andrew Henle
1
@AndrewHenle 这真是令人沮丧。当前macOS(Mojave,不管那是什么营销术语)附带的ed仍然保留硬链接。你的情况可能会有所不同。 - William Pursell

16

您可以使用vi编辑器

vi -c '%s/foo/bar/g' my.txt -c 'wq'

10

sed支持原地编辑。来自man sed

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

示例:

假设您有一个名为hello.txt的文件,其中包含以下文本:

hello world!

如果您想保留旧文件的备份,请使用:

sed -i.bak 's/hello/bonjour' hello.txt

你最终将获得两个文件:hello.txt,其内容为:

bonjour world!

同时使用旧的内容和hello.txt.bak文件。

如果您不想保留副本,只需不传递扩展参数即可。


5
OP明确提到他的平台不支持这一(流行但)非标准选项。 - tripleee

5

如果你要替换相同数量的字符,并仔细阅读了“文件原地”编辑,...

你也可以使用重定向运算符<>来打开文件进行读写:

sed 's/foo/bar/g' file 1<> file

现场演示:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

来自Bash参考手册 → 3.6.10 打开文件描述符进行读写

The redirection operator

[n]<>word

causes the file whose name is the expansion of word to be opened for both reading and writing on file descriptor n, or on file descriptor 0 if n is not specified. If the file does not exist, it is created.


这是一个已经损坏的文件。我不确定原因是什么。https://paste.fedoraproject.org/462017/ - Nehal J Wani
请查看第[43-44]行、第[88-89]行和第[135-136]行。 - Nehal J Wani
3
此篇博客解释了为什么不应该使用这个答案。http://backreference.org/2011/01/29/in-place-editing-of-files/ - Nehal J Wani
@NehalJWani,我会详细阅读这篇文章,感谢提醒!在回答中我没有提到的是,如果更改的字符数相同,那么此方法有效。 - fedorqui
@NehalJWani,话虽如此,如果我的回答对您的文件造成了伤害,我很抱歉。在阅读您提供的链接后,我将进行更正(或必要时删除)。 - fedorqui
@NehalJWani 在顶部更新了一个警告。感谢提供这篇非常详尽的文章的链接。 - fedorqui

4

正如《007:大破天幕杀机》中蒙尼彭尼所说:“有时候老方法最好。” 后来金凯德也说了类似的话。

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

愉快地进行原地编辑。 添加了'\nw\n'来写入文件。 对于回答请求的延迟表示歉意。


你能解释一下这个做什么吗? - Matt Montag
2
它调用了文本编辑器 ed(1),并将一些字符写入 stdin。作为一个30岁以下的人,我认为说ed(1)就像恐龙对我们一样,没有问题。无论如何,jlettvin通过使用 | 将字符管道输入而不是打开ed并键入这些内容。@MattMontag - Ari Sweedler
如果你想知道这些命令的作用,那么有两个命令,以\n结尾。[range]s/<old>/<new>/<flag>是替换命令的形式——这种范例在许多其他文本编辑器中仍然存在(好吧,vim是ed的直接后代)。无论如何,g标志代表“全局”,意味着“每一行上你查看到的每一个实例”。范围3,5查看第3-5行。,5查看StartOfFile-5,3,查看第3行-EndOfFile,,查看StartOfFile-EndOfFile。全局替换命令在每一行上将“false”替换为“true”。然后,输入写入命令w - Ari Sweedler

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