Pregunta

I have a bash script written for our DD-WRT routers, it restarts the NAS daemons periodically to fix random problems with iPhone's connecting to wireless.

This is one of my first bash scripts, and I'm lost trying to figure out how to identify the root of this problem.

The script does run, and it does restart the services as it's supposed to. However, I am getting the error: eval: line 1: $: not found on the terminal when the script runs.


#!/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

I presume the error originates from eval since it's exactly 1 line long. I'm trying to figure out what $ is and why it's not found. I escape all the dollar signs, if they exist. There shouldn't be any, however, I was just being safe in case someone changes the encryption pass phrase to something with a dollar sign.

¿Fue útil?

Solución

There's a problem in the definition of safecmd:

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

When the shell parses the backquoted string, it treats backslashes as quoting the next character. So the command whose output is stored into safecmd is

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

The regex passed to sed is $, i.e. match at the end of the line. The replacement text is \$. So safecmd ends up being something like

/usr/bin/nas --whatever\$

If there's no running process, nascmd ends up being empty, so safecmd is \$, i.e. execute a command whose name is $.

The rules about quoting inside backquotes are complicated, and they vary from shell to shell (BusyBox hush, dash, bash, ksh93 and pdksh all treat this particular case in the same way though). The fix is simple: never use backquotes, use $(…) instead. Dollar-parenthesis is supported on all shells except older Bourne shells which you're unlikely to encounter unless you maintain very old Unix servers. $(…) has an intuitive syntax: just write the command with no additional quoting.

There may be another problem in the way you assign nascmd, I haven't thought about that much. Your script lacks support for the case when no nas process is running.


There are many quoting problems in your script, so if the arguments no nas contain any special characters, you won't be executing the same command. Your failed attempt to quote $ signs is a drop in the sea. For a start, always use double quotes around variable substitutions and command substitutions: "$foo", "$(foo)". Then, you call eval on what is not at all a shell command, but a command name and its arguments with space characters in between. You cannot reliably restore the original command because there's no way to tell which spaces were parts of an argument and which ones are argument separators.

You can reliably recover the command line from /proc/$pid/cmdline, because the arguments are separated by null bytes which can't occur inside the command. But it's cumbersome to do that in a shell script, because shells can't deal with null bytes. It's not too difficult to do if you assume that newlines won't occur inside arguments:

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
"$@"

But all this seems rather pointless. Somewhere, you must have something that starts nas when the device boots. Find that thing and run it again when you want to restart the service.

P.S. This is not a bash script, it's an sh script. There are several sh implementations; DD-WRT, like most embedded devices, uses the sh of BusyBox. Bash is another sh implementation; not all features of bash are present in BusyBox sh.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top