0

I'm using this method to open a Gnome terminal window and run command(s) — in this case, to send me simple notification messages (will be scheduled via at):

#!/bin/sh
gnome-terminal -- /bin/sh -c 'echo "remember: $1"; exec bash'

The shell only prints the remember: string and not my specified value, for instance when running remind.sh "pick up the package" or remind.sh "buy groceries X and Y".

When I strip the script down to its core everything works as expected (of course won't have any use in automation via cron or systemd as nothing is displayed on the foreground):

#!/bin/sh
echo "remember: $1"
Kusalananda
  • 333,661

1 Answers1

4

Analysis

The code you (or rather gnome-terminal in your case) provides to /bin/sh -c will see positional parameters provided after the code. E.g. this

gnome-terminal -- /bin/sh -c 'echo "remember: $1"; exec bash' zeroth first second …

will print remember: first.

Providing nothing after the code causes $1 (or $2, $3 etc.) expand to an empty string when the code is finally interpreted ($0 is somewhat different but this doesn't matter here). This is the reason you got remember:. /bin/sh run by gnome-terminal simply knows nothing about the positional parameters of the main script, unless you pass them explicitly.


Solution

To populate positional parameters of the inner code with positional parameters of the script, use "$@" instead of first second …:

gnome-terminal -- /bin/sh -c 'echo "remember: $1"; exec bash' sh "$@"

You can pass whatever you want. Here we pass all the parameters ("$@" expands to all positional parameters), but in general you can use "$1" (if you want the inner code to know the first parameter only) or "$2" (if you want the inner code to see the second positional parameter of the main script as its own $1).


Notes

  • sh as the zeroth argument is explained here: What is the second sh in sh -c 'some shell code' sh?

  • If in your original script (i.e. without my fix) the shell code you intend to provide to sh -c was double-quoted, then the shell interpreted the script would expand $1 and embed the result in the inner code. Don't do this though.

    # flawed  
    gnome-terminal -- /bin/sh -c "echo 'remember: $1'; exec bash"

    In many cases this would work as you expect; but not in general. In general it is as flawed as embedding {}. Single quoted-code and arguments after it are the right way.

  • Single-quoting the code ensures it will get to /bin/sh -c as-is (the inner double-quotes don't matter). Then sh will see the code as

    echo "remember: $1"; exec bash
    

    and it's very good you double-quoted $1 here.

  • and if they want to concatenate the args of the script to one arg for the inner sh -c command, they'd use $*. E.g. gnome-terminal -- /bin/sh -c 'echo "remember: $1"; exec bash' sh "$*" would allow running the script as remind.sh pick up the package without the quotes (as long as they don't use shell special characters in the message that would need quoting) – ilkkachu May 22 '22 at 16:03