1

I am confused with this behavior, I have the following script:

backup.sh

#!/bin/bash -x

set -e

if [[ $# -eq 0 ]] ; then
    echo 'No arguments passed'
    exit 1
fi

# Get the arguments
for ARGUMENT in "$@"; do
  KEY=$(echo $ARGUMENT | cut -f1 -d=)
  VALUE=$(echo $ARGUMENT | cut -f2 -d=)

  case "$KEY" in
  backup_dir) BACKUP_DIR=${VALUE} ;;
  postgres_dbs) POSTGRES_DBS=${VALUE} ;;
  backup_name) BACKUP_NAME=${VALUE} ;;
  postgres_port) POSTGRES_PORT=${VALUE} ;;
  postgres_host) POSTGRES_HOST=${VALUE} ;;
  *) ;;
  esac
done

And I am executing it using:

1.

/bin/bash -c /usr/bin/backup.sh postgres_dbs=grafana,keycloak backup_name=postgres-component-test-20220210.165630 backup_dir=/backups/postgres postgres_port=5432 postgres_host=postgres.default.svc.cluster.local
/usr/bin/backup.sh postgres_dbs=grafana,keycloak backup_name=postgres-component-test-20220210.165630 backup_dir=/backups/postgres postgres_port=5432 postgres_host=postgres.default.svc.cluster.local

But the output is:

+ set -e
+ [[ 0 -eq 0 ]]
+ echo 'No arguments passed'
No arguments passed
+ exit 1

Environment:

# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

Bash version where I can reproduce this issue:

GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

However, this is not happening in the Bash version:

GNU bash, version 5.1.8(1)-release (x86_64-apple-darwin20.3.0)
Vishrant
  • 15,456
  • 11
  • 71
  • 120
  • 1
    Are you **sure** this is reproducible with the second form, `/usr/bin/backup.sh postgres...`, without `/bin/bash -c` at the front? – John Kugelman Feb 10 '22 at 23:56
  • @JohnKugelman it is, I added the docker image and the steps where I can reproduce it. – Vishrant Feb 10 '22 at 23:58
  • 1
    Does this answer your question? [I can't seem to use the Bash "-c" option with arguments after the "-c" option string](https://stackoverflow.com/questions/1711970/i-cant-seem-to-use-the-bash-c-option-with-arguments-after-the-c-option-st) – Deathgrip Feb 11 '22 at 00:01
  • 1
    I cannot reproduce the problem using that Docker image and running `./backup.sh foo bar baz`: the script gets 3 arguments, as expected. – John Kugelman Feb 11 '22 at 00:03
  • 1
    I *can* reproduce it by running `bash -c ./backup.sh foo bar baz`: the script gets 0 arguments. But that's expected. – John Kugelman Feb 11 '22 at 00:05
  • @Deathgrip The question title is a perfect match, but unfortunately the question and answers don't really apply here. In that question the poster misused double quotes. – John Kugelman Feb 11 '22 at 00:05
  • @JohnKugelman - I beg to differ. The issue is related to how `bash -c` processes the command and arguments afterwards. Here, @Vishrant is not using either when the command should probably be `/bin/bash -c "/usr/bin/backup.sh postgres_dbs=grafana,keycloak backup_name=postgres-component-test-20220210.165630 backup_dir=/backups/postgres postgres_port=5432 postgres_host=postgres.default.svc.cluster.local"` – Deathgrip Feb 11 '22 at 00:11
  • 1
    I can reproduce this exact behavior on `GNU bash, version 5.1.8(1)-release (x86_64-pc-linux-gnu)` looking at the man page this seems to be the intended behavior of `-c` option - the arguments do not belong to the script, but to the shell. Why are you running the script with `bash -c` if it already has a shabang line? – Lev M. Feb 11 '22 at 00:11
  • @JohnKugelman I tried `./backup.sh arguments` here is the pastebin https://pastebin.com/k0C99PWp still I can reproduce the issue – Vishrant Feb 11 '22 at 00:11
  • Did you notice in the pastebin you have an error - you typed `0` instead of `$#` so the condition is always true! – Lev M. Feb 11 '22 at 00:13
  • 1
    @Vishrant, that pastebin is not equivalent to the script presented in the question. Its literal `if [[ 0 -eq 0 ]]` will always evaluate to true. – John Bollinger Feb 11 '22 at 00:14
  • I didn't realize the `cat << EOF > backup.sh` rendered the env variables. – Vishrant Feb 11 '22 at 00:15
  • Well, it does, @Vishrant. The contents of a heredoc are subject to parameter and other expansions, *unless* any part of the terminator token is quoted (in the redirection operator; it must not be quoted at the end of the heredoc). – John Bollinger Feb 11 '22 at 00:16
  • 1
    You guys are correct, it's a feature and not a bug, the script is missing the argument when I am running with `-C` and it works when I am removing it. – Vishrant Feb 11 '22 at 00:27

2 Answers2

3

It's not a bug, just a feature!

When you use the bash -c 'code …' style, actually the first CLI argument is passed to the inline code as $0, not $1.

Furthermore, if the 'code …' itself invokes an external script such as ./script.sh, then you should not forget to pass the arguments using the "$@" construct.

So you could just write (as pointed out in the comments):

bash -c './script.sh "$@"' bash "first argument"

Or most succinctly, just like you mention you had already tried:

bash script.sh "first argument"

Additional notes

As your example was not really "minimal" (it had a very long command-line), here is a complete minimal example that you might want to test for debugging purpose:

script.sh

#!/usr/bin/env bash
echo "\$#: $#"
for arg; do printf -- '- %s\n' "$arg"; done

Then you should get a session similar to:

$ chmod a+x script.sh

$ bash -c ./script.sh "arg 1" "arg 2"
$#: 0

$ bash -c './script.sh "$@"' "arg 1" "arg 2"
$#: 1
- arg 2

$ bash -c './script.sh "$@"' bash "arg 1" "arg 2"
$#: 2
- arg 1
- arg 2

$ bash script.sh "arg 1" "arg 2"
$#: 2
- arg 1
- arg 2

$ ./script.sh "arg 1" "arg 2"
$#: 2
- arg 1
- arg 2
ErikMD
  • 13,377
  • 3
  • 35
  • 71
  • Please check the question, I have already tried what you are suggesting, I thought adding double quotes will fix it, but it does not. – Vishrant Feb 10 '22 at 23:48
  • I checked the question, and I can reproduce the issue **only** with `/bin/bash -c /usr/bin/backup.sh postgres_dbs=…`, while with `/usr/bin/backup.sh postgres_dbs=…` I get `… + [[ 1 -eq 0 ]] …`, hence my answer… – ErikMD Feb 10 '22 at 23:51
  • Are you using the same environment that I posted? I can not reproduce it in the Bash 5.1.8, same script, same command. – Vishrant Feb 10 '22 at 23:51
  • Hi @Vishrant, to sum up: the issue I mentioned in my answer **does take place** (your commands 1. and 2. are **not equivalent** and I can reproduce a difference with "GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)"); admittedly, you might also experience another, bash-version-specific issue, but could you confirm whether you also get a different outcome between your commands 1. and 2. as I explained in my answer? – ErikMD Feb 10 '22 at 23:55
  • I updated the questions with the steps to reproduce it. – Vishrant Feb 11 '22 at 00:01
  • Thanks @JohnKugelman for your remarks… so I fixed the answer accordingly. But, I'm puzzled the OP can't reproduce our suggestions alike… – ErikMD Feb 11 '22 at 00:14
3

You wrote two ways to invoke the script, which boil down to:

  1. bash -c ./script.sh arg1 arg2 arg3
  2. ./script.sh arg1 arg2 arg3

The second way is the preferred way to invoke scripts. Running them directly tells Linux to use the interpreter listed in the shebang line. There's no reason I can see for this invocation style to drop arguments.

The first, however, does indeed lose all the arguments. It's because -c doesn't belong there. If you want to invoke an explicit bash shell you should write simply:

bash ./script.sh arg1 arg2 arg3

That will correctly pass all the arguments to the script.

When you add -c it turns ./script.sh from the name of a script into a full blown command line. What's the difference? Well, now that command line is responsible for forwarding its arguments to the script, if that's what it wants to have happen. With -c you need to explicitly pass them on:

bash -c './script.sh "$@"' bash arg1 arg2 arg3

Yuck! It's encased in single quotes, and there's an ugly "$@" in there. It's needed, though. Without "$@" the arguments are simply dropped on the floor.

-c also takes an extra argument, the value for $0. So not only is "$@" needed, you also have to add an extra bash argument to set $0. (bash is a good choice since that's what $0 is normally set to when running a bash script.)

John Kugelman
  • 349,597
  • 67
  • 533
  • 578