【问题标题】:Replace text between two lines with contents of a file stored in a variable in sed用存储在 sed 变量中的文件内容替换两行之间的文本
【发布时间】:2017-10-04 22:02:37
【问题描述】:

假设我有一个名为 original.txt 的文件,其中包含以下内容:

红色
蓝色

食物

灰色
白色

我还有一个名为 new.txt 的文件,其中包含以下内容:

绿色
黑色
黄色
紫色

现在我想编写一个脚本,用new.txt 的内容替换original.txtbluegray 之间的行,所以它给了我这个结果:

红色
蓝色
绿色
黑色
黄色
紫色
灰色
白色

我为此编写了这段代码(新文件的名称并不总是相同,因此它存储在一个变量中):

newtext="new.txt"

sed -i "/blue/,/gray/{
    r $newtext
    d
}" original.txt

但是,当运行它时,我得到了这个废话:

red
green
black
yellow
purplegreen
black
yellow
purplegreen
black
yellow
purplegreen
black
yellow
purplegreen
black
yellow
purplewhite

我做错了什么?

【问题讨论】:

  • 您只想插入第二个文件中的行一次,而不是每次遇到范围内的行时。

标签: linux bash sed


【解决方案1】:

这可能对你有用(GNU sed):

sed '/blue/,/gray/!b;//!d;/blue/r file2' file1

对于bluegray 之间的行范围,删除与该范围内的第一行或最后一行不匹配的行,并读取要插入到该范围最后一行之前的行。

编辑:

第一个 sed 命令/blue/,/grey/!b匹配一系列行,即从包含blue 的行到包含gray 的行之间的行。 !b 部分表示如果行不在该范围内,则退出 sed 命令,即不对这些行进行任何进一步的 sed 处理,只需正常打印即可。

后面的 sed 命令只会影响bluegray 之间的行。

第二条命令//!d 的意思是:删除那些不匹配范围的开始/结束的行,即bluegray// 使用前一个 /.../ 命令中的正则表达式。注:删除命令会终止该行的任何进一步 sed 处理。

后面的 sed 命令只会影响包含 bluegray 的行。

第三条命令匹配包含blue 的行并从file2 中读取行。

注意包含bluegrey 的行由sed 自然处理并在下一行被读入模式空间之前打印,bluegray 之间的行也是如此。

另一种选择:

sed '/blue/,/gray/!b;//!d;/gray/e cat file2' file1

还有一个:

sed -ne '/blue/{p;r file2' -e ':a;n;/gray/!ba};p' file1

【讨论】:

  • 谢谢!它工作得很好,我只需要添加 //a 来追加一个新行,因为 purplegray 正在合并。你介意解释一下每个命令的作用吗?另外,有没有办法避免在说明中写两次blue
  • 如果您可以添加这些sed 命令的工作方式,那确实很有帮助。
  • 也想知道命令//是什么//!d,有人能解释一下吗?
  • 也可以使用sed -e '/blue/r file2' -e '/blue/,/gray/{//!d} file1'...但是,这两种解决方案都没有严格检查bluegray行是否存在...如果没有gray,它将删除blue 之后的所有行等等
  • @CWLiu 请参阅stackoverflow.com/a/38978201/4082052 和评论部分
【解决方案2】:

sed 用于 s/old/new/,仅此而已。对于其他任何你应该使用 awk 的东西:

$ awk 'NR==FNR{new = new $0 ORS; next} /gray/{f=0} !f{print} /blue/{printf "%s",new; f=1}' new.txt original.txt
red
blue
green
black
yellow
purple
gray
white

以上内容适用于任何 UNIX 机器上任何 shell 中的任何 awk,并且不需要重复任何开始/结束正则表达式。非常重要的是,它也可以轻松地建立在现在或将来做任何你想做的事情!例如,能够将开始和结束的正则表达式指定为参数:

$ awk -v beg='blue' -v end='gray' 'NR==FNR{new = new $0 ORS; next} $0~end{f=0} !f{print} $0~beg{printf "%s",new; f=1}' new.txt original.txt

ad 然后你可以改变它们:

$ awk -v beg='water' -v end='tree' 'NR==FNR{new = new $0 ORS; next} $0~end{f=0} !f{print} $0~beg{printf "%s",new; f=1}' new.txt original.txt
red
blue
water
green
black
yellow
purple
tree
gray
white

您可能想做的只是使用相同的结构进行简单的重新排列,例如不打印开始行:

$ awk -v beg='blue' -v end='gray' 'NR==FNR{new = new $0 ORS; next} $0~end{f=0} $0~beg{printf "%s",new; f=1} !f{print}' new.txt original.txt
red
green
black
yellow
purple
gray
white

以及类似的调整以不打印结束行或不打印两者或做任何其他事情......

【讨论】:

  • 实际上并没有在替换之前检查开始和结束正则表达式是否匹配......但OP没有提到作为要求,所以我想这不是问题......
  • 感谢您的回答。出于舒适的原因,我使用了 sed,因为它是我一直使用的,并且语法更短更简洁;我也需要它来做一个私人项目,所以我真的不需要便携性;而且我确信使用 sed 可以完成这项任务,所以我主要想知道我的代码中的错误在哪里。但我给你理由,我应该开始在这些情况下使用 awk,因为依赖标准和便携式解决方案总是更好,我再次感谢你向我解释如何。 :)
  • cleaner syntax???你一定是在开玩笑。我已经使用 sed 40 年了,通常无法判断给定脚本是要读取文件还是要召唤神 cthulu。我想都是我们自己的.. 是的,你真的应该学习 awk - 你会惊讶于一切变得多么简单和一致。
【解决方案3】:

我看到您请求 sed 帮助,但 ex 以更直接的方式处理此用例。 这可能对你有用(在 shell 脚本中):

ex original.txt << EOF_EX
/blue/+1,/gray/-1 d
r new.txt
w edited.txt
q!
EOF_EX

上面打开 original.txt 进行编辑,删除从前一蓝到前一灰的行范围,读取文件 new.txt,并写出文件edited.txt。

结果:

$ cat edited.txt
red
blue
gray
green
black
yellow
purple
white

或者,您可以将“wedited.txt”替换为“wq”,它会将更改保存在 original.txt 文件中。

另外,请注意范围中的 +1 和 -1 语法,这非常方便。 例如,如果您还想删除范围边界模式“蓝色”和“灰色”,则可以删除 +1 和 -1,这意味着删除范围,包括范围边界模式。

【讨论】:

  • 我将如何使用文本(如果可能,使用新行)而不是从像 r new.txt 这样的文件中读取?
猜你喜欢
  • 2012-09-10
  • 1970-01-01
  • 1970-01-01
  • 2017-12-20
  • 2017-12-03
  • 2020-10-31
  • 2018-10-09
  • 1970-01-01
  • 2019-01-19
相关资源
最近更新 更多