0

I have the following script:

installRequiredFiles=$1

install_with_pkg()
{
    arg1=$1
        echo $arg1
    echo "[+] Running update: $arg1 update."
}

if [ $installRequiredFiles = "true" ]; then
    if [ -x "$(command -v apt)" ]; then
          install_with_pkg "apt"
        elif [ -x "$(command -v yum)" ]; then
          install_with_pkg "yum"
        elif [ -x "$(command -v apk)" ]; then
          install_with_pkg "apk"
        else
          echo "[!] Can't install files."
    fi
fi

When I run it straight forward it works fine:

root@ubuntu:/# ./myscript.sh "true"
apt
[+] Running update: apt update.
Printing installRequiredFiles: true
Printing arg1: apt

But when I am using sh -c I am getting the following error:

root@ubuntu:/# sh -c ./myscript.sh "true"
./c.sh: 11: [: =: unexpected operator
Printing installRequiredFiles:
Printing arg1:

I want to be able to run it correctly with sh -c and I want it to support sh and bash which currently does.

2 Answers 2

5

That's not what the -c option is for. You normally don't give it a file, you give it shell commands. It is meant for doing things like this:

$ sh -c 'echo hello'
hello

Now that you gave it a file, it is trying to read it and execute the commands found in it, but the argument isn't passed to the script (myscript.sh), the argument is only given to the sh command itself as you can see if you simply print the arguments:

$ cat script.sh
echo "ARGS: $@"

$ sh ./script.sh true
ARGS: true
$ sh -c ./script.sh true
ARGS: 

All you need to do is to not use -c and it will work as expected:

sh ./myscript.sh "true"

Or, if you absolutely must use -c for some reason, pass the script and the argument for the script as a single, quoted argument to sh:

sh -c './myscript.sh "true"'
3
  • 1
    Also, for -c invocations, the first argument becomes $0: sh -c 'echo $0; echo $1' foo bar Commented Feb 2, 2022 at 12:24
  • Note that the last command will end up executing the script with a shell that possibly depends on what shell implements the sh shell (since the script lacks #!-line). It's a bit unclear, but addressed by unix.stackexchange.com/questions/373223
    – Kusalananda
    Commented Feb 2, 2022 at 13:33
  • 1
    Another issue they have is the unquoted $installRequiredFiles in the test. If it was quoted, there would be no error, the test would be just false. Not that it might make it easier to figure out what's wrong, but When is double-quoting necessary? applies here, too...
    – ilkkachu
    Commented Feb 2, 2022 at 14:20
3

The error that you get is from the dash shell (the shell used for implementing /bin/sh on your system). It is due to $installRequiredFiles being

  1. an empty string, and
  2. used unquoted.

Since the variable is empty, using it unquoted removes it completely from the command, which means that the line

if [ $installRequiredFiles = "true" ]; then

would be interpreted as

if [ = "true" ]; then

which in turn is an error in the usage of the [ command; it sees the operator = when no operator was expected.

So, why is $installRequiredFiles (and $1), empty?

The command

sh -c ./myscript.sh "true"

runs the command ./myscript.sh with $0 set to the string true in the script. The value in $0 is usually the name of the script or of the shell, and the value is most commonly used in diagnostic messages (e.g. in error messages that the shell produces).

If you had used

sh -c ./myscript.sh sh "true"

instead, then $1 would have been set to test as expected, and $0 would have been set to sh (which is customary for in-line sh -c scripts). In both cases, the script would have been executed by whatever shell executes scripts with no #!-line. What shell ends up running the script is possibly depending on the sh shell on your machine, and it may not be sh or bash. See, e.g., Which shell interpreter runs a script with no shebang?.

You likely want to add a #!-line in your script pointing to /bin/sh:

#!/bin/sh

# ... rest of script here ...
# ... just remember to quote all expansions ...

That means you'll be able to run your script like so:

$ ./myscript.sh test

or, with the equivalent

$ sh -c './myscript.sh "test"'

or,

$ sh -c './myscript.sh "$@"' sh test

Note that in the last two of these cases, it's not the sh -c shell that is executing the script, but whatever shell the #!-line in the script refers to, just as if you ran ./myscript.sh "test" directly. The difference compared to before adding the #!-line is that you now know for certain that this is /bin/sh and not some other shell.

The script uses only POSIX shell syntax, which means it would be executable by /bin/sh, regardless of what shell is used to implement /bin/sh on any given system. If /bin/sh is bash, ksh or dash or some other more exotic shell does not matter, the user should not need to worry about running the script with the correct interpreter.

Related to various parts of this answer:

1
  • To clarify: running sh -c ./myscript.sh sh "true" will set $1 to "true" while executing the command ./myscript.sh, but not while running the script ./myscript.sh. Since that command runs the script without passing any arguments, the arg list within the script will be empty. That's why sh -c './myscript.sh "$@"' is needed to pass the arguments on to the script. Commented Feb 2, 2022 at 17:10

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.