如何在使用Spawn的Expect脚本中调用Awk Print Variable?

时间:2017-01-21 01:25:52

标签: bash escaping tcl expect quoting

我一直在创建一些基本的系统运行状况检查,其中一项检查包括yum repo运行状况,它使用一种名为'knife'的Chef工具。但是,当我尝试awk一列时,我得到了

can't read "4": no such variable.

以下是我目前正在使用的内容:

read -s -p "enter password: " pass
/usr/bin/expect << EOF
spawn knife ssh -m host1 "sudo bash -c \"yum repolist -v| grep Repo-name| awk '{print $4}'\" "
expect {
-nocase password: { send "$pass\r" }; expect eof }
}
EOF

我也尝试了其他变体,例如用双花括号替换awk单引号,转义变量,甚至将变量设置为命令,并继续得到相同的负面结果:

awk {{print $4}}
awk '{print \$4}'
awk '\{print \$4\}'
awk {\{print \$4\}}

是否有人知道如何在生成的刀ssh命令中将此awk列选择器变量传递给ssh主机?

2 个答案:

答案 0 :(得分:3)

这一行:

spawn knife ssh -m host1 "sudo bash -c \"yum repolist -v|grep Repo-name|awk '{print $4}'\""

有多层引用(Tcl / Expect,ssh,bash,awk),它引用了不同类型。这样的事情通常很讨厌,可能需要使用相当多的反斜杠来说服值通过外层不受干扰。特别是,Tcl和shell都希望得到使用以$开头并继续使用alpha 数字的变量。 (深入的反斜杠需要自己引用更多的反斜杠,使得代码难以阅读并且难以准确理解。)

spawn knife ssh -m host1 "sudo bash -c \"yum repolist -v|grep Repo-name|awk '{print \\\$4}'\""

但是,我们可以使用一个优势:我们可以将大部分代码放在外层的大括号中,因为我们实际上并没有从Tcl中替换任何东西。

spawn knife ssh -m host1 {sudo bash -c "yum repolist -v|grep Repo-name|awk '{print \$4}'"}

大括号内的东西是传统的shell代码,而不是Tcl。事实上,我们可以进一步简化,因为grepawk都不需要提升:

spawn knife ssh -m host1 {sudo bash -c "yum repolist -v"|grep Repo-name|awk '{print $4}'}

根据sudo配置,你甚至可以做到这一点(我实际上是人们在我控制的系统上做的事情,而不是给出root shell的一般访问权限):

spawn knife ssh -m host1 {sudo yum repolist -v|grep Repo-name|awk '{print $4}'}

如果我的awk足够好,你可以像这样摆脱grep

spawn knife ssh -m host1 {sudo yum repolist -v|awk '/Repo-name/{print $4}'}

这开始看起来更易于管理。但是,如果你想用Repo-name代替Tcl变量,你需要做一些重新引入反斜杠的工作,但是现在它比以前更加驯服了,因为创建头痛的复杂层次更少。

set repo "Repo-name"
spawn knife ssh -m host1 "sudo yum repolist -v|awk '/$repo/{print \$4}'"

在实践中,我更有可能完全摆脱awk并在Tcl代码中执行该部分,以及设置基于密钥的直接访问root帐户,允许避开sudo,但这已超出原始问题的范围。

答案 1 :(得分:1)

Donal Fellows' helpful answer包含了很好的分析和建议。

补充说明为什么用$4替换\\\$4 取代

目标最终是awk按原样查看$4,因此我们必须转义$具有特殊含义的所有中间层。

我将在下面的示例中使用简化命令。

让我们暂时删除 shell 层,通过引用开头here-doc分隔符为'EOF',这使得内容表现得很好像一个单引号的字符串;即,shell将内容视为文字,而不应用扩展:

expect <<'EOF'  # EOF is quoted -> literal here-doc
spawn bash -c "awk '{ print \$1 }' <<<'hi there'"
expect eof
EOF

输出:

spawn bash -c awk '{ print $1 }' <<<'hi there'
hi

请注意,$1必须以\$1进行转义,以防止 expect 将其解释为expect变量引用。

鉴于您的问题中的here-doc使用未加引号打开here-doc分隔符(EOF),shell会将内容视为 double-引用字符串;即,应用来应用shell扩展

鉴于shell现在首先扩展了脚本,我们必须为shell 添加一个额外的转义层,方法是将两个额外\添加到{ {1}}:

\$1

这产生与上面相同的输出。

根据解析未加引号的here-doc(如上所述,解析为双引号字符串)的规则,shell将expect <<EOF # EOF is unquoted -> shell expansions happen spawn bash -c "awk '{ print \\\$1 }' <<<'hi there'" expect eof EOF 转换为单\\和{{ 1}}到文字\,结合文字\$1,这是$1脚本需要看到的内容。
(在shell中验证\$1。)

使用命令行参数和引用简化方法here-doc:

正如您所看到的,多层引用(转义)可能会让您感到困惑。

避免问题的一种方法是:

  • 使用引用的 here-doc,这样shell就不会以任何方式解释它,因此您可以专注于expect&#39;引用需求

  • 通过命令行参数传递任何 shell 变量值,并从echo "\\\$1"脚本中引用它们作为表达式(直接或通过首先将它们分配给expect变量。)

expect

文本expect作为第一个(也是唯一的)命令行参数传递,在脚本中可以引用为expect -f - 'hi there' <<'EOF' set txt [lindex $argv 0] spawn bash -c "awk '{ print \$1 }' <<<'$txt'" expect eof EOF
hi there只是明确地告诉[lindex $argv 0]从stdin读取它的脚本,这里必须区分脚本和参数。

-f -创建expect变量set txt ...,然后可以不加引号使用或作为双引号字符串的一部分使用。

要在expect中创建文字字符串,请使用$txt(相当于shell expect)。