sed编辑文件到位


298

我试图找出是否有可能在单个sed命令中编辑文件,而无需手动将编辑后的内容流式传输到新文件中,然后将新文件重命名为原始文件名。我尝试了该-i选项,但是我的Solaris系统说这-i是非法的选项。有其他方法吗?


7
-i是gnu sed中的选项,但不是标准sed中的选项。但是,它将内容流式传输到新文件,然后重命名该文件,因此它不是您想要的。
William Pursell

2
实际上,这就是我想要的,我只是不想暴露于必须执行的将新文件重命名为原始名称的
繁琐

3
然后,您需要重述该问题。
William Pursell

3
@amphibient:您是否愿意在问题的标题前加上“ Solaris”一词?您的问题的价值正在丢失。请查看我的答案下方的评论。谢谢。
史蒂夫

1
@Steve:我再次从标题中删除了Solaris前缀,因为这绝不是Solaris专有的。
三胞胎

Answers:


475

-i选项将编辑后的内容流式传输到一个新文件中,然后无论如何都要在幕后对其进行重命名。

例:

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

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

macOS上


3
至少它对我
有用,

2
然后,您可以尝试在系统上编译GNU sed。
choroba 2012年

3
例如:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin 2014年

2
这比perl解决方案好。不知何故,它不会创建任何.swap文件....谢谢!
数学2014年

3
@maths:可以,但是也许在其他地方或更短的时间?
choroba

72

sed无法编辑文件的系统上,我认为更好的解决方案是使用perl

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

尽管这确实创建了一个临时文件,但是由于提供了空的后缀/扩展名,因此它将替换原始文件。


14
它不仅会创建一个临时文件,还会断开硬链接(例如,代替更改文件的内容,而是使用相同名称的新文件替换该文件。)有时这是所需的行为,有时是可以接受,但是当有多个链接指向文件时,几乎总是错误的行为。
威廉·珀塞尔

3
@DwightSpencer:我相信所有没有Perl的系统都坏了。当一个命令要提升权限并必须使用时,单个命令比一连串命令要重要sudo。在我看来,问题在于如何避免在无需安装最新GNU或BSD的情况下手动创建和重命名临时文件sed。只需使用Perl。
2014年

4
@PetrPeller:真的吗?如果仔细阅读问题,您将理解OP正在尝试避免类似的情况sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt。仅仅因为就地编辑会使用临时文件进行重命名,并不意味着他/她在选项不可用时必须执行手动重命名。还有其他工具可以满足他/她的需求,Perl是显而易见的选择。这不是原始问题的答案吗?
2014年

2
@a_arias:您是对的;他做到了。但是他不能使用Solaris实现他想要的sed。这个问题实际上是一个很好的问题,但由于无法阅读手册页或不愿意在此处实际阅读问题的人而降低了其价值。
史蒂夫

4
@EdwardGarson:IIRC,Solaris附带Perl,但不附带Python或Ruby。就像我之前说过的那样,OP无法使用Solaris sed实现所需的结果。
史蒂夫

56

请注意,在OS X上,运行此命令时可能会遇到奇怪的错误,例如“无效的命令代码”或其他奇怪的错误。要解决此问题,请尝试

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

这是因为在OSX版本的上sed,该-i选项需要一个extension参数,因此您的命令实际上被解析为extension参数,而文件路径被解释为命令代码。资料来源:https : //stackoverflow.com/a/19457213


1
这是OS X的另一个怪异之处。幸运的是,带有brew install gnu-sed以及PATH可以使您使用sed的GNU版本。感谢您的提及;明确的头部抓痒器。
道格

23

以下在我的Mac上正常工作

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

我们在示例文件中用bar替换了foo。原始文件的备份将保存在sample.bak中

要编辑内联而不进行备份,请使用以下命令

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

有什么办法可以防止保留备份文件?
Petr Peller

@PetrPeller,您可以对相同的命令使用提到的命令... sed -i'''s / foo / bar / g'示例
minhas23 2014年

18

需要注意的一件事是,sed不能仅凭sed自己写文件就是充当“流”(即stdin,stdout,stderr和其他>&n缓冲区,套接字等的管道)的编辑器。考虑到这一点,您可以使用另一个命令tee将输出写回到文件中。另一种选择是通过将内容传送到中创建补丁diff

三通法

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

补丁方式

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

更新:

另外,请注意,patch将使文件从diff输出的第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

2
/dev/stdin 2014-03-151月7回答 -您是外行吗?
AdrianFrühwirth2014年

在Windows上,/dev/stdin不存在使用msysgit的功能,因此您必须替换/dev/stdin为'-',即没有引号的单个连字符,因此以下命令应该可以使用:$ sed 's/oo/u/' fubar | diff -p fubar -$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden 2014年

@AdrianFrühwirth我的确在附近某处有一个蓝色的警察岗亭,由于某种原因,他们确实在工作中称我为“医生”。但说实话,看起来当时系统上确实存在严重的时钟漂移。
德怀特·斯宾塞

在我看来,这些解决方案依赖于特定的缓冲行为。我怀疑对于较大的文件,当sed读取时这些文件可能会损坏,文件可能会在以下通过patch命令开始更改,甚至更糟的是在将截断文件的tee命令下更改。实际上,我不确定是否patch也不会截断。我认为将输出保存到另一个文件,然后cat-ing成原始文件会更安全。
akostadinov

来自@ william-pursell的真实就位答案– akostadinov
2016年

11

无法执行您想要的操作sed。甚至sed支持该-i选项的版本也可以执行您明确声明的不想要的操作:它们写入一个临时文件,然后重命名该文件。但是也许您可以使用ed。例如,改变所有出现foobar文件中file.txt,你可以这样做:

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

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

但是也许您应该考虑为什么不想使用重命名的临时文件。即使没有-i支持sed,也可以轻松编写脚本来为您完成工作。相反sed -i 's/foo/bar/g' file,您可以这样做inline file sed 's/foo/bar/g'。这样的脚本编写起来很简单。例如:

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

应该适合大多数用途。


1
因此,例如,您如何命名该脚本以及如何命名?
2012年

2
我会inline如上所述调用它并调用它:inline inputfile sed 's/foo/bar/g'
William Pursell,2012年

1
哇,ed只有真正可以就地回答。我第一次需要ed。谢谢。一个小的优化是使用echo -e ",s/foo/bar/g\012 w"echo $',s/foo/bar/g\012 w'shell允许它避免额外的tr调用。
akostadinov

2
ed并没有真正就地进行修改。从strace输出中,GNU ed创建一个临时文件,将更改写入该临时文件,然后重写整个原始文件,并保留硬链接。从truss输出来看,Solaris 11 ed还使用了一个临时文件,但是在使用w命令进行保存后,将其重命名为原始文件名,这会破坏所有硬链接。
安德鲁·亨利

@AndrewHenle令人沮丧。该ed附带当前的MacOS(莫哈韦,不管这营销术语手段)仍保留硬链接。YMMV
William Pursell,

10

你可以用vi

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

9

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保留了旧内容。

如果您不想保留副本,则不要传递扩展参数。


3
OP特别提到他的平台不支持此(受欢迎但非标准)选项。
三胞胎

3

您没有指定要使用的shell,但是使用zsh可以使用该=( )构造来实现。类似于以下内容:

cp =(sed ... file; sync) file

=( )类似于,>( )但创建一个临时文件,该文件在cp终止时会自动删除。


3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

应该保留所有硬链接,因为输出被定向回以覆盖原始文件的内容,并且避免了需要特殊版本的sed。


3

如果要替换相同数量的字符,并在仔细阅读文件的“就地”编辑之后, ...

您还可以使用重定向操作符<>打开文件进行读写:

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打开文件描述符进行读写

重定向运算符

[n]<>word

导致打开名称为单词扩展的文件,以便在文件描述符n或未指定n的文件描述符0上进行读写操作。如果该文件不存在,则会创建它。


这已被文件损坏。我不确定其背后的原因。paste.fedoraproject.org/462017
Nehal J Wani

参见[43-44],[88-89],[135-136]行
Nehal J Wani

2
该博客文章解释了为什么不应该使用此答案。backreference.org/2011/01/29/就地编辑文件
Nehal J Wani

@NehalJWani我会仔细阅读这篇文章,感谢大家的注意!我没有在答案中提及的是,如果更改相同数量的字符,则此方法有效。
fedorqui'SO停止伤害

有人说@NehalJWani,很抱歉,如果我的回答对您的文件造成了损害。阅读您提供的链接后,我将对其进行更正(或在必要时删除),以进行更正。
fedorqui'SO停止伤害

2

就像Moneypenny在Skyfall中说的那样:“有时候旧的方法是最好的。” Kincade稍后也说了类似的话。

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

编辑愉快。添加了“ \ nw \ n”来写入文件。很抱歉延迟回答要求。


您能解释一下这是什么吗?
马特·蒙塔格

1
它调用文本编辑器ed(1),并将某些字符写入stdin。作为30岁以下的人,我认为对恐龙来说ed(1)对我们来说就像恐龙一样。无论如何,jlettvin不用打开ed并在其中输入内容,而是在using中使用字符进行传递|。@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
阿里·斯威德勒

1

很好的例子。我面临的挑战是就地编辑许多文件,并且-i选项似乎是在find命令中使用它的唯一合理的解决方案。这里的脚本在每个文件的第一行前面添加“版本:”:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.