Bash 4.4 版与 5.1 版中参数的无效行为?

Invalid behavior for arguments in Bash version 4.4 vs version 5.1?

我对这种行为感到困惑,我有以下脚本:

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

我正在执行它:

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

但输出是:

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

环境:

# 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 可以重现此问题的版本:

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

然而,这在 Bash 版本中没有发生:

GNU bash, version 5.1.8(1)-release (x86_64-apple-darwin20.3.0)

这不是错误,只是一个功能!

当您使用 bash -c 'code …' 样式时,实际上第一个 CLI 参数作为 [=15=] 而不是 </code>.</p> 传递给内联代码 <p>此外,如果 <code>'code …' 本身调用外部脚本,例如 ./script.sh,那么您不应该忘记使用 "$@" 结构传递参数。

所以你可以写(正如评论中指出的那样):

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

或者最简洁的,就像你提到你已经尝试过的那样:

bash script.sh "first argument"

补充说明

由于您的示例并不是真正的“最小”(它有一个很长的 command-line),这里是一个完整的最小示例,您可能希望出于调试目的对其进行测试:

script.sh

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

然后你应该得到一个类似于:

的会话
$ 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

您编写了两种调用脚本的方法,归结为:

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

第二种方式是调用脚本的首选方式。 运行 他们直接告诉 Linux 使用 shebang 行中列出的解释器。我认为这种调用风格没有理由放弃参数。

然而,第一个确实失去了所有论据。这是因为 -c 不属于那里。如果你想调用显式 bash shell 你应该简单地写:

bash ./script.sh arg1 arg2 arg3

这将正确地将所有参数传递给脚本。

当您添加 -c 时,它会将 ./script.sh 从脚本名称变成完整的命令行。有什么不同?好吧,现在该命令行负责将其参数转发给脚本,如果这是它想要发生的事情的话。使用 -c 您需要明确传递它们:

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

呸!它用单引号括起来,里面有一个难看的 "$@" 。不过,这是需要的。没有 "$@" 争论就直接丢在地上了。

-c 还需要一个额外的参数,即 [=22=] 的值。所以不仅需要 "$@",你还必须添加一个额外的 bash 参数来设置 [=22=]。 (bash 是一个不错的选择,因为当 运行 一个 bash 脚本时,[=22=] 通常设置为 bash。)