I would like to run a Bash shell script (.sh
) using the Windows Subsystem for Linux as part of a Build Event in Visual Studio, in a C++ project.
But there are lots of errors when I do this, and I can't seem to find the right combination of quotation marks and apostrophes and backslashes to either make Bash run in the first place, or to properly pass the path to the script.
How do I make Visual Studio run my Bash shell script as a build event?
In this case it's simple : add your . sh script file to your Visual Studio solution one way or another, then right-click on it in the Solution Explorer, and select « Open With… ». This will determine what program gets called when you double click on the file in this view.
In the Post-build event command line box, specify the syntax of the build event. Add a call statement before all post-build commands that run . bat files. For example, call C:\MyFile.
Run code ~/. bashrc in bash to open the file in VS Code. Add the following to your PowerShell profile. Run code $Profile in pwsh to open the file in VS Code.
(Feel free to skip to the bottom of this answer if you don't care about how to solve the problem and just want a command you can copy and paste!)
I run a number of Bash shell scripts as part of Build events in Visual Studio, and I used to use Cygwin to run them. Now that the Windows Subsystem for Linux is available, I spent some time switching my builds over to use WSL, and it wasn't as easy as I'd hoped, but it can work, with a little time and energy.
There are several issues you'll run into if you're going to do this:
bash.exe
may not be what you think it is, because under the hood, Visual Studio uses a 32-bit build process, so if you're on a 64-bit machine, you can't simply run the 64-bit bash.exe
without getting the dreaded 'C:\Windows\System32\bash.exe' is not recognized
error.\
), and those don't play nice in Unix, which prefers forward slashes (/
) as a path delimiter.C:\
, is meaningless gibberish in Unix; to reach the root drive in WSL, you'll need to use a mounted drive under /mnt
.C:\
, and in WSL, it's lowercase /mnt/c
.And to make it a little harder, we don't want to hard-code any of the paths: It should all Just Work, no matter where the solution is found.
The good news is that they're all solvable issues! Let's tackle them one at a time.
1. The proper path to Bash
Per the answer given here, you'll need to use a magic path to reach Bash when running it from a Visual Studio build. The correct path is not C:\Windows\System32\bash.exe
, but is actually
%windir%\sysnative\bash.exe
The magic sysnative
folder avoids the invisible filesystem redirection performed by the WOW64 layer, and points to the native bash.exe
file.
2. Fixing the backslashes
The next problem you're likely to run into is the backslashes. Ideally, you'd like to run a project script like $(ProjectDir)myscript.sh
, but that expands to something like C:\Code\MySolution\MyProject\myscript.sh
. At a minimum, you'd like that to be at least C:/Code/MySolution/MyProject/myscript.sh
, which isn't exactly right, but which is a lot closer to correct.
So sed
to the rescue! sed
is a Unix tool that mutates text in files: It searches for text using regular expressions, and, among other things, can replace that text with a modified version. Here, we're going to pipe the path we have into sed
, and then use some regex magic to swap the path separators, like this (with lines wrapped here for readability):
%windir%\sysnative\bash.exe -c "echo '$(ProjectDir)myscript.sh'
| sed -e 's/\\\\/\//g;'"
If you include this as your build event, you'll see that it now doesn't run the script, but it at least prints something like C:/Code/MySolution/MyProject/myscript.sh
to the output console, which is a step in the right direction.
And yes, that's a lot of backslashes and quotes and apostrophes to get the escaping right, because Nmake.exe
and bash
and sed
are all going to consume some of those special symbols while processing their respective command-lines.
3. Fixing the C:\
root path
We want to mutate the sed
script so that it turns the C:\
into /mnt/C
. A little more regex substitution magic can do that. (And we have to turn on the -r
flag in sed
so that we can easily use capture groups.)
%windir%\sysnative\bash.exe -c "echo '$(ProjectDir)myscript.sh'
| sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\1/i;'"
If you run this, you'll now see the output path as something like /mnt/C/Code/MySolution/MyProject/myscript.sh
, which is almost but not quite correct.
4. Fixing the case-change in the root path
WSL mounts your disks in lowercase, and Windows mounts them in uppercase. Consistency! How do we fix this? Yet more sed
magic!
The \L
command can be used to tell sed
to transform succeeding characters to lowercase (and there's an equivalent \U
for uppercase). The \E
command will switch output back to "normal" mode, where characters are left untouched.
Adding these in finally results in the correct path being output:
%windir%\sysnative\bash.exe -c "echo '$(ProjectDir)myscript.sh'
| sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\L\1\E/i;'"
5. Running it
This whole time, Bash has just been printing out the path to the script. How do we run it instead, now that it's the correct path?
The answer is to add `backticks`! Backticks cause Bash to execute the command contained within them, and to then use that command's output as the arguments to the next command. In this case, we're not going to output anything: We just want to run the output of sed
as a command.
So including the backticks, here's the result:
%windir%\sysnative\bash.exe -c "`echo '$(ProjectDir)myscript.sh'
| sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\L\1\E/i;'`"
Here's what the whole solution looks like, for running a script named myscript.sh
as a Build Event, in the current Project directory of the current Solution:
%windir%\sysnative\bash.exe -c "`echo '$(ProjectDir)myscript.sh' | sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\L\1\E/i;'`"
Here's a screen-shot showing the same thing in Visual Studio 2017, for a real C++ project:
It's not easy, but it's not impossible.
If you have Git for Windows installed, try this. I found it simpler than installing WSL. The basic idea is to create an intermediate batch script to call your bash script, using Git bash's in-built bash
or sh
command from the batch script.
With Git for Windows, you'll have a Git\bin
folder e.g. at:
C:\Program Files\Git\bin
Inside that directory you should see the bash.exe
and sh.exe
programs. So if you add that directory to your Windows Path environment variable then you'll be able to use sh
and bash
from the Windows command line. These commands will allow you to run your bash scripts "inline" within a CMD
console window. That is, they won't spawn a new bash window; meaning the console output will be visible in your VS build.
From there, just create a .bat
file which calls your .sh
file using either the sh
command or the bash
command. Not sure the difference; we just use the sh
command. So if your bash script is pre.sh, then your batch file would be just a single line calling the bash script:
sh %~dp0\pre.sh
if errorlevel 1 (
exit /b %errorlevel%
)
The %~dp0
assumes the batch and bash scripts are in the same directory. You then point your VS build event to the .bat
file. The check for error level is necessary so that any failures from the bash script are forwarded up to the batch script. See: How do I get the application exit code from a Windows command line?.
To hook this in as a build event in VS2019 then, just follow the standard instructions for hooking in a .bat
file: https://docs.microsoft.com/en-us/visualstudio/ide/specifying-custom-build-events-in-visual-studio?view=vs-2019.
One thing we found quite frustrating with this solution was the tendency of VS to not load in the path variable correctly. It seems to prefer the user variable over the system variable. But even after we deleted the user variable, sometimes the path didn't seem to be getting picked up by VS, and we kept getting "sh is not recognised..." messages on our build console. Whenever that happened, restarting VS seemed to do the trick. Not very satisfying, but it gets us by.
Git for Windows does have a lot of Unix commands available, but not all of them. So in general, this won't work. For the general case, WSL is more robust. However, if it's just pretty lightweight Unix you need, this will suffice, and will likely be an easier approach for Windows users who would rather avoid the steeper setup cost of installing the full WSL.
Original idea to use Git bash came from here: https://superuser.com/questions/1218943/windows-command-prompt-capture-output-of-bash-script-in-one-step
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With