如何在Visual Studio中将Bash shell脚本作为构建事件运行?

8

我想在Visual Studio的C++项目中,作为构建事件之一,使用Windows Subsystem for Linux来运行Bash shell脚本(.sh)。

但是当我这样做时会出现很多错误,我似乎无法找到正确的引号、撇号和反斜杠的组合,以使Bash首先运行,或者正确传递脚本的路径。

我该如何让Visual Studio将我的Bash shell脚本作为构建事件运行?

3个回答

7

如果您不关心如何解决问题,只想要一个可以复制粘贴的命令,请随意跳到本答案底部!


概述

我在Visual Studio的构建事件中运行了一些Bash shell脚本,以前我使用Cygwin来运行它们。现在Windows子系统Linux已经可用,我花了一些时间将我的构建迁移到WSL上,但并不像我希望的那么容易,但只要花费一些时间和精力,它是可以工作的。

如果你也想这样做,你会遇到几个问题:

  1. bash.exe的路径可能不是你想象的那样,因为在内部,Visual Studio使用32位构建过程,因此如果你在64位计算机上运行64位bash.exe,就会出现恐怖的'C:\Windows\System32\bash.exe' is not recognized错误。
  2. 你的解决方案或项目的路径是一个使用反斜杠(\)的Windows路径,在Unix中这些符号并不适用,Unix更喜欢将正斜杠(/)作为路径分隔符。
  3. 解决方案的根驱动器通常是类似于C:\的无意义符号,在Unix中毫无意义;要在WSL中访问根驱动器,你需要使用/mnt下的已挂载驱动器。
  4. 驱动器字母的大小写在Windows和WSL之间不同:在Windows中它是大写的C:\,而在WSL中它是小写的/mnt/c

为了让它更加困难,我们不想硬编码任何路径:无论解决方案在哪里找到,它都应该正常工作。

好消息是,所有这些问题都是可以解决的!让我们逐个解决它们。


修复问题

1. Bash的正确路径

根据这里给出的答案,当从Visual Studio构建中运行Bash时,您需要使用一个特殊的路径来到达Bash。 正确的路径不是C:\Windows\System32\bash.exe,而实际上是

%windir%\sysnative\bash.exe

神奇的sysnative文件夹避免了WOW64层执行的不可见文件系统重定向,并指向本地的bash.exe文件。

2. 修复反斜杠

接下来你可能会遇到的问题是反斜杠。理想情况下,你希望像这样运行一个项目脚本:$(ProjectDir)myscript.sh,但这会扩展为类似于C:\Code\MySolution\MyProject\myscript.sh的东西。至少,你希望它至少是C:/Code/MySolution/MyProject/myscript.sh,这并不完全正确,但比之前更接近正确了。

所以使用sed来解决! sed是一个Unix工具,可以修改文件中的文本:它使用正则表达式搜索文本,并且可以将该文本替换为修改后的版本。在这里,我们将路径输入到sed中,然后使用一些正则表达式技巧交换路径分隔符,就像这样(这里为了可读性将行包装):

%windir%\sysnative\bash.exe -c "echo '$(ProjectDir)myscript.sh'
    | sed -e 's/\\\\/\//g;'"

如果您将此作为构建事件包含在内,您将会发现它不会运行脚本,但至少会在输出控制台中打印类似于C:/Code/MySolution/MyProject/myscript.sh的内容,这是朝着正确方向迈出的一步。
是的,这需要很多反斜杠、引号和撇号来进行转义,因为Nmake.exebashsed都会在处理各自的命令行时消耗其中一些特殊符号。
3. 修复C:\根路径
我们想要改变sed脚本,使其将C:\变成/mnt/C。更多的正则表达式替换魔法可以做到这一点。(我们必须在sed中打开-r标志,以便我们可以轻松使用捕获组。)
%windir%\sysnative\bash.exe -c "echo '$(ProjectDir)myscript.sh'
    | sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\1/i;'"

如果您运行此命令,您将看到输出路径类似于/mnt/C/Code/MySolution/MyProject/myscript.sh,这几乎是正确的,但不完全正确。
4. 修复根路径中的大小写问题
WSL将磁盘挂载为小写,而Windows则挂载为大写。如何解决这个问题呢?使用更多的sed魔法! \L命令可用于告诉sed将后续字符转换为小写(还有一个等效的\U用于大写)。\E命令将输出切换回“正常”模式,其中字符保持不变。
添加这些命令最终会输出正确的路径:
%windir%\sysnative\bash.exe -c "echo '$(ProjectDir)myscript.sh'
    | sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\L\1\E/i;'"

5. 运行它

这段时间里,Bash 一直在打印脚本的路径。现在我们已经找到了正确的路径,如何运行它呢?

答案是添加“反引号”!反引号会导致 Bash 执行其中包含的命令,并将该命令的输出作为下一个命令的参数。在这种情况下,我们不会输出任何内容:我们只想运行 sed 的输出作为命令。

因此,包括反引号,这就是结果:

%windir%\sysnative\bash.exe -c "`echo '$(ProjectDir)myscript.sh'
    | sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\L\1\E/i;'`"

完整解决方案

下面是完整解决方案的样子,用于在当前解决方案的当前项目目录中将名为myscript.sh的脚本作为构建事件运行:

%windir%\sysnative\bash.exe -c "`echo '$(ProjectDir)myscript.sh' | sed -re 's/\\\\/\//g; s/([A-Z]):/\/mnt\/\L\1\E/i;'`"

这是一张Visual Studio 2017中真实C++项目的截图:

enter image description here

它不容易,但并非不可能。


你可以做的另一件事是利用WSL进程“继承”了它执行时的工作目录这个事实。所以,在我的情况下,我能够创建一个目标文件,像这样执行 <Exec Command="%WINDIR%\sysnative\wsl.exe bash script.sh $(Configuration) $(someOtherArg)" WorkingDirectory="$(MSBuildThisFileDirectory)" ConsoleToMSBuild="true" />。唯一的问题是从 script.sh 访问的文件必须相对于 $PWD - cschadl

2
如果您已安装Windows版的Git,请尝试以下方法。相比安装WSL,我发现这更为简便。基本思路是创建一个中间批处理脚本来调用您的bash脚本,使用Git bash内置的bashsh命令从批处理脚本中调用。

使用Windows版的Git,您将在Git\bin文件夹下找到相关文件,例如:

C:\Program Files\Git\bin

在该目录下,您应该看到bash.exesh.exe程序。因此,如果将该目录添加到Windows Path环境变量中,则可以在Windows命令行中使用shbash。这些命令将允许您在CMD控制台窗口内“内联”运行bash脚本。也就是说,它们不会生成新的bash窗口;这意味着控制台输出将在您的VS构建中可见。
从那里开始,只需创建一个.bat文件,使用sh命令或bash命令调用您的.sh文件。不确定区别;我们只使用sh命令。因此,如果您的bash脚本是pre.sh,则批处理文件将只是调用bash脚本的单个命令行。
sh %~dp0\pre.sh
if errorlevel 1 (
   exit /b %errorlevel%
)
%~dp0 假设批处理和bash脚本在同一个目录中。然后将VS的构建事件指向.bat文件。检查错误级别是必要的,以便将bash脚本的任何故障转发到批处理脚本中。请参见:如何从Windows命令行获取应用程序退出代码?
要将此作为VS2019的构建事件挂钩,只需按照挂钩.bat文件的标准说明操作:https://learn.microsoft.com/en-us/visualstudio/ide/specifying-custom-build-events-in-visual-studio?view=vs-2019

更新:注意Visual Studio(VS)的路径变量行为

我们发现这个解决方案中的一个非常令人沮丧的问题是,VS倾向于不正确地加载路径变量。它似乎更喜欢用户变量而不是系统变量。但即使我们删除了用户变量,有时也似乎无法正确获取路径,并且我们在构建控制台上一直收到“sh未被识别”的消息。每当发生这种情况时,重新启动VS似乎就可以解决问题。这并不是很令人满意,但它可以帮助我们。

更新:这不是完整的Unix解决方案

Git for Windows确实有许多Unix命令可用,但并不是全部。因此,一般情况下,这不起作用。对于一般情况,WSL更为强大。但是,如果只需要相当轻量级的Unix,那么这就足够了,并且对于希望避免安装完整WSL的Windows用户来说,这可能是更简单的方法。
使用Git bash的原始想法来自于这里:https://superuser.com/questions/1218943/windows-command-prompt-capture-output-of-bash-script-in-one-step

1
这也不是一个坏的解决方案,但它会限制您使用Git-for-Windows软件包中安装的命令。该软件包包括许多最常见的命令(甚至包括perl!);但它仍然不是完整的Unix发行版,因此如果您需要其中未包含的任何Unix工具,则需要自己编译它们,或者考虑改用WSL。话虽如此,对于缺乏Unix经验或不愿安装完整的Unix发行版的团队来说,这个解决方案将非常有效,因为安装Git-for-Windows非常容易。 - Sean Werkema
1
非常感谢,加一。我为此苦苦挣扎了几个小时。我已经接近成功了,但是无法在构建控制台中获取脚本的输出,因为最初我使用的是 C:\Program Files (x86)\Git\git-bash.exe,它总是打开一个新窗口,窃取脚本的输出,然后关闭。我没有想到如果我使用 ...\bin\sh 会有所不同。受到你的回答的激励,我尝试了一下,令我惊讶的是,它起作用了。 - Binarus

-2

你可以用 $( 和 ) 包裹命令,而不是使用反引号。


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