1

How do I invoke a batch file from a bash script under Cygwin, with...

  • absolute path to the batch file
  • batch file argument(s) representing absolute (windows) path(s)
  • output redirection into a file at an absolute path

where...

  • all paths may contain whitespace and special characters like parentheses
  • all paths are initially present only in Unix format within the bash script?

I have a windows batch script C:\Program Files (x86)\bin\script.bat, which takes a (windows) path to an input file as argument - example:

@ECHO OFF
echo "This is script.bat. The value of the first argument is [%~1]"

I want to redirect the output of script.bat into an output file at another absolute path.

On the windows command prompt (cmd.exe), I would invoke the command like this:

C:\> "C:\Program Files (x86)\bin\script.bat" "C:\Input Path\input.txt" > "C:\Output Path\output.txt"
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].

I can omit the double quotes around the script and the redirection target if I apply "batch-style escaping", but that doesn't work for the argument (for some reason):

C:\> C:\Program^ Files^ ^(x86^)\bin\script.bat "C:\Input Path\input.txt" > C:\Output^ Path\output.txt
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].

...but:

C:\> C:\Program^ Files^ ^(x86^)\bin\script.bat C:\Input^ Path\input.txt > C:\Output^ Path\output.txt
output.txt: This is script.bat. The value of the first argument is [C:\Input].

I can also wrap the command into an additional call to cmd.exe, using additional double quotes around the whole term:

C:\> cmd.exe /C ""C:\Program Files (x86)\bin\script.bat" "C:\Input Path\input.txt" > "C:\Output Path\output.txt""
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].

C:\> cmd.exe /C "C:\Program^ Files^ ^(x86^)\bin\script.bat "C:\Input Path\input.txt" > C:\Output^ Path\output.txt"
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].

I can also apply the output redirection to the outer cmd.exe instance:

C:\> cmd.exe /C ""C:\Program Files (x86)\bin\script.bat" "C:\Input Path\input.txt"" > "C:\Output Path\output.txt"
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].

C:\> cmd.exe /C "C:\Program^ Files^ ^(x86^)\bin\script.bat "C:\Input Path\input.txt"" > C:\Output^ Path\output.txt
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].

So far so good. But how can I invoke any of the above command lines from a bash script under Cygwin?

All paths initially exist only in Unix format:

#!/bin/sh
BIN_UNIX="/cygdrive/c/Program Files (x86)/bin/script.bat"
ARG_UNIX="/cygdrive/c/Input Path/input.txt"
OUT_UNIX="/cygdrive/c/Output Path/output.txt"

Note that the file $OUT_UNIX does not exist at the time the script is called (so 'cygpath --dos ...' doesn't work).

I have experimented with dozens of more or less clumsy combinations of Unix / Windows paths (converting with cygpath), no quotes, single quotes, double quotes, no escaping, "bash-style" escaping, "batch-style" escaping, etc. The only working solution I could find depends on an 8.3-style path for the batch script, which eliminates white spaces and special characters:

#!/bin/sh
# [...]
BIN_DOS=$( cygpath --dos "${BIN_UNIX}" )
ARG_WIN=$( cygpath --windows "${ARG_UNIX}" )
cmd /C "${BIN_DOS}" "${ARG_WIN}" > "$OUT_UNIX"

But there must be a more systematic and robust way to do this, right?


Related questions which all don't fully answer my question:

Why is it that Cygwin can run .bat scripts?

Windows batches in Cygwin with spaces in path and arguments

correct quoting for cmd.exe for multiple arguments

How to Pass Command Line Parameters with space in Batch File

rikinet
  • 93
  • 6
  • Where is the Batch file question in this. You want NON WINDOWS answers. – Noodles Apr 29 '19 at 08:32
  • @Noodles The tricky point is the transition from bash to batch, the quoting/escaping can be really exciting. I spent many hours to examine similar problems – jeb Apr 29 '19 at 12:36

1 Answers1

3

You could use

BIN_WIN=$( cygpath --windows "${BIN_UNIX}" )
ARG_WIN=$( cygpath --windows "${ARG_UNIX}" )
OUT_WIN=$( cygpath --windows "${OUT_UNIX}" )
cmd /c CALL "$BIN_WIN" "${ARG_WIN}" > "$OUT_WIN"

The important point is the CALL in front of your command.

cmd /c fails to start a command with spaces inside, because it splits the command even when there are quotes around.

The cmd /c would also work without CALL, but with additional enclosing quotes.

cmd /c "  "C:\Program Files (x86)\bin\script.bat" "args"   "

But it seems to be impossible to transfer raw quotes from cygwin to cmd.exe, because cygwin translates quotes to \", so the expression becomes

cmd /c \"  "C:\Program Files (x86)\bin\script.bat" "args"   \"
jeb
  • 78,592
  • 17
  • 171
  • 225