在Solaris中,“ sed -i”命令是否可以替代?


11

我的项目中有一项要求,就是将文件中的某些现有文本替换为foo其他文本,例如fooofoo

abc.txt
name
foo
foo1

所以我尝试了:

sed -i "s/foo/fooofoo/g" abc.txt

但是我得到这个错误:

sed:非法选项- i

我在手册中发现必须使用:

sed -i\ "s/foo/fooofoo/g" abc.txt

但是,这也不起作用。

我发现,替代品perlawk也,但在Solaris中的解决方案sed,将不胜感激。

我正在使用此版本的bash:

GNU bash版本3.2.57(1)-发行版(sparc-sun-solaris2.10)


在Solaris 11和更高版本上,只需使用/usr/gnu/bin/sed即可获得-i支持。
alanc '16

Answers:


15

使用ed。它在大多数平台上都可用,并且可以就地编辑文件。
由于sed基于ed替换模式的语法类似:

ed -s infile <<\IN
,s/old/new/g
w
q
IN

为什么要ed代替ex,只是好奇?);(我知道都是POSIX兼容的,我已经联系到的规格。)
通配符

1
@Wildcard-我可以反问-为什么ex?要回答您的问题:由于我从不使用过,ex 所以我对此并不熟悉。顺便说一句,我已经看到了ed存在但ex不存在的设置(但并非相反)...最值得注意的是我的笔记本电脑:)
don_crissti

好答案。:)我的回答:由于我一直都在使用Vim,因此我对ex命令非常熟悉。vi您可以键入以冒号开头的所有命令都是ex命令。当然,有一些特定于Vim的扩展,但是只有核心的命令集才具有强大而灵活的功能,并且对于脚本编辑来说已经足够了。
2016年

我的Manjaro系统有ex,但现在还没有ed
中旬

8

如果无法安装GNU sed,请使用:

sed "s/foo/fooofoo/g" abc.txt >abc.tmp && mv abc.tmp abc.txt

这使用重定向将sed的输出发送到临时文件。如果sed成功完成,则将abc.txt用临时文件覆盖。

从GNU sed的源代码可以看出,这正是sed -i它的作用。因此,这与几乎一样有效sed -i

如果abc.tmp已经存在一个机会,那么您可能想要使用mktemp或类似的实用程序来生成临时名称的唯一名称。


感谢帮助。但是,solaris中是否有任何选项可以用来更新现有文件而不创建临时文件?
tpsaitwal

10
讨厌破坏它,但sed甚至会创建一个临时文件。git.savannah.gnu.org/cgit/sed.git/tree/sed/sed.c#n84
Jeff Schaller

1
如果您不关心硬链接,则可以执行此操作-请参阅我的示例。仍然有一个临时文件,但是它是隐藏的(未命名)。如果没有临时文件,则需要使用ed,因为它将整个文件读入内存。
Toby Speight

3

如果您想要等效于sed -i.bak,则非常简单。

考虑以下GNU sed脚本:

#!/bin/sh

# Create an input file to demonstrate
trap 'rm -r "$dir"' EXIT
dir=$(mktemp -d)
grep -v '[[:upper:][:punct:]]' /usr/share/dict/words | head >"$dir/foo"

# sed program - removes 'aardvark' and 'aardvarks'
script='/aard/d'

##########
# What we want to do
sed -i.bak -e "$script" "$dir"
##########

# Prove that it worked
ls "$dir"
cat "$dir/foo"

我们可以简单地用

cp "$dir/foo" "$dir/foo.bak" && sed -e "$script" "$dir/foo.bak" >"$dir/foo"

这会将现有文件移动为备份文件,并写入一个新文件。

如果我们想要相当于

sed -i -e "$script" "$dir"  # no backup

然后稍微复杂一点。我们可以打开文件以作为标准输入读取,然后取消链接,然后指示sed的输出替换它:

( cp "$dir/foo" "$dir/foo.bak"; exec <"$dir/foo.bak"; rm "$dir/foo.bak"; exec sed -e "$script" >"$dir/foo" )

我们在子外壳中执行此操作,因此此后仍可以使用原始的stdin。可以在没有子外壳的情况下切换输入并切换回去,但是这种方式对我来说似乎更清晰。

请注意,我们要谨慎复制,而不是先创建新foo文件-如果文件的名称不只一个(即具有硬链接),并且您希望确保不要断开链接,则此操作非常重要。 。


3

首先,您应该知道i\您所指的命令是用于插入一行文本,而不是用于编辑后的文本保存回文件中。没有POSIX指定的方式使用sed了点。

可以做的是使用ex,这由POSIX规定

printf '%s\n' '%s/find/replace/g' 'x' | ex file.txt

x命令用于“保存并退出”。您也可以使用,wqx较短。

%替换命令开头的符号表示:“将此命令应用于缓冲区中的每一行。” 您也可以使用1,$s/find/replace/g


之间的一个主要区别ex,并sedsed一个编辑器。它仅按顺序逐行运行。 ex灵活得多,实际上,您可以直接在中进行交互式文本文件编辑ex。它是的直接前身vi


1

正在使用sed且没有可见的临时文件:

您可以避免创建单独的可见 “临时文件”:

exec 3<abc.txt
rm abc.txt
sed 's/foo/fooofoo/' <&3 >abc.txt
exec 3<&-

说明

类似于Unix的系统实际上不会从磁盘上删除文件的内容,直到它们在文件系统中取消链接并且在任何进程中都未打开。因此,您可以exec 3<在外壳中打开文件以读取文件描述符3(rm文件(将其与文件系统断开链接)),然后sed使用文件描述符3作为其输入进行调用。

请注意,这与此非常不同:

# Does not work.
sed 's/foo/fooofoo/' <abc.txt >abc.txt

区别在于,在一个命令中执行此操作时,shell会打开同一文件以供读取和写入,并提供截断该文件的选项-由于该文件仍是同一文件,因此您丢失了内容。但是,如果先打开它进行读取,然后rm再打开相同的路径名进行写入,则实际上是在相同的路径名下创建一个新文件(但是在新的inode和磁盘位置,因为原始文件仍处于打开状态):内容仍然可用。

然后,一旦完成,就可以关闭先前打开的文件描述符(这是exec 3<&-特殊语法所做的工作),它释放原始文件,以便操作系统可以删除(标记为未使用)其磁盘空间。

注意事项

关于此解决方案,需要牢记以下几点:

  1. 您只能通过目录“遍历”-shell无法通过便携式方式“追溯”文件描述符-因此,一旦程序读取了某些内容,其他程序将仅看到文件的其余部分。并且sed将读取整个文件。

  2. 如果您的shell / script / sed在执行之前被杀死,则极有可能丢失原始文件。


需要明确的是,@ TobySpeight已经在回答的结尾处发布了此解决方案的示例,但我认为可以使用更深入的阐述。
mtraceur '16

致低级投票者:虽然我确定只是留下低级投票权会使您感觉良好,但如果您能通过提供有关此答案的问题,如何改善等方面的反馈来做出更多贡献,我将不胜感激。
mtraceur

也许您可以使用perl -i
c4f4t0r 16-10-21

@ c4f4t0r:对不起,您的答复很晚:是的,这perl -i是另一个不错的选择。这个问题专门请了Solaris兼容的解决方案sed,而相比之下,同解决方案perlawk他们已经提到发现。另外,我的答案主要是要添加一些我认为有用的知识,这些知识在其他任何答案中都不为所知。
mtraceur
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.