sed 编辑文件到位

Posted

技术标签:

【中文标题】sed 编辑文件到位【英文标题】:sed edit file in place 【发布时间】:2012-09-23 15:37:25 【问题描述】:

我正在尝试找出是否可以在单个 sed 命令中编辑文件,而无需手动将编辑后的内容流式传输到新文件中,然后重命名新文件改成原来的文件名。我尝试了-i 选项,但我的Solaris 系统说-i 是非法选项。有什么不同的方法吗?

【问题讨论】:

-i 是 gnu sed 中的一个选项,但不在标准 sed 中。但是,它将内容流式传输到一个新文件,然后重命名该文件,因此它不是您想要的。 实际上,这正是我想要的,我只是不想被暴露于必须执行将新文件重命名为原始名称的平凡任务 那你需要重述问题。 @amphibient:你介意在你的问题标题前加上“Solaris”这个词吗?你的问题的价值正在丢失。请参阅我的答案下方的 cmets。谢谢。 @Steve:我再次从标题中删除了 Solaris 前缀,因为这绝不是 Solaris 独有的。 【参考方案1】:

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

例子:

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

【讨论】:

至少它对我有用,所以我不必 那么你可以尝试在你的系统上编译 GNU sed。 示例:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file> 这比 perl 解决方案要好。不知何故,它没有创建任何 .swap 文件....谢谢! @maths:确实如此,但可能在其他地方或更短的时间?【参考方案2】:

支持-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

应该足以满足大多数用途。

【讨论】:

例如,您将如何命名此脚本以及如何调用它? 我将其称为 inline 并按上述方式调用它:inline inputfile sed 's/foo/bar/g' 哇,ed 只回答真正到位的答案。我第一次需要ed。谢谢你。一点优化是使用echo -e ",s/foo/bar/g\012 w"echo $',s/foo/bar/g\012 w',其中shell 允许它避免额外的tr 调用。 ed 并没有真正就地进行修改。从strace 输出,GNU ed 创建一个临时文件,将更改写入临时文件,然后重写整个原始文件,保留硬链接。从truss 输出来看,Solaris 11 ed 也使用临时文件,但在使用w 命令保存时将临时文件重命名为原始文件名,从而破坏所有硬链接。 @AndrewHenle 这令人沮丧。当前 macos 附带的 ed(Mojave,无论营销术语是什么意思)仍然保留硬链接。 YMMV【参考方案3】:

您没有指定您使用的 shell,但使用 zsh 您可以使用 =( ) 构造来实现此目的。大致如下:

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

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

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

它不仅会创建一个临时文件,还会破坏硬链接(例如,它不会更改文件的内容,而是将文件替换为同名的新文件。)有时这是期望的行为,有时它是可以接受的,但是当一个文件有多个链接时,它几乎总是错误的行为。 @DwightSpencer:我相信所有没有 Perl 的系统都会被破坏。当一个人想要提升权限并且必须使用sudo 时,单个命令胜过一系列命令。在我看来,问题在于如何避免手动创建和重命名临时文件,而无需安装最新的 GNU 或 BSD sed。只需使用 Perl。 @PetrPeller:真的吗?如果您仔细阅读该问题,您会明白 OP 试图避免类似 sed 's/foo/bar/g' file.txt &gt; file.tmp &amp;&amp; mv file.tmp file.txt 的内容。仅仅因为就地编辑使用临时文件进行重命名,并不意味着他/她必须在选项不可用时执行手动重命名。还有其他工具可以做他/她想做的事,而 Perl 是显而易见的选择。这怎么不是对原始问题的回答? @a_arias:你是对的;他做到了。但是他无法使用 Solaris sed 实现他想要的。这个问题实际上是一个非常好的问题,但它的价值已经被那些无法阅读手册页或懒得实际上在 SO 上阅读问题的人削弱了。 @EdwardGarson:IIRC,Solaris 附带 Perl,但不附带 Python 或 Ruby。就像我之前说过的,OP 无法使用 Solaris sed 达到预期的结果。【参考方案5】:

有一点需要注意,sed 不能自己编写文件,因为 sed 的唯一目的是充当“流”(即标准输入、标准输出、标准错误和其他 &gt;&amp;n 缓冲区的管道)的编辑器、插座等)。考虑到这一点,您可以使用另一个命令tee 将输出写回文件。另一种选择是创建一个补丁,将内容通过管道传输到diff

三通方法

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

补丁方法

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

更新:

另外,请注意,patch 将使文件从 diff 输出的第 1 行更改:

Patch 不需要知道要访问哪个文件,因为它可以在 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

【讨论】:

/dev/stdin 2014-03-151 月 7 日回答 - 你是时间旅行者吗? 在 Windows 上,使用 msysgit,/dev/stdin 不存在,因此您必须将 /dev/stdin 替换为 '-',一个不带引号的连字符,因此以下命令应该可以工作:@ 987654331@和$ sed 's/oo/u/' fubar | diff -p fubar - | patch @AdrianFrühwirth 我确实在某个地方有一个蓝色的派出所,出于某种原因,他们确实称我为工作中的医生。但老实说,看起来当时系统的时钟漂移真的很糟糕。 这些解决方案在我看来依赖于特定的缓冲行为。我怀疑对于更大的文件,这些文件可能会在 sed 读取时中断,文件可能会在下面通过 patch 命令开始更改,或者更糟糕的是 tee 命令会截断文件。实际上我不确定patch 是否也不会被截断。我认为将输出保存到另一个文件,然后将cat-ing 保存到原始文件会更安全。 来自@william-pursell 的真实就地回答【参考方案6】:

以下在我的 Mac 上运行良好

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

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

要在不备份的情况下编辑内联,请使用以下命令

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

【讨论】:

有什么办法可以防止保留备份文件? @PetrPeller,您可以将上述命令用于相同的... sed -i '' 's/foo/bar/g' 示例 我喜欢这个答案“sed -i.bak”,因为它适用于 OS X 和 GNU【参考方案7】:

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

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

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

【讨论】:

这是 OS X 的另一个奇怪之处。幸运的是,brew install gnu-sedPATH 将允许您使用 GNU 版本的 sed。感谢提及;一个明确的挠头。【参考方案8】:

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

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /'  \;

【讨论】:

【参考方案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 与旧内容。

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

【讨论】:

OP 特别提到他的平台不支持这个(流行但)非标准选项。【参考方案10】:
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

应该保留所有硬链接,因为输出会被引导回覆盖原始文件的内容,并且不需要特殊版本的 sed。

【讨论】:

【参考方案11】:

如果要替换相同数量的字符并仔细阅读“In-place” editing of files...

也可以使用重定向操作符&lt;&gt;打开文件进行读写:

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 Reference Manual → 3.6.10 Opening File Descriptors for Reading and Writing:

重定向操作符

[n]<>word

导致打开文件名是单词的扩展 在文件描述符 n 或文件描述符 0 上读写 如果未指定 n。如果文件不存在,则创建它。

【讨论】:

这已被文件损坏。我不确定其背后的原因。 paste.fedoraproject.org/462017 参见第 [43-44]、[88-89]、[135-136] 行 这篇博文解释了为什么不应该使用这个答案。 backreference.org/2011/01/29/in-place-editing-of-files @NehalJWani 我会仔细阅读这篇文章,感谢您的提醒!我在答案中没有提到的是,如果它改变相同数量的字符,这将有效。 @NehalJWani 话虽这么说,如果我的回答对您的文件造成损害,我很抱歉。阅读您提供的链接后,我将对其进行更正(或在必要时将其删除)。【参考方案12】:

就像 Moneypenny 在 Skyfall 中所说的那样:“有时老办法是最好的。” Kincade 后来说了类似的话。

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

编辑愉快。 添加了 '\nw\n' 来写入文件。对延迟回复请求表示歉意。

【讨论】:

你能解释一下这是做什么的吗? 它调用 ed(1),一个将一些字符写入标准输入的文本编辑器。作为一个 30 岁以下的人,我认为我可以说 ed(1) 之于恐龙就像恐龙之于我们一样。无论如何,jlettvin 没有打开 ed 并输入这些内容,而是使用 | 将字符输入管道。 @MattMontag 如果您要询问命令的作用,其中有两个,以\n 结尾。 [range]s/// 是替代命令的形式 - 在许多其他文本编辑器中仍然可以看到一个范例(好吧,vim,ed 的直接后代)无论如何,g flag 代表“全局”,意思是“您查看的每一行上的每个实例”。范围3,5 查看第 3-5 行。 ,5 查看 StartOfFile-5,3, 查看第 3-EndOfFile 行,, 查看 StartOfFile-EndOfFile。每行上的全局替换命令,将“false”替换为“true”。然后,输入写命令w【参考方案13】:

你可以使用 vi

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

【讨论】:

【参考方案14】:

要在 Mac 上解决这个问题,我必须在 this 之后向 core-utils 添加一些 unix 函数。

brew install grep
==> Caveats
All commands have been installed with the prefix "g".
If you need to use these commands with their normal names, you
can add a "gnubin" directory to your PATH from your bashrc like:
  PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"

使用gsed 而不是sed 调用。 mac 默认不喜欢grep -rl 显示带有./ 前缀的文件名。

~/my-dir/configs$ grep -rl Promise . | xargs sed -i 's/Promise/Bluebirg/g'

sed: 1: "./test_config.js": invalid command code .

对于名称中带有空格的文件,我还必须使用 xargs -I sed -i 's/Promise/Bluebirg/g'

【讨论】:

【参考方案15】:

如果你想替换包含'/'的字符串,你可以使用'?'。即将所有 *.py 文件的 '/usr/local/bin/python' 替换为 '/usr/bin/python3'。

找到 . -name \*py -exec sed -i 's?/usr/local/bin/python?/usr/bin/python3?g' \;

【讨论】:

以上是关于sed 编辑文件到位的主要内容,如果未能解决你的问题,请参考以下文章

Sed编辑器

sed命令基本用法

无法使用 SED 快速编辑文件

sed命令

Shell编辑器之sed编辑器

sed编辑器