3

The following shell script takes a list of arguments, turns Unix paths into WINE/Windows paths and invokes the given executable under 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

However, there's something wrong with the quotation of command-line arguments.

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

Note that:

  1. The path to the executable is being chopped off at the first space, even though it is single-quoted.
  2. The literal "\t" in the last path is being transformed into a tab character.

Obviously, the quotations aren't being parsed the way I intended by the shell. How can I avoid these errors?

EDIT: The "\t" is being expanded through two levels of indirection: first, "$p" (and/or "$ARGS") is being expanded into Z:\tmp\smtlib3cee8b.smt; then, \t is being expanded into the tab character. This is (seemingly) equivalent to

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

which yields

zy\tyz

and not

zy  yz

UPDATE: eval "$CMD" does the trick. The "\t" problem seems to be echo's fault: "If the first operand is -n, or if any of the operands contain a backslash ( '\' ) character, the results are implementation-defined." (POSIX specification of echo)

Chris Conway
  • 55,321
  • 43
  • 129
  • 155

4 Answers4

3
  • bash’s arrays are unportable but the only sane way to handle argument lists in shell
  • The number of arguments is in ${#}
  • Bad stuff will happen with your script if there are filenames starting with a dash in the current directory
  • If the last line of your script just runs a program, and there are no traps on exit, you should exec it

With that in mind

#! /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[@]}"
andrewdotn
  • 32,721
  • 10
  • 101
  • 130
  • Define "unportable". I don't mind requiring /bin/bash instead of /bin/sh, but I"m hesitant to require a recent version. Has someone put together a Unix shell script portability matrix (i.e., which features are available in which versions of which shells on which platforms)? – Chris Conway Sep 22 '08 at 17:13
1

I you do want to have the assignment to CMD you should use

eval $CMD

instead of just $CMD in the last line of your script. This should solve your problem with spaces in the paths, I don't know what to do about the "\t" problem.

WMR
  • 12,661
  • 5
  • 35
  • 30
0

You can try preceeding the spaces with \ like so:

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

You can also do the same with your \t problem - replace it with \\t.

Kyle Cronin
  • 77,653
  • 43
  • 148
  • 164
  • Both of the paths are perfectly valid Unix paths *as input to the script*. The single quotes should eliminate the need for escaping spaces. And the "\t" is automatically generated by the `winepath` command (it turns "/tmp" into "Z:\tmp"). – Chris Conway Aug 30 '08 at 17:01
0

replace the last line from $CMD to just

wine '$EXEC' $ARGS

You'll note that the error is ''/home/chris/.wine/drive_c/Program' and not '/home/chris/.wine/drive_c/Program'

The single quotes are not being interpolated properly, and the string is being split by spaces.