我有一个具有以下格式的文本文件:
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 命令执行,并将其结果读入模式缓冲区(甚至是多行)。使用p
after(!)选项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;"
并且它起作用了。