关于 unix:在 shell 脚本中引用命令行参数

关于 unix:在 shell 脚本中引用命令行参数

Quoting command-line arguments in shell scripts

以下 shell 脚本采用参数列表,将 Unix 路径转换为 ??WINE/Windows 路径并调用 WINE 下的给定可执行文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#! /bin/sh

if ["${1+set}" !="set" ]
then
  echo"Usage; winewrap EXEC [ARGS...]"
  exit 1
fi

EXEC="$1"
shift

ARGS=""

for p in"$@";
do
  if [ -e"$p" ]
  then
    p=$(winepath -w $p)
  fi
  ARGS="$ARGS '$p'"
done

CMD="wine '$EXEC' $ARGS"
echo $CMD
$CMD

但是,命令行参数的引用有问题。

1
2
3
$ winewrap '/home/chris/.wine/drive_c/Program Files/Microsoft Research/Z3-1.3.6/bin/z3.exe' -smt /tmp/smtlib3cee8b.smt
Executing: wine '/home/chris/.wine/drive_c/Program Files/Microsoft Research/Z3-1.3.6/bin/z3.exe' '-smt' 'Z: mp\\smtlib3cee8b.smt'
wine: cannot find ''/home/chris/.wine/drive_c/Program'

请注意:

  • 可执行文件的路径在第一个空格处被截断,即使它是单引号。
  • 最后一个路径中的文字"\\\\t"正在转换为制表符。
  • 显然,shell 并没有按照我想要的方式解析引号。如何避免这些错误?

    编辑:"\\\\t"正在通过两个间接级别进行扩展:首先,"$p"(和/或 "$ARGS")正在扩展为 Z:\\tmp\\smtlib3cee8b.smt;然后, \\t 被扩展为制表符。这(似乎)等同于

    1
    2
    3
    Y='y\\ty'
    Z="z${Y}z"
    echo $Z

    产生

    1
    zy\\tyz

    而不是

    1
    zy  yz

    更新: eval"$CMD" 可以解决问题。"\\t" 问题似乎是 echo 的错误:"如果第一个操作数是 -n,或者任何操作数包含反斜杠 ('\\\\') 字符,则结果是实现定义的。" (echo 的 POSIX 规范)


    • bash 的数组是不可移植的,但却是在 shell 中处理参数列表的唯一明智的方法
    • 参数的数量在 ${#}
    • 如果当前目录中存在以破折号开头的文件名,您的脚本会出现问题
    • 如果脚本的最后一行只是运行一个程序,并且退出时没有陷阱,则应该执行它

    考虑到这一点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #! /bin/bash

    # push ARRAY arg1 arg2 ...
    # adds arg1, arg2, ... to the end of ARRAY
    function push() {
        local ARRAY_NAME="${1}"
        shift
        for ARG in"${@}"; do
            eval"${ARRAY_NAME}[\\${#${ARRAY_NAME}[@]}]=\\${ARG}"
        done
    }

    PROG="$(basename --"${0}")"

    if (( ${#}  1 )); then
      # Error messages should state the program name and go to stderr
      echo"${PROG}: Usage: winewrap EXEC [ARGS...]" 1&2
      exit 1
    fi

    EXEC=("${1}")
    shift

    for p in"${@}"; do
      if [ -e"${p}" ]; then
        p="$(winepath -w --"${p}")"
      fi
      push EXEC"${p}"
    done

    exec"${EXEC[@]}"

    我确实想要分配给 CMD 你应该使用

    eval $CMD

    而不是 $CMD 在脚本的最后一行。这应该可以解决路径中的空格问题,我不知道该怎么处理 "\\\\\\\\t" 问题。


    将 $CMD 的最后一行替换为 just

    葡萄酒\\'$EXEC\\'$ARGS

    您会注意到错误是 \\'\\'/home/chris/.wine/drive_c/Program\\' 而不是 \\'/home/chris/.wine/drive_c/Program\\'

    单引号没有正确插入,字符串被空格分割。


    您可以尝试使用 \\\\\\\\ 在空格之前,如下所示:

    1
    /home/chris/.wine/drive_c/Program Files/Microsoft\\ Research/Z3-1.3.6/bin/z3.exe

    你也可以对你的 \\\\\\\\t 问题做同样的事情 - 用 \\\\\\\\\\\\\\\\t 替换它。


    推荐阅读