Tuesday, 15 January 2013

linux - Execute bash command stored in associative array over SSH, store result -



linux - Execute bash command stored in associative array over SSH, store result -

for larger project that's not relevant, need collect scheme stats local scheme or remote system. since i'm collecting same stats either way, i'm preventing code duplication storing stats-collecting commands in bash associative array.

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

i can collect local scheme stats this:

get_local_system_stats() { # collect stats local scheme complex_data_structure_that_doesnt_matter=${stats_cmds[total_ram]} # many more similar calls here }

a precondition of script ~/.ssh/config setup such ssh $ssh_hostname works without user input. this:

get_remote_system_stats() { # collect stats remote scheme complex_data_structure_that_doesnt_matter=`ssh $ssh_hostname ${stats_cmds[total_ram]}` }

i've tried every combination of single quotes, double quotes, backticks , such can imagine. combinations result in stats command getting executed (bash: 7986: command not found), others cause syntax errors, others homecoming null (single quotes around stats command) none store proper result in info structure.

how can evaluate command, stored in associative array, on remote scheme via ssh , store result in info construction in local script?

first, i'm going suggest approach makes minimal changes existing implementation. then, i'm going demonstrate closer best practices.

smallest modification

given existing code:

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

...one can evaluate these locally follows:

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

...or remotely follows:

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

no separate form required.

the right thing

now, let's talk exclusively different way this:

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

...and then, evaluate locally:

result=$(collect_cpus)

...or, evaluate remotely:

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

...or, iterate through defined functions collect_ prefix , both of these things:

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

...or, collect items single remote array in 1 pass, avoiding ssh round-trips and not eval'ing or otherwise taking security risks results received remote system:

remote_cmd="" while ifs= read -r funcname; 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; remote_results["${funcname#collect_}"]=$result done < <(ssh "$hostname" bash <<<"$remote_cmd")

arrays linux bash shell ssh

No comments:

Post a Comment