Saturday, March 12, 2022

[SOLVED] Invalid behavior for arguments in Bash version 4.4 vs version 5.1?

Issue

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)

Solution

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


Answered By - ErikMD
Answer Checked By - Pedro (WPSolving Volunteer)