【问题标题】:Modify a variable in a running Bash script修改正在运行的 Bash 脚本中的变量
【发布时间】:2018-11-22 11:40:43
【问题描述】:

我有一个处理多年数据的 bash 脚本,因此该脚本可能需要一周时间才能完成。 为了加快这个过程,我使用多线程,通过并行运行多个实例(每个实例 = 1 天的数据)。每个实例占用 1 个 CPU,因此我可以运行尽可能多的可用 CPU 实例。当我在与他人共享的强大服务器中运行该进程时,有时我可能会有更多或更少的 CPU 可用。 我当前的脚本是:

#!/bin/bash
function waitpid {
   #Gather the gLABs PID background processes (Maximum processes in 
   #background as number of CPUs)
   NUMPIDS=`jobs -p|awk 'END {print NR}'`
   #A while is set because there seems to be a bug in bash that makes 
   #sometimes the "wait -n" command
   #exit even if none of provided PIDs have finished. If this happens, 
   #the while loops forces the 
   #script to wait until one of the processes is truly finished
   while [ ${NUMPIDS} -ge ${NUMCPUS} ]
   do
     #Wait for gLAB processes to finish
     PIDS="`jobs -p|awk -v ORS=" " '{print}'`"
     wait -n ${PIDS} >/dev/null 2>/dev/null
     NUMPIDS=`jobs -p|awk 'END {print NR}'`
   done
}
NUMPCUS=10
for(...) #Loop for each day
do
   day=... #Set current day variable
   #Command to execute, put in background
   gLAB_linux -input ${day}folder/${day}.input -output ${day)outfolder/${day}.output &        
   #Wait for any process to finish if NUMCPUS number of processes are running in background
   waitpid 
done

因此,我的问题是:如果此脚本正在运行,有什么方法可以在不停止脚本的情况下将变量 N​​UMCPUS 更改为任何值(例如 NUMCPUS=23)? 如果可能,我更喜欢不涉及读取或写入文件的方法(如果可能,我希望将临时文件减少到 0)。 我不介意这是否是一个“hackish”过程,例如this answer 中描述的方法。实际上,我在 gdb 中尝试了与该答案中类似的命令,但没有成功,我在 gdb 中出现以下错误(并且还导致进程崩溃):

(gdb) attach 23865
(gdb) call bind_variable("NUMCPUS",11,0)
'bind_variable' has unknown return type; cast the call to its declared return type
(gdb) call (int)bind_variable("NUMCPUS",11,0)
Program received signal SIGSEGV, Segmentation fault

EDIT1:脚本的一些 cmets:

  • gLAB_linux 是一个单核处理程序,不知道 NUMCPUS 变量
  • 每次 gLAB_linux 执行大约需要 5 个小时才能完成,因此 bash 脚本大部分时间都在 wait -n 内休眠。
  • NUMCPUS 必须是脚本的局部变量,因为可能有另一个类似这样的脚本并行运行(仅更改提供给 gLAB_linux 的参数)。因此 NUMCPUS 不能是环境变量。
  • 访问 NUMCPUS 的唯一进程是 bash 脚本

EDIT2:@Kamil 回答后,我添加了从文件中读取 CPU 数量的建议

function waitpid {
   #Look if there is a file with new number of CPUs
   if [ -s "/tmp/numCPUs_$$.txt" ]
   then
     TMPVAR=$(awk '$1>0 {print "%d",$1} {exit}' "/tmp/numCPUs_$$.txt")
     if [ -n "${TMPVAR}" ]
     then
       NUMCPUS=${TMPVAR}
       echo "NUMCPUS=${TMPVAR}"
     fi
     rm -f "/tmp/numCPUs_$$.txt"
   fi

   #Gather the gLABs PID background processes (Maximum processes in 
   #background as number of CPUs)
   NUMPIDS=`jobs -p|awk 'END {print NR}'`
   #A while is set because there seems to be a bug in bash that makes 
   #sometimes the "wait -n" command
   #exit even if none of provided PIDs have finished. If this happens, 
   #the while loops forces the 
   #script to wait until one of the processes is truly finished
   while [ ${NUMPIDS} -ge ${NUMCPUS} ]
   do
     #Wait for gLAB processes to finish
     PIDS="`jobs -p|awk -v ORS=" " '{print}'`"
     wait -n ${PIDS} >/dev/null 2>/dev/null
     NUMPIDS=`jobs -p|awk 'END {print NR}'`
   done
}

【问题讨论】:

  • 查看GNU parallel--limit 参数。
  • 用信号控制怎么样?
  • 每个实例会有不同的输入输出文件和文件夹,所以我不能使用并行bash命令。我编辑了这个问题。使用信号,问题是我不能为变量设置任意值
  • @AwkMan 从文件中读取变量比使用 gdb 破解要可靠得多,但我猜你这样做是为了好玩,而不是祝你好运! btw parallel 可以从标准输入读取 cmd 行。
  • @AwkMan 并行支持从文件中读取 proc num。

标签: bash gdb


【解决方案1】:

最好的办法是修改 bash 脚本,以便它知道您更改了值。从 gdb 会话中修改环境变量 - 这只是侵入性的,而且大多会放弃其他开发人员的工作。

下面我使用了一个名为/tmp/signal_num_cpus 的文件。如果文件不存在,则脚本使用 NUMCPUS 值。如果文件确实存在,它会读取它的内容并相应地更新 NUMCPUS 的数量,然后打印一些通知,说明 numcpus 已更改为文件。如果文件确实存在并且不包含有效数字(例如在预定义的范围或 smth 中),它会在文件中打印一些错误消息。通知对方一切正常或发生了不好的事情

#!/bin/bash

is_not_number() { 
    (( $1 != $1 )) 2>/dev/null
}

# global variable to hold the number of cpus with a default value
NUMCPUS=${NUMCPUS:=5}
# this will ideally execute on each access to NUMCPUS variable
# depending on content
get_num_cpus() { 
   # I tell others that NUMCPUS is a global variable and i expect it here
   declare -g NUMCPUS
   # I will use this filename to communicate
   declare -r file="/tmp/signal_num_cpus"
   # If the file exists and is a fifo...
   if [ -p "$file" ]; then
       local tmp
       # get file contents
       tmp=$(<"$file")
       if [ -z "$tmp" ]; then
           #empty is ignored
           :;
       elif is_not_number "$tmp"; then
           echo "Error reading a number from $file" >&2
           echo "error: not a number, please give me a number!" > "$file"
       else
           # If it is ok, update the NUMCPUS value
           NUMCPUS=$tmp
           echo "ok $NUMCPUS" > "$file"  # this will block until other side starts reading
       fi
   fi
   # last but not least, let's output it
   echo "$NUMCPUS"
}

# code duplication is the worst (ok, sometimes except for databases frameworks)
get_num_bg_jobs() {
    jobs -p | wc -l
}

waitpid() {
   while 
         (( $(get_num_bg_jobs) >= $(get_num_cpus) ))
   do
         wait -n
   done
}

# rest of the script

NUMPCUS=10
for(...) #Loop for each day
do
   day=... #Set current day variable
   #Command to execute, put in background
   gLAB_linux -input "${day}folder/${day}.input" -output "${day)outfolder/${day}.output" &        
   #Wait for any process to finish if NUMCPUS number of processes are running in background
   waitpid 
done

更改值脚本可能如下所示:

#!/bin/bash

# shared context between scripts
declare -r file="/tmp/signal_num_cpus"

mkfifo "$file"

echo 1 > "$file" # this will block until other side will start reading

IFS= read -r line < "$file"

case "$line" in
ok*) 
     read _ numcpus <<<"$line"
     echo "the script changed the number of numcpus to $numcpus"
     ;;
*)
     echo "the script errored with $error"
     ;;
esac

rm "$file"

标记:

  • 定义函数的正确方法是func() { :; } 使用function func { } 是从ksh 中获取的,并且作为扩展支持。使用func() {}
  • 很高兴使用算术扩展(( ... )) 进行数字比较和处理。
  • 不推荐使用反引号 ` 进行命令替换 $( ... )

【讨论】:

  • 你使用文件的方法我觉得太复杂了,特别是“declare”和mkfifo的使用。也许是因为我忘记在我的问题中澄清一些事情(请参阅我的问题中的编辑)。我将(再次)编辑我的问题以添加我的提案
  • declare 只是为了简化阅读,你可以把它们全部扔掉。使用 mkfifo 是因为使用它们更容易同步进程,您可以使用普通文件 + flock 或完全忽略锁定。如果您搜索要运行的脚本的多个实例,请将唯一名称附加到每个脚本中的配置文件名,例如使用$$
【解决方案2】:

GNU Parallel 2018 的第 7.1 章介绍了如何更改运行时使用的线程数 https://zenodo.org/record/1146014

echo 50% > my_jobs
/usr/bin/time parallel -N0 --jobs my_jobs sleep 1 :::: num128 &
sleep 1
echo 0 > my_jobs
wait

因此,您只需将--jobs 的参数放入my_jobs,GNU Parallel 就会在每个完成的作业后读取此内容。

【讨论】:

    猜你喜欢
    • 2018-01-20
    • 1970-01-01
    • 1970-01-01
    • 2013-12-17
    • 1970-01-01
    • 2016-10-13
    • 1970-01-01
    • 1970-01-01
    • 2012-01-17
    相关资源
    最近更新 更多