ChatGPT解决这个技术问题 Extra ChatGPT

sed 编辑文件到位

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

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

c
choroba

-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:确实如此,但可能在其他地方或更短的时间?
S
Steve

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 > file.tmp && mv file.tmp file.txt 的内容。仅仅因为就地编辑使用临时文件进行重命名,并不意味着他/她必须在选项不可用时执行手动重命名。还有其他工具可以做他/她想做的事,而 Perl 是显而易见的选择。这怎么不是对原始问题的回答?
@a_arias:你是对的;他做到了。但他无法使用 Solaris sed 实现他想要的。这个问题实际上是一个非常好的问题,但它的价值已经被那些无法阅读手册页或懒得实际上阅读 SO 上的问题的人所削弱。
@EdwardGarson:IIRC,Solaris 附带 Perl,但不附带 Python 或 Ruby。就像我之前说过的,OP 无法使用 Solaris sed 达到预期的结果。
C
Community

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

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

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


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

以下在我的 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
j
jww

需要注意的一点是,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 行更改:

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 替换为 '-',一个不带引号的连字符,因此以下命令应该可以工作:$ sed 's/oo/u/' fubar | diff -p fubar -$ sed 's/oo/u/' fubar | diff -p fubar - | patch
@AdrianFrühwirth 我确实在某个地方有一个蓝色的警察局,出于某种原因,他们确实称我为工作中的医生。但老实说,看起来当时系统上的时钟漂移真的很糟糕。
这些解决方案在我看来依赖于特定的缓冲行为。我怀疑对于更大的文件,这些文件可能会在 sed 读取时中断,文件可能会在下面通过 patch 命令开始更改,或者更糟糕的是 tee 命令会截断文件。实际上我不确定 patch 是否也不会截断。我认为将输出保存到另一个文件然后 cat-ing 到原始文件会更安全。
来自@william-pursell 的真实就地回答
W
William Pursell

支持 -i 选项以就地编辑文件的 sed 版本写入临时文件,然后重命名该文件。

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

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

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

即使您没有 -i 支持 sed,您也可以轻松编写脚本来为您完成这项工作。您可以使用 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(Mojave,无论营销术语是什么意思)附带的 ed 仍然保留硬链接。 YMMV
m
mench

你可以使用 vi

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

S
Sergio A.

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 特别提到他的平台不支持这个(流行但)非标准选项。
f
fedorqui

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

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

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 会导致打开名称为 word 扩展的文件,以便在文件描述符 n 上读取和写入,如果未指定 n,则在文件描述符 0 上打开。如果文件不存在,则创建它。


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

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

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

快乐编辑到位。添加了 '\nw\n' 来写入文件。为延迟回复请求道歉。


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

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

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

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


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

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


R
Robert

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

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

C
Clark Benham

为了在 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' {}


M
Mark Yuan

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

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