【问题标题】:How can I use command in sed replace with groups capturing?如何在 sed 中使用命令替换组捕获?
【发布时间】:2018-07-21 18:48:07
【问题描述】:

我在内容中有一个这样的文件: #INCLUDE<~/boulou/billy.txt>

从 bash/sed 脚本/命令,我想用文件 ~/boulou/billy.txt 的内容替换这一行

这是我当前可以找到文件路径的命令:

sed -E "s/#INCLUDE\<(.*)\>/\1/g" test.sh

这显示了文件路径,但是当我想用这个获取内容时:

sed -E "s/#INCLUDE\<(.*)\>/$(cat \1)/g" test.sh

我收到错误“cat: 1: No such file or directory”

如果您想了解更多信息,请告诉我。

【问题讨论】:

  • 请在您的问题中添加示例输入和该示例输入所需的输出。
  • 嗯,我想用文件的内容(例如~/boulou/billy.txt)替换整行(#INCLUDE)跨度>
  • @ChristopheCoquelet 这不是一个例子,这就是你的问题所说的。

标签: bash sed


【解决方案1】:

您的代码不起作用,因为它首先运行 cat \1(与 cat 1 相同),将输出插入 sed 命令,然后运行 ​​sed -E "s/...//g" test.sh(空替换,因为 cat 1 没有输出任何内容标准输出)。

这是因为 shell 先处理$( ... ),然后运行生成的命令行。

对于您要执行的操作,您必须从搜索/替换命令中读取文件,而不是预先读取。

我不知道如何用 sed 做到这一点,所以我只是使用 Perl:

perl -pe 's{#INCLUDE<([^<>]*)>}{ open my $fh, "<", $1 or die "$1: $!"; local $/; readline $fh }eg' test.sh

但是,这不适用于您的示例,因为~/boulou/billy.txt 不存在(您的当前工​​作目录中很可能没有一个字面上称为~ 的目录)。为了解决这个问题(并大大简化代码),我会使用:

perl -MPath::Tiny -pe 's{#INCLUDE<([^<>]*)>}{ path($1)->slurp }eg' test.sh

但是,这需要 Path::Tiny 模块,它不是核心 perl 发行版的一部分。

可以手动扩展~,但这会使代码更加混乱(此时我会考虑将其放入单独的脚本文件中):

perl -pe 's{#INCLUDE<([^<>]*)>}{ my $p = $1; $p =~ s{^~/}{$ENV{HOME}/}; open my $fh, "<", $p or die "$p: $!"; local $/; readline $fh }eg' test.sh

(免责声明:有点 hacky,不处理 ~user 表示法,只用 HOME 环境变量的内容替换前导 ~/。)

【讨论】:

  • 我会说 melpomene 的回答比我的好。它实际上有一个解决方案,而我只是将您指向 awk 和 m4。我会说 perl 比 awk 更有用。如果你不得不花时间学习另一种语言,我会选择 perl。考虑 perl 一个 awk 的超集(它有一个 'awk 模式')。
  • 非常感谢您的回答和解释! :)
【解决方案2】:

你可以试试这样的:

$ sed -r 's/#INCLUDE<(.*)>/printf "%b" "$(cat \1)"/e' test.sh

它应该工作得非常顺利:)

【讨论】:

  • e 标志似乎是 GNU 扩展,所以这需要 GNU sed。
  • 简单,我喜欢! :D
  • 确实,e 标志仅在 GNU sed 中可用...而不是 BSD sed。嗯
  • 完全错误,没有人测试过吗?这将从被包含的文件中删除所有空格和换行符,并且可能会灾难性地尝试执行 test.sh 中包含 INCLUDE 语句的整个行。
【解决方案3】:

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

sed 's/^#INCLUDE<\(.*\)>$/cat \1/e' file

这会计算替换命令右侧的表达式。在这种情况下,它将用&lt;&gt; 之间命名的文件的内容替换匹配的行。

【讨论】:

  • 比当前接受的答案更接近正确,只要 INCLUDE 在它自己的行上并且要包含的文件名仅包含字母数字字符(例如,不是空格或 ;或在执行替换文本时执行 shell 会解释的任何其他内容)。如果 OP 希望扩展文件路径中的 shell 变量,cat '\''\1'\'' 会更健壮或cat "\1"
【解决方案4】:

这里的问题是 shell 很早就替换了 $(cat \1) 。也就是说,即使在执行 sed 命令并传递该参数之前,shell 也会进行插值。

如果您只是在做#INCLUDE 替换,您可以考虑使用 m4。如果您需要自己编写,Awk 可能是解决此问题的合理方法。

【讨论】:

    【解决方案5】:

    这将与在任何 UNIX 系统上的任何 shell 中运行任何 awk 的任何输入文件内容一起稳健地工作:

    输入:

    $ cat file1
    before
    foo [#INCLUDE<file2>] bar
    after
    
    $ cat file2
    stuff
    &       nonsense
        here
    

    脚本:

    $ cat tst.awk
    match($0,/#INCLUDE<[^<>]*>/) {
        file = substr($0,RSTART+9,RLENGTH-10)
        rep = sep = ""
        while ( (getline line < file) > 0 ) {
            rep = rep sep line
            sep = ORS
        }
        $0 = substr($0,1,RSTART-1) rep substr($0,RSTART+RLENGTH)
    }
    { print }
    

    输出:

    $ awk -f tst.awk file1
    before
    foo [stuff
    &       nonsense
        here] bar
    after
    

    the currently accepted answer 将使用相同的输入执行以下操作:

    $ sed -r 's/#INCLUDE<(.*)>/printf "%s" $(cat \1)/e' file1
    before
    /bin/sh: foo: command not found
    
    after
    

    如果 INCLUDE 在它自己的一行上,它会执行以下操作:

    $ cat file1
    before
    #INCLUDE<file2>
    after
    
    $ sed -r 's/#INCLUDE<(.*)>/printf "%s" $(cat \1)/e' file1
    before
    stuff&nonsensehere
    after
    

    如果 INCLUDE 单独一行,这个更简单的脚本会更接近正确:

    $ sed -r 's/#INCLUDE<(.*)>/cat \1/e' file1
    before
    stuff
    &       nonsense
        here
    after
    

    但如果任何其他文本在行上也会失败,因为它仍然会尝试执行它!

    【讨论】:

      猜你喜欢
      • 2018-07-23
      • 2017-05-02
      • 2022-01-10
      • 2018-04-27
      • 1970-01-01
      • 2017-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多