如何在使用 Spawn 的 Expect 脚本中调用 Awk 打印变量?

How do I call Awk Print Variable within Expect Script that uses Spawn?

我一直在创建一些基本的系统健康检查,其中一项检查包括 yum 存储库健康状态,它使用名为 '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 }'\" "
expect {
-nocase password: { send "$pass\r" }; expect eof }
}
EOF

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

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

有谁知道如何在将变量发送到 ssh 主机的 spawned knife ssh 命令中传递这个 awk 列选择器变量?

这一行:

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

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

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 }'}

根据 sudo 配置,您甚至可以执行此操作(我实际上更希望人们在我控制的系统上这样做,而不是授予对 root shell 的一般访问权限出来):

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

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

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

这看起来更易于管理了。然而,如果你想用 Repo-name 代替 Tcl 变量,你需要做更多的工作来重新引入反斜杠,但现在它比以前更温和了,因为造成麻烦的复杂层更少了。

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

实际上,我更有可能完全摆脱 awk 并在 Tcl 代码中完成该部分,以及设置 key-based 直接访问 root 帐户,允许避免 sudo,但这已经超出了你原来问题的范围。

包含很好的分析和建议。

为什么用 \$4 替换 </code> 的解释来补充它</strong>:</p> <p>最终目的是让<code>awk看到</code>as-is,所以必须逃避所有<code>$有特殊意义的中间层[=48] =]

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

让我们先暂时删除shell层,通过引用开头的here-doc分隔符作为'EOF',这使得内容表现得像 single-quoted 字符串;即,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  }' <<<'hi there'
hi

请注意,</code> 必须转义为 <code>$1 以防止 expect 将其解释为 expect 变量参考。

鉴于您问题中的 here-doc 使用 未加引号的 开头 here-doc 定界符 (EOF),shell将内容视为 double-quoted 字符串;即,应用了 shell 扩展

鉴于 shell 现在首先扩展脚本,我们必须为 shell 添加额外的转义层 ,方法是在 [=86] 前面加上=]两个额外的\$1:

expect <<EOF  # EOF is unquoted -> shell expansions happen
spawn bash -c "awk '{ print \$1 }' <<<'hi there'"
expect eof
EOF

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

根据解析不带引号的 here-doc 的规则(如前所述,它被解析为 double-quoted 字符串),shell 将 \ 转换为将 \$1 合并为文字 </code>,合并为文字 <code>$1,这是 expect 脚本需要看到的内容。
(在 shell 中用 echo "\$1" 验证。)


使用 command-line 参数和 引用 简化方法 here-doc:

如您所见,多层引用(转义)可能会造成混淆。

避免问题的一种方法是:

  • 使用 引用 here-doc,这样 shell 就不会以任何方式解释它,这样您就可以关注expect的报价需求

  • 通过command-line参数传递任何shell变量值并引用它们从 expect 脚本内部作为 表达式 (直接或首先将它们分配给 expect 变量)。

expect -f - 'hi there' <<'EOF'
set txt [lindex $argv 0]
spawn bash -c "awk '{ print $1 }' <<<'$txt'"
expect eof
EOF

文本 hi there 作为第一个(也是唯一的)command-line 参数传递,可以在脚本中引用为 [lindex $argv 0]
-f - 只是明确地告诉 expect 从 stdin 读取它的脚本,这是区分脚本和参数所必需的)。

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

要在 expect 中创建 文字 字符串,请使用 {...}(相当于 shell 的 '...') .