如何将 shell 命令嵌入到 sed 表达式中?

如何将 shell 命令嵌入到 sed 表达式中?

我有一个具有以下格式的文本文件:

keyword value
keyword value
...

其中关键字是单个单词,值是行尾之前的所有内容。我想从 shell 脚本中读取文件,方式是使值(而不是关键字)经历 shell 扩展。

使用 sed 可以轻松匹配关键字和值部分

input='
keyword value value
keyword "value  value"
keyword `uname`
'

echo "$input"|sed -e 's/^\([^[:space:]]*\)[[:space:]]\(.*\)$/k=<\1> v=<\2>/'

产生

k=<keyword> v=<value value>
k=<keyword> v=<"value  value">
k=<keyword> v=<`uname`>

但问题是如何将 shell 命令嵌入到 sed 表达式的替换部分中。在这种情况下,我希望替换为\1 `echo \2`.

答案1

有了 GNU,sed你可以使用以下命令:

sed -nr 's/([^ ]+) (.*)/echo "\1" \2\n/ep' input

哪个输出:

keyword value value
keyword value  value
keyword Linux

与您的输入数据。

解释:

sed 命令使用该-n选项抑制常规输出。-r传递以使用扩展正则表达式,这可以节省我们对模式中特殊字符的一些转义,但这不是必需的。

s命令用于将输入行转入命令中:

echo "\1" \2

关键字被引用,而值则未被引用。我将选项e(GNU 特定的)传递给s命令,它告诉 sed 将替换结果作为 shell 命令执行,并将其结果读入模式缓冲区(甚至是多行)。使用pafter(!)选项e可以sed在执行命令后打印模式缓冲区。

答案2

标准 sed 无法调用 shell (GNU sed 有一个扩展可以做到这一点,如果您只关心非嵌入式 Linux),因此您必须在 sed 之外进行一些处理。有多种解决方案;都需要仔细引用。

目前尚不清楚您希望如何扩展这些值。例如,如果一行是

foo hello; echo $(true)  3

输出应该是以下哪一个?

k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo 3>
k=<foo> value=<foo hello
  3>

我将在下面讨论几种可能性。

纯贝壳

您可以让 shell 逐行读取输入并处理它。这是最简单的解决方案,对于短文件来说也是最快的。这是最接近您的要求“ echo \2”:

while read -r keyword value; do
  echo "k=<$keyword> v=<$(eval echo "$value")>"
done

read -r keyword value设置$keyword为该行的第一个空格分隔的单词,以及$value该行的其余部分减去尾随空格。

如果您想扩展变量引用,但不执行命令替换之外的命令,请$value放入这里的文档。我怀疑这就是您真正想要的。

while read -r keyword value; do
  echo "k=<$keyword> v=<$(cat <<EOF
$value
EOF
)>"
done

sed 通过管道传输到 shell

您可以将输入转换为 shell 脚本并对其进行评估。 Sed 可以胜任这项任务,尽管这并不容易。满足您的“ echo \2”要求(请注意,我们需要转义关键字中的单引号):

sed  -e 's/^ *//' -e 'h' \
     -e 's/[^ ]*  *//' -e 'x' \
     -e 's/ .*//' -e "s/'/'\\\\''/g" -e "s/^/echo 'k=</" \
     -e 'G' -e "s/\n/>' v=\\</" -e 's/$/\\>/' | sh

对于此处文档,我们仍然需要转义关键字(但有所不同)。

{
  echo 'cat <<EOF'
  sed -e 's/^ */k=</' -e 'h' \
      -e 's/[^ ]*  *//' -e 'x' -e 's/ .*//' -e 's/[\$`]/\\&/g' \
      -e 'G' -e "s/\n/> v=</" -e 's/$/>/'
  echo 'EOF'
 } | sh

如果您有大量数据,这是最快的方法:它不会为每一行启动单独的进程。

awk

我们在 sed 中使用的技术与 awk 中使用的技术相同。生成的程序明显更具可读性。与“”一起echo \2

awk '
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\047/, "\047\\\047\047", $1);
      print "echo \047k=<" kw ">\047 v=\\<" $0 "\\>";
  }' | sh

使用此处的文档:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh

答案3

你可以尝试这个方法:

input='
keyword value value
keyword "value  value"
keyword `uname`
'

process() {
  k=$1; shift; v="$*"
  printf '%s\n' "k=<$k> v=<$v>"
}

eval "$(printf '%s\n' "$input" | sed -n 's/./process &/p')"

(如果我明白你的意图的话)。即在每个非空行的开头插入“process”,使其成为如下脚本:

process keyword value value
process keyword "value  value"
process keyword `uname`

待评估 ( eval) 其中过程是一个打印预期消息的函数。

答案4

ONLY KISS 短纯 SED

我会做的

echo "ls_me" | sed -e "s/\(ls\)_me/\1/e" -e "s/to be/continued/g;"

并且它起作用了。

相关内容