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


16

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

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`


嗯...我不太确定要给出答案,但是使用sed引用的DOUBLE应该让您在表达式内使用shell $(command)或$ variables。
St0rM 2013年

Answers:


18

标准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>

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

纯壳

您可以使外壳逐行读取输入并进行处理。这是最简单的解决方案,对于短文件也是最快的解决方案。这是最符合您要求的“ 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脚本并进行评估。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

在here文档中,我们仍然需要对关键字进行转义(但要有所不同)。

{
  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

我们在awk的sed中使用的相同技术。生成的程序更具可读性。与“ 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

好答案。我将使用纯shell解决方案,因为输入文件的确很小,并且性能不是问题,它也很干净且可读。
欧内斯特AC

有点骇人但很整洁。例如,使用sed调出xxd来解码长十六进制字符串。。。猫FtH.ch13 | sed -r's /(.* text。*:[)([0-9a-fA-F] *)] / \ 1 $(echo \ 2 | xxd -r -p)] /; s / ^( *)$ /回波“\ 1”/ G” |的bash> FtHtext.ch13凡FtH.ch13具有像线“富酒吧六角文本试验:[666f6f0a62617200]”
gaoithe

14

拥有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

关键字get的值不带引号。我将选项e(特定于GNU)传递给s命令,该命令告诉sed作为shell命令执行替换结果,并将其结果读入模式缓冲区(甚至多行)。使用此选项p后(!)e使sed该命令执行后打印模式缓冲区。


您不能同时使用-np选项,即sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input。但是,谢谢你!我不知道该e选项。
Kaushal Modi

@KaushalModi哦,是的,你是对的!关于e选项(由GNU引入),我坐在栅栏上。还在sed吗?:)
hek2mgl

好吧,它为我工作。在RHEL发行版上,默认情况下是我的GNU sed(GNU sed版本4.2.1)。
Kaushal Modi

4

您可以尝试这种方法:

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),其中process是显示预期消息的函数。


1

如果可以采用非解决方案,则此PERL片段将完成以下工作:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'

1
谢谢,但是我宁愿避免使用另一种脚本语言,如果可以的话,将其保留为标准的Unix命令和bourne shell
Ernest AC

0

仅吻短纯SED

我会做的

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

并且有效。


您能解释一下它是如何工作的吗?
elysch
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.