I was reading Jeremy Rothman-Shore’s post regarding kicking off a Cygwin script from a Windows batch file which addresses two things:
- Invoking Cygwin’s Bash, passing it a shell script to execute
- Resolving the script’s directory in bash so that it can access other resources its directory.
Here I want to improve a bit on the first point and come up with:
- A general purpose “shim” that will execute a shell script on Windows in Cygwin
- Will pass through any command-line arguments
- Doesn’t trip up on any spaces in the path
The idea is that if I have a shell script called foo.sh, I create a Windows equivalent called foo.cmd alongside it that can be called directly on Windows without the caller worrying about Cygwin etc (apart from having it installed):
(Or foo.bat – I prefer the *.cmd extension because this isn’t MS-DOS we’re running here).
The CMD script has to:
- Find its fully-qualified current location and locate its *.sh counterpart
- Translate this Windows-style path into a Unix style path
- Pass this to Cygwin’s bash along with any other arguments
Firstly, finding a batch file’s location is similar to how it’s done in Unix: it’s the first argument to the script. So in our case we use %0, and we can extract parts of the path like so: %~dp0 will extract the drive and path from argument 0 (i.e. the directory). See the for command for more information on this funky %~ syntax.
Secondly, the translation from a c:windowsstylepath to a /unix/style/path is done by Cygwin’s cygpath command. We do this in a slightly roundabout way via the ever versatile for command.
Thirdly, arguments passed to a batch file can either be accessed individually using $1, %2, $3 etc, or all in one go using %*, which is what we use here.
In addition, so that we don’t litter the Cygwin environment with temporary variables that we’ve created, we use a little setlocal/endlocal trick.
Here it is:
@echo off
setlocal
if not exist "%~dpn0.sh" echo Script "%~dpn0.sh" not found & exit 2
set _CYGBIN=C:cygwinbin
if not exist "%_CYGBIN%" echo Couldn't find Cygwin at "%_CYGBIN%" & exit 3
:: Resolve ___.sh to /cygdrive based *nix path and store in %_CYGSCRIPT%
for /f "delims=" %%A in ('%_CYGBIN%cygpath.exe "%~dpn0.sh"') do set _CYGSCRIPT=%%A
:: Throw away temporary env vars and invoke script, passing any args that were passed to us
endlocal & %_CYGBIN%bash --login "%_CYGSCRIPT%" %*
Note that you just name this the same as your shell script with a .cmd or .bat file extension. and then just execute it as normal.
For example for the following shell script called foo.sh:
#!/bin/sh echo echo "Hello from bash script $0" echo "Working dir is $(pwd)" echo echo "arg 1 = $1" echo "arg 2 = $2" echo
Here’s me calling it from Windows:
Hope that helps!