将命令行参数从 bash 脚本逐字传递给程序,并进行转义

Pass command line arguments from bash script to program verbatim, with escaping

所以,我有一个 bash 脚本,如果设置了 --xyz 标志,则需要 运行 X,否则需要 Y。它还需要将所有命令行参数传递给它 运行 的程序。我的问题是我无法让它完全按照它们出现的方式传递参数,而是它要么转义不需要转义的东西,要么从应该转义的东西中剥离转义。

这是我的测试脚本:

#!/bin/sh
echo "$@"
args=$@
BASEDIR=$(dirname [=11=])
while test $# -gt 0
do
    case "" in
        --xyz) echo ${args/--xyz/}
            exit $?
            ;;
    esac
    shift
done

echo "$args"
printf %s "$args"
echo
echo "$(printf %s "$args")"
echo "$(printf %q "$args")"

然后我这样测试:

[jearls@earlzpremb X]$ ./test.sh -c Foo\ Bar
-c Foo Bar
-c Foo Bar
-c Foo Bar
-c Foo Bar
-c\ Foo\ Bar
[jearls@earlzpremb X]$ echo -c Foo\ Bar
-c Foo Bar
[jearls@earlzpremb X]$ echo "-c Foo\ Bar"
-c Foo\ Bar

我需要它像上一条命令一样。 Foo\ Bar 保留 space 的转义,但在 -c 之后没有 space 的转义。

我无法控制传递给此脚本的参数,因此我无法进行双重转义或类似操作。

我怎样才能达到基本逐字传递命令行参数并完整转义到另一个程序的目标?

您不能(轻松地)将参数 list 转换为可以转换回列表的字符串。幸运的是,您实际上很少需要这样做。

我不清楚你的具体要求是什么,但这里有一个简单的脚本,它似乎展示了你期望的行为:

# Function to execute if --xyz in argument list
X() { local -i i=0
      for arg in "$@"; do echo X$((++i)): "$arg"; done
}
# Function to call otherwise
Y() { local -i i=0
      for arg in "$@"; do echo Y$((++i)): "$arg"; done
}

# Select one or the other
x_or_y() {
  local arg
  # Scan arguments for --xyz (Not very precise)
  for arg in "$@"; do
    case "$arg" in
      --xyz) X "$@";
             return $?;;
      --)    break;;
    esac
  done
  # No --xyz found
  Y "$@"
}

# Demonstrate that arguments are passed verbatim:
$ x_or_y something "a b c" 'a backslash\inside'
Y1: something
Y2: a b c
Y3: a backslash\inside

$ x_or_y something --xyz "a b c" 'a backslash\inside'
X1: something
X2: --xyz
X3: a b c
X4: a backslash\inside

与bash,如果需要去掉--xyz参数,可以使用数组。 (也可以用set --,但我觉得数组更清晰):

x_or_y() {
  local -i i=1
  local arg
  local todo=Y
  local args=()
  # Scan arguments for --xyz (Not very precise)
  for arg in "$@"; do
    ((++i))
    case "$arg" in
      --xyz) todo=X; break;;
      --)    args+=("$arg"); break;;
      *)     args+=("$arg");;
    esac
  done
  # Call with the args scanned and the rest of the args:
  "$todo" "${args[@]}" "${@:i}"
}

编辑: 正如 Etan Reisner 指出的那样,可以在没有数组的情况下执行此操作。数组是一个很有用的技巧,但是对于这个问题你可以用子字符串表达式来做:

x_or_y() {
  local -i i
  local -i skip=0
  local todo=Y
  # Scan arguments for --xyz (Not very precise)
  for ((i=1; i<=$#; ++i)); do
    case "${!i}" in
      --xyz) todo=X; skip=1; break;;
      --)    break;;
    esac
  done
  # Call with the args scanned and the rest of the args:
  "$todo" "${@:1:i-1}" "${@:i+skip}"
}

如果你需要从参数列表中删除 --xyz 参数,下面的方法应该有效(虽然我不知道这是否适用于 /bin/sh)。

$ cat test.sh
#!/bin/sh

c() {
    printf 'argc: %s\n' "$#";
    printf 'argv: %s\n' "$@"
}

counter=1
for arg; do
    case $arg in
        --xyz)
            echo "Found $arg at position $counter"
            break
        ;;
    esac
    ((counter++))
done

echo "${@::$counter}" "${@:$((counter + 1))}"
c "${@::$counter}" "${@:$((counter + 1))}"
$ ./test -c Foo\ Bar -a Blah\ Quux
-c Foo Bar -a Blah Quux
argc: 4
argv: -c
argv: Foo Bar
argv: -a
argv: Blah Quux
$ ./test.sh -c Foo\ Bar --xyz -a Blah\ Quux
Found --xyz at position 3
-c Foo Bar -a Blah Quux
argc: 4
argv: -c
argv: Foo Bar
argv: -a
argv: Blah Quux