我一直在创建一些基本的系统运行状况检查,其中一项检查包括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主机?
答案 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。事实上,我们可以进一步简化,因为grep
和awk
都不需要提升:
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,这样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
)。