Converting a BASH script to run on SH (via BusyBox)

2.7k Views Asked by At

I have an Asus router running a recent version of FreshTomato - that comes with BusyBox.

I need to run a script that was made with BASH in mind - it is an adaptation of this script - but it fails to run with this error: line 41: syntax error: bad substitution

Checking the script with shellcheck.net yields these errors:

Line 41:
        for optionvarname in ${!foreign_option_*} ; do
                             ^-- SC3053: In POSIX sh, indirect expansion is undefined.
                             ^-- SC3056: In POSIX sh, name matching prefixes are undefined.
 
Line 42:
        option="${!optionvarname}"
                ^-- SC3053: In POSIX sh, indirect expansion is undefined.

These are the lines that are causing problems:

for optionvarname in ${!foreign_option_*} ; do   # line 41
    option="${!optionvarname}"                   # line 42

    # do some stuff with $option...
done

If my understanding is correct, the original script simply does something with all variables that have a name starting with foreign_option_

However, as far as I could determine, both ${!foreign_option_*} and ${!optionvarname} constructs are BASH-specific and not POSIX compliant, so there is no direct "bash to sh" code conversion possible.

I have tried to create a /bin/bash symlink that points to busybox, but I got the Read-only file system error.

So, how can I get this script to run on my router? I see only two options, but I cant figure out how to implement either:

  1. Make BusyBox interpret the script as BASH instead of SH - can I use a specific shebang for this?
    • Seems like the fastest option, but only if BusyBox has a "complete" implementation of BASH
  2. Alter the script code to not use BASH specifics.
    • This is safer, but since there is no "collect al variables starting with X" for SH, how can I do it?
2

There are 2 best solutions below

1
On BEST ANSWER

how can I get this script to run on my router?

That easy, either:

  • install bash on your router or
  • port the script to busybox/posix compatible shell.

Make BusyBox interpret the script as BASH instead of SH - can I use a specific shebang for this?

That doesn't make sense. Busybox comes with ash shell interpreter and bash is bash. Bash can interpret bash extensions, ash can't interpret them. You can't "make busybox interpret bash" - cars don't fly, planes are for that. If you want to make a car fly, you add wings to it and make it faster. The answer to Make BusyBox interpret the script as BASH instead of SH would be: patch busybox and implement all bash extensions in it.

Shebang is used to run a file under different interpreter. Using #!/bin/bash would invoke bash, which would be unrelated to anything busybox related and busybox wouldn't be involved in it.

how can I do it?

Decide on a unrealistic maximum, iterate over variables named foreign_option_{1...some_max}, for each variable see if it is set, if it is set, cotinue the script.

for i in $(seq 100); do
    optionvarname="foreign_option_${i}"        
    # https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash
    if eval "[ -z \"\${${optionvarname}+x}\" ]"; then continue; fi;

With enough luck maybe you can use the set output. The following will fail if any variable contains a value as newline + the string that matches the regex:

for optionvarname in $(set | grep -o '^foreign_option_[0-9]\+=' | sed 's/=//'); then

Indirect expansion can be easily replaced by eval:

eval "option=\"\$${optionvarname}\""
      
0
On

If you really cannot install Bash on that router, here is one possible workaround, which seems to work for me in BusyBox on a Qnap NAS :

foreign_option_one=1
foreign_option_two=2

for x in one two; do
    opt_var=foreign_option_${x}
    eval "opt_value=\$$opt_var"
    echo "$opt_var = $opt_value"
done

(But you will probably encounter more problems with moving a Bash script to busybox, so you might want to first consider alternatives like replacing the router)