在shell脚本中引用命令行参数

3
下面的shell脚本接受一系列参数,将Unix路径转换为WINE/Windows路径,并在WINE下调用给定的可执行文件。
#! /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

然而,命令行参数的引用存在问题。

$ 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'

请注意:
  1. 可执行文件路径在第一个空格处被截断,即使它已经用单引号括起来了。
  2. 最后一个路径中的字面值“\t”被转换为制表符。
显然,引号没有按照我在shell中预期的方式进行解析。我该如何避免这些错误?
编辑:"\t" 通过两个间接级别进行扩展:首先,"$p"(和/或"$ARGS")被扩展为 "Z:\tmp\smtlib3cee8b.smt";然后,"\t" 被扩展为制表符。这(似乎)等同于:
Y='y\ty'
Z="z${Y}z"
echo $Z

这将产生

zy\tyz

并且不是

zy  yz

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

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

考虑到这一点

#! /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[@]}"

什么是"unportable"? 我不介意需要/bin/bash而不是/bin/sh,但我不确定是否需要最新版本。是否有人制作了Unix shell脚本可移植矩阵(即,在哪些平台的哪些版本的哪些shell中可用哪些功能)? - Chris Conway

1

如果你确实想要将任务分配给CMD,你应该在脚本的最后一行使用

eval $CMD

而不是只使用$CMD。这可以解决路径中空格的问题,但我不知道如何解决"\t"问题。


0

您可以尝试使用反斜杠 \ 来转义空格,例如:

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

你也可以用相同的方法解决你的问题 - 用 \\t 替换它。


这两个路径都是完全有效的Unix路径,作为脚本的输入。单引号应该可以消除转义空格的需要。而"\t"是由winepath命令自动生成的(它将"/tmp"转换为"Z:\tmp")。 - Chris Conway

0

将最后一行从$CMD替换为

wine '$EXEC' $ARGS

您会注意到错误是“/home/chris/.wine/drive_c/Program”,而不是“/home/chris/.wine/drive_c/Program”

单引号没有被正确地插值,字符串被空格分隔。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接