【问题标题】:Execute bash command stored in associative array over SSH, store result通过 SSH 执行存储在关联数组中的 bash 命令,存储结果
【发布时间】:2014-06-20 20:11:12
【问题描述】:

对于不相关的大型项目,我需要从本地系统或远程系统收集系统统计信息。由于我以任何一种方式收集相同的统计信息,因此我通过将收集统计信息的命令存储在 Bash 关联数组中来防止代码重复。

declare -A stats_cmds
# Actually contains many more key:value pairs, similar style
stats_cmds=([total_ram]="$(free -m | awk '/^Mem:/{print $2}')")

我可以像这样收集本地系统统计信息:

get_local_system_stats()
{
    # Collect stats about local system
    complex_data_structure_that_doesnt_matter=${stats_cmds[total_ram]}
    # Many more similar calls here
}

我的脚本的先决条件是 ~/.ssh/config 的设置使得 ssh $SSH_HOSTNAME 在没有任何用户输入的情况下工作。我想要这样的东西:

get_remote_system_stats()
{
    # Collect stats about remote system
    complex_data_structure_that_doesnt_matter=`ssh $SSH_HOSTNAME ${stats_cmds[total_ram]}`
}

我已经尝试了单引号、双引号、反引号等我能想象到的所有组合。某些组合会导致 stats 命令过早执行 (bash: 7986: command not found),其他组合会导致语法错误,其他组合会返回 null(stats 命令周围的单引号),但没有一个将正确的结果存储在我的数据结构中。

如何通过 SSH 在远程系统上评估存储在关联数组中的命令,并将结果存储在本地脚本的数据结构中?

【问题讨论】:

  • 这仍然是一个悬而未决的问题,我正在积极寻求答案。正如 gniourf_gniourif 的回答中的 cmets 中所述,它不支持远程和本地执行。
  • 还开着吗? 2014 年 10 月添加的评论是否解决了您对先前答案的问题似乎尚未得到答复。
  • 是的,它仍然开放。我正在使用基于两种解决方案组合的解决方法。 github.com/dfarrell07/wcbench/blob/master/wcbench.sh#L56-L82
  • Eww。再说一次,一定数量的“ewww”与该领域有关——在字符串中嵌入任意 shell 命令至少是中等程度的邪恶。我至少会考虑将它们封装在函数中——然后你可以直接在本地运行函数,或者使用declare -f 要求 shell 给你一个字符串,当评估时,它将远程定义它。
  • 顺便说一句,您能否澄清如何该评论未能解决您的问题(以及为什么您仍需要该解决方法)?

标签: arrays linux bash shell ssh


【解决方案1】:

确保在分配数组时存储在数组中的命令不会被扩展!

还请注意,嵌套单引号时需要看起来复杂的引用样式。请参阅this SO 帖子以获得解释。

stats_cmds=([total_ram]='free -m | awk '"'"'/^Mem:/{print $2}'"'"'')

然后将您的ssh 启动为:

sh "$ssh_hostname" "${stats_cmds[total_ram]}"

(是的,我把你的变量名小写了,因为 Bash 中的大写变量名真的很恶心)。那么:

get_local_system_stats() {
    # Collect stats about local system
    complex_data_structure_that_doesnt_matter=$( ${stats_cmds[total_ram]} )
    # Many more similar calls here
}

get_remote_system_stats() {
    # Collect stats about remote system
    complex_data_structure_that_doesnt_matter=$(ssh "$ssh_hostname" "${stats_cmds[total_ram]}")
}

【讨论】:

  • 将 awk 命令转换为使用双引号会中断打印(打印整行,而不是第二个字段)。正在寻找解决此问题的方法...
  • 使用this method 嵌套单引号似乎有效。仍然看到“bash: : command not found”。调试中...
  • 使用此答案中当前描述的方法,我可以成功存储远程统计信息,但会看到本地统计信息错误。 total_ram 示例存储free -m 的完整输出,例如
  • @dfarrell07,对于本地命令,使用result=$(eval "${cmd_stats[total_ram]}");这为您提供了与 ssh 完成的外部 shell 调用类似的效果。
【解决方案2】:

首先,我将建议一种对现有实现进行最小更改的方法。然后,我将展示一些更接近最佳实践的东西。


最小的修改

鉴于您现有的代码:

declare -A remote_stats_cmds
remote_stats_cmds=([total_ram]='free -m | awk '"'"'/^Mem:/{print $2}'"'"''
            [used_ram]='free -m | awk '"'"'/^Mem:/{print $3}'"'"''
            [free_ram]='free -m | awk '"'"'/^Mem:/{print $4}'"'"''
            [cpus]='nproc'
            [one_min_load]='uptime | awk -F'"'"'[a-z]:'"'"' '"'"'{print $2}'"'"' | awk -F "," '"'"'{print $1}'"'"' | tr -d " "'
            [five_min_load]='uptime | awk -F'"'"'[a-z]:'"'"' '"'"'{print $2}'"'"' | awk -F "," '"'"'{print $2}'"'"' | tr -d " "'
            [fifteen_min_load]='uptime | awk -F'"'"'[a-z]:'"'"' '"'"'{print $2}'"'"' | awk -F "," '"'"'{print $3}'"'"' | tr -d " "'
            [iowait]='cat /proc/stat | awk '"'"'NR==1 {print $6}'"'"''
            [steal_time]='cat /proc/stat | awk '"'"'NR==1 {print $9}'"'"'')

...人们可以按如下方式在本地评估这些:

result=$(eval "${remote_stat_cmds[iowait]}")
echo "$result" # demonstrate value retrieved

...或远程如下:

result=$(ssh "$hostname" bash <<<"${remote_stat_cmds[iowait]}")
echo "$result" # demonstrate value retrieved

不需要单独的表格。


正确的事情

现在,让我们谈谈一种完全不同的方法:

# no awful nested quoting by hand!
collect_total_ram() { free -m | awk '/^Mem:/ {print $2}'; }
collect_used_ram()  { free -m | awk '/^Mem:/ {print $3}'; }
collect_cpus()      { nproc; }

...然后,在本地进行评估:

result=$(collect_cpus)

...或者,远程评估:

result=$(ssh "$hostname" bash <<<"$(declare -f collect_cpus); collect_cpus")

...或者,使用 collect_ 前缀遍历定义的函数并执行以下两项操作:

declare -A local_results
declare -A remote_results
while IFS= read -r funcname; do
  local_results["${funcname#collect_}"]=$("$funcname")
  remote_results["${funcname#collect_}"]=$(ssh "$hostname" bash <<<"$(declare -f "$funcname"); $funcname")
done < <(compgen -A function collect_)

...或者,一次将所有项目收集到一个远程数组中,避免额外的 SSH 往返而不是 eval'ing 或以其他方式冒着收到结果的安全风险从远程系统:

remote_cmd=""
while IFS= read -r funcname; do
  remote_cmd+="$(declare -f "$funcname"); printf '%s\0' \"$funcname\" \"\$(\"$funcname\")\";"
done < <(compgen -A function collect_)

declare -A remote_results=( )
while IFS= read -r -d '' funcname && IFS= read -r -d '' result; do
  remote_results["${funcname#collect_}"]=$result
done < <(ssh "$hostname" bash <<<"$remote_cmd")

【讨论】:

  • 这太棒了!谢谢!
猜你喜欢
  • 1970-01-01
  • 2011-08-05
  • 2015-06-13
  • 2017-11-28
  • 1970-01-01
  • 1970-01-01
  • 2014-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多