Bash关联数组在For循环中运行时被覆盖

时间:2018-10-19 00:26:53

标签: bash for-loop associative-array subshell

我正在尝试编写一个bash脚本,该脚本使用一个函数在后台生成多个应用程序,然后将它们的PID存储在以应用程序名称为键的关联数组中。当我使用for循环遍历具有应用程序路径的另一个数组时遇到问题。生成所有应用程序后,我将回显数组的所有值,但仅显示最后生成的应用程序的PID。这会在脚本的其余部分中引发一些问题。

我已经找到this thread可能会发生这种情况的原因,但是我无法针对自己的情况实施此修复程序。有人可以指出正确的方向吗?

proc_name=( application1 application2 application3 )
declare -A proc_pid

spawn_application () {
if [ $# -ne 1 ] || [ ! -x $1 ]
then
    echo "$FUNCNAME: usage $FUNCNAME <proc_name>"
    if [ $# -ne 1]
    then
        echo "$FUNCNAME: invalid amount of arguments, expected 1 received $#"
    elif [ ! -x $1 ]
    then
        echo "$FUNCNAME: $1 is not an executable file"
    fi
    exit 1
else
    ./$1 $host_ipaddr &
    proc_pid[$1]=$!
    echo "$0: starting $1 with PID: ${proc_pid[$1]}"
fi
}

for proc in ${proc_name[@]}
do
    spawn_application $proc
done
echo ${proc_pid[@]}

这是完整的脚本:

#!/bin/bash
#####################
#     Functions     #
#####################

# init - deletes all existing _metrics.csv files, populates array of executables, initializes map of proc_name:pid
init () {
  rm ./*_metrics.csv >/dev/null 2>&1
  proc_name=( application1 application2 application3 )
  declare -A proc_pid
}

# spawn_application - takes a proc_name, checks that it is executable, and then runs the executable in the background
spawn_application () {
  if [ $# -ne 1 ] || [ ! -x "$1" ]
  then
    echo "$FUNCNAME: usage $FUNCNAME <proc_name>"
    if [ $# -ne 1 ]
    then
      echo "$FUNCNAME: invalid amount of arguments, expected 1 received $#"
    elif [ ! -x "$1" ]
    then
      echo "$FUNCNAME: $1 is not an executable file"
    fi
    exit 1
  else
    ./"$1" "$host_ipaddr" &
    #proc_pid[$1]=$!
    #echo "$0: starting $1 with PID: ${proc_pid[$1]}"
  fi
}

# collect_proc_metrics - takes a proc_name and returns the elapsed time in seconds, cpu percentage, and memory percentage, passes these values to write_proc_metrics
collect_proc_metrics () {
  if [ $# -ne 1 ]
  then
    echo "$FUNCNAME: usage $FUNCNAME <proc_name>"
    if [ $# -ne 1 ]
    then
      echo "$FUNCNAME: invalid amount of arguments, expected 1 received $#"
    fi
    exit 1
  else
    read -r seconds cpu memory <<<"$(ps -q ${proc_pid["$1"]} -eo etimes=,%cpu=,%mem=)"
    write_proc_metrics "$1" "$seconds" "$cpu" "$memory" &
fi
}

# collect_sys_metrics - takes no arguments, obtains the rx and tx rates, disk writes, and available disk space. Uses the last set elapsed time in seconds from the collect_proc_metrics. Automatically passes these values to write_sys_metrics
collect_sys_metrics () {
  read -r rx tx <<<"$(ifstat en* | sed -n 4p | tr -s ' ' | awk '{print $7, $9}')"
  disk_writes=$(iostat sda -k | tail -n 2 | tr -s ' ' | cut -d' ' -f4)
  available_disk_space=$(df /dev/mapper/centos-root --output=avail --block-size=1M | tail -n 1)
  write_sys_metrics "$seconds" "$rx" "$tx" "$disk_writes" "$available_disk_space" &
}

# write_proc_metrics - takes the elapsed time in seconds, cpu percentage, and memory percentage and appends them to a .csv file.
write_proc_metrics () {
if [ $# -ne 4 ]
then
  echo "$FUNCNAME: usage $FUNCNAME <proc_name> <seconds> <cpu> <memory>"
  echo "$FUNCNAME: invalid amount of arguments, expected 4 received $#"
else
  local metricsout="$1_metrics.csv"
  if [ ! -f "$metricsout" ]
  then
    echo "SECS,CPU%,MEM%" > "$metricsout"
  fi
  echo "$2,$3,$4" >> "$metricsout"
fi
}

# write_sys_metrics - takes the elapsed time in seconds, RX and TX rates, disk writes, and disk usage and appends them to a .csv file. 
write_sys_metrics () {
if [ $# -ne 5 ]
then
  echo "$FUNCNAME: usage $FUNCNAME <seconds> <rx> <tx> <disk_writes> <available_disk_space>"
  echo "$FUNCNAME: invalid amount of arguments, expected 5 received $#"
else
  local metricsout="system_metrics.csv"
  if [ ! -f $metricsout ]
  then
    echo "SECS,RX,TX,kB_wrtn/s,MB_avail" > $metricsout
  fi
  echo "$1,$2,$3,$4,$5" >> system_metrics.csv
fi
}

# cleanup - loops through the proc_names in the proc_name array and kills their associated PID with a -9 flag.
cleanup () {
  pkill -f application >/dev/null 2>&1
  pkill -f ifstat >/dev/null 2>&1
  #for proc in ${proc_name[@]}
  #do
  #  kill -9 ${proc_pid[$proc]}
  #done
}
trap cleanup EXIT

################
#     Main     #
################
if [ $# -ne 1 ]
then
  echo "$0: usage $0 <host_ipaddr>"
  echo "$0: invalid amount of arguments, expected 1 received $#"
else
  init
  host_ipaddr=$1
  for proc in "${proc_name[@]}"
  do
    spawn_application "$proc"
    proc_pid["$proc"]=$!
    echo "$0: starting $proc with PID: ${proc_pid[$proc]}"
  done
  echo "${proc_pid[@]}"

  # Starts the ifstat command as a background process.
  ifstat -d1 -n
  ifstat_pid=$( ps aux | grep "ifstat -d1" | head -1 | tr -s ' ' | cut -f2 -d' ' )
  echo "$0: starting ifstat with PID: $ifstat_pid"
  ifstat -nr

  # Duration is how long to sample metrics in seconds. 900 seconds is 15 minutes
  duration=$(( SECONDS+900 ))
  # Interval is how long to wait between sampling
  interval=5
  while [ $SECONDS -lt $duration ]
  do
    for proc in "${proc_name[@]}"
    do
      collect_proc_metrics "$proc"
    done
    collect_sys_metrics
    sleep $interval
  done
  cleanup
fi

输出:

[rje6459@localhost Desktop]$ ./apm.sh 129.21.229.64
./apm.sh: starting APM1 with PID: 48163
./apm.sh: starting APM2 with PID: 48164
./apm.sh: starting APM3 with PID: 48165
./apm.sh: starting APM4 with PID: 48166
./apm.sh: starting APM5 with PID: 48167
./apm.sh: starting APM6 with PID: 48168
48168
./apm.sh: starting ifstat with PID: 48170

0 个答案:

没有答案