1

我有一个为我们的 DD-WRT 路由器编写的 bash 脚本,它会定期重新启动 NAS 守护程序以修复 iPhone 连接到无线的随机问题。

这是我的第一个 bash 脚本之一,我在试图找出如何确定这个问题的根源时迷失了方向。

该脚本会运行,并且会按预期重新启动服务。但是,我收到错误消息:eval: line 1: $: not found脚本运行时在终端上。


#!/bin/sh

##
# Restarts the nas daemon on the specified interval
##

# Restart interval, in seconds
#T=3600 # hourly
T=60 # for testing

# Log file
if [ $# -ne 0 ]; then
        log=$1
else
        log=/tmp/restart.log
fi

while [ true ]; do
        # Wait
        sleep $T

        # Find commands to relaunch all nas daemons
        nascmd=`ps ww | grep nas | awk '{if($5!="grep"){$1=$2=$3=$4=""; print $0";"}}'`
        echo [`date`] Existing pid: `ps | grep nas | awk '{ORS=",";if($5!="grep"){print $1}}'` >> $log

        # Restart nas with original arguments
        killall -HUP nas
        echo [`date`] Running command: $nascmd >> $log

        # Strip special characters prior to eval
        safecmd=`echo $nascmd | sed 's/\$/\\$/g'`
        eval $safecmd

        echo [`date`] Finished, new pid: `ps | grep nas | awk '{ORS=",";if($5!="grep"){print $1}}'` >> $log
done

我认为错误源于eval它正好是 1 行长。我试图弄清楚$它是什么以及为什么找不到它。我会避开所有的美元符号,如果它们存在的话。不应该有,但是,我只是为了安全,以防有人将加密密码短语更改为带有美元符号的东西。

4

1 回答 1

1

的定义有问题safecmd

safecmd=`echo $nascmd | sed 's/\$/\\$/g'`

当 shell 解析反引号字符串时,它会将反斜杠视为引用下一个字符。所以输出存储到的命令safecmd

echo $nascmd | sed 's/$/\$/g'

传递给 sed 的正则表达式是$,即在行尾匹配。替换文本是\$. 所以safecmd最终会像

/usr/bin/nas --whatever\$

如果没有正在运行的进程,nascmd则最终为空,因此safecmd为空\$,即执行名称为 的命令$

关于在反引号内引用的规则很复杂,并且它们因 shell 而异(但 BusyBox 静默、破折号、bash、ksh93 和 pdksh 都以相同的方式处理这种特殊情况)。解决方法很简单:永远不要使用反引号,$(…)而是使用。除了旧的 Bourne shell 之外,所有 shell 都支持美元括号,除非您维护非常旧的 Unix 服务器,否则您不太可能遇到这种情况。$(…)具有直观的语法:只需编写命令,无需额外引用。

您分配的方式可能还有另一个问题nascmd,我没有考虑那么多。您的脚本不支持没有nas进程运行的情况。


您的脚本中有许多引用问题,因此如果参数 nonas包含任何特殊字符,您将不会执行相同的命令。你引用标志的失败尝试$是沧海一粟。首先,始终在变量替换和命令替换周围使用双引号: "$foo", "$(foo)". 然后,您调用eval的根本不是 shell 命令,而是命令名称及其参数,中间有空格字符。您无法可靠地恢复原始命令,因为无法分辨哪些空格是参数的一部分,哪些是参数分隔符。

您可以从 可靠地恢复命令行/proc/$pid/cmdline,因为参数由不能在命令内部出现的空字节分隔。但是在 shell 脚本中这样做很麻烦,因为 shell 不能处理空字节。如果您假设换行符不会出现在参数中,这并不难:

nas_pid=$(pidof nas)
# <insert a check that $nas_pid contains exactly one PID here>
set -f
IFS='
'
set -- $(</proc/$nas_pid/cmdline tr \\0 \\n)
set +f
# Kill the old NAS pid
kill $nas_pid
# Start a new instance
"$@"

但这一切似乎毫无意义。在某个地方,您必须有一些nas在设备启动时启动的东西。当您想重新启动服务时,找到那个东西并再次运行它。

PS 这不是 bash 脚本,而是 sh 脚本。有几种 sh 实现;DD-WRT 与大多数嵌入式设备一样,使用BusyBox的 sh 。Bash 是另一种 sh 实现;BusyBox sh 中并非所有 bash 功能都存在。

于 2013-01-03T21:06:38.700 回答