Bash: 正确地将带空格的 wsl 参数传递给 win10 主机上的程序

Bash: properly pass from wsl parameters with spaces to a program on the win10 host

我在 bash 包含空格 and/or 换行符的参数值中正确传递从 wsl 到 win10 主机上的程序时遇到问题。

嗯。这是一个普遍的问题,但它来自一个具体的用例。 具体用例是使用主机级 git 二进制文件,即来自 wsl.

git.exe

我知道有一些相当流行的解决方案,例如 wslgit,它允许您从 windows 调用 wsl 中的 git 二进制文件。然而,这不仅意味着您的 wsl 会为每个 git 命令提供午餐(如果它还没有 运行),而且它会导致一个陷阱完整的设置和一个有问题的可优化性能足迹。 我的 wsl 并不总是打开 - 我的许多任务不需要它,我不想为 git 保留它 运行 - 所以我的选择是相反的:从 wsl 使用 git.exe,它始终存在。

无论如何,我相信这代表了一个更通用的案例,并且可以与从 wsl 调用的任何主机级程序相关。

因此,在 git 的具体示例中 - 我需要一个 shim,只要从任何 shell 中调用 git 命令,它就会将调用传递给 git.exe WSL 的成员。

这可以通过将 /usr/bin/git 替换为将调用连接到 git.exe 的垫片来完成。

所以。第一个天真的实现是:

#!/usr/bin/bash
/c/Program\ Files/Git/cmd/git.exe $@

天真的实现几乎运行良好 - 直到参数包含空格 and/or 引号(例如 git commit -m "a human comment with \"important\" hints")。然后就疯了。

所以下一步是使用 "$@" 而不是 $@,但没有任何效果,因为 git.exe 听到 "commit" 而不是 commit,并拒绝了配合。 所以我试图跳过包装第一个参数。现在 git stash list 不知道 "list" 是什么意思,因为它期望 list。 所以我遍历了所有其他表格都无济于事。

我什至尝试进入一个循环并只用引号括起包含空格的参数(并转义用户有意提供的任何引号作为她的价值的一部分) - 所以,是的 - 一个兔子洞 - 并且不能获得有效的设置。

好几天没破解我认输了——我只是不太了解bash破解这个(或wsl-win-bash关系),所以我退缩了到以下解决方案 - 这对我有用,但不太理想:

#!/usr/bin/env node

require('child_process').spawn(
  `git.exe`,
   process.argv.slice(2),
   { stdio: [0, 1, 2 ] },
);

问题在于此解决方案依赖于 MY 工作区的实现细节:nodejs。 :P

我需要你的帮助才能更上一层楼。

如果我知道一个依赖于 sh/bash 的通用解决方案,或者任何可用的 linux 上开箱即用的解决方案,那就更好了对于 WSL。

这就是答案:

首先,仅作备份:

sudo mv /usr/bin/git /usr/bin/git_linux

然后,将 /usr/bin/git 创建为:

#!/bin/bash
cmd.exe /c @ git.exe "$@"

确保它可以运行 - 你可能需要 chmod +x 它,我不记得我是否必须...


所有人向 Bender the Greatest 致敬,他在评论中指出了以下 link:


已编辑: 检查基于 NotTheDr01ds(标有 V)

的 symlink 的解决方案

我也将 post 作为答案。我无法重现这个问题,从 WSL 中调用一个 Windows-installed git.exe 二进制文件,同时使用空格和来自 bash 的转义引号似乎工作正常,只要你逃避要求二进制路径中的特殊字符带有反斜杠 \ 或双引号 ":

cd /mnt/c/some/working/gitrepo

# With backslashes in the binary path
/mnt/c/Program\ Files/Git/bin/git.exe commit -m "This commit has some spaces \"and quotes\""

# Quoting the binary path due to spaces
"/mnt/c/Program Files/Git/bin/git.exe" commit -m "This commit has some spaces \"and quotes\""

/mnt/c/Program Files/Git/bin/ 添加到 bash 中的 PATH 变量也应该有效,因此您不必为二进制文件使用完全限定名称。

除了确保 git.exe 二进制文件的路径被正确转义外,我不需要创建 shim 或做任何其他特殊操作。

Note: This is a guess and not a claim (I will remove this if disproven) but my suspicion here is that OP is using a Git binary for Windows that just isn't as compatible with direct invocation as the version I'm using. It may not even be specific to a Git version, but resultant of how it was built for a Windows target in the first place. Read on for how I arrived at this hypothesis.


一些背景:此问题不影响 WSL 中的 所有 exe 个文件 运行。根据这个 Microsoft/WSL 问题,这与其说是一个“错误”,不如说是由于 WSL/bash 将参数传递给 Windows 可执行文件的方式,以及某些程序实际上如何处理命令行输入。该解决方案不适用于所有二进制文件,OP 的答案也可能不适用于所有二进制文件。当同时处理两个 运行 时间时,这种不一致很不幸,但并不少见。

链接的问题似乎要求文档 added/updated 以便开发人员和运营商可以更好地理解 WSL 实际上如何将参数传递给本机 Windows' 可执行文件。

适合我而不是垫片的替代方案:

sudo mv /usr/bin/git /usr/bin/git_linux
sudo ln -s "$(command -v git.exe)" /usr/bin/git

这假定 git.exe 在创建 link 时位于路径中(它应该是,给定默认值)。如果不是,只需将 $(command -v git.exe) 替换为 git.exe.

的完全限定路径

传递带有转义引号和空格的字符串也适用于这种方式。

在 Unix 上,启动程序的系统调用需要并传递人类理解的命令行选项和参数作为单独的字符串:如果 unix 命令行是 git commit -m "Initial commit",参数字符串shell 传递给 Unix execve 系统调用,并由启动的程序接收,将是 gitcommit-mInitial commit

在 Windows 上,启动程序的系统调用需要并传递一个字符串参数:未消化的命令行。 Microsoft's runtime supplies a default utility often linked in to built programs that scans that command line to break it out into separate arguments,然后传递给编程语言具有的 main 的任何等价物,这就是为什么 Windows 上的命令行通常遵循那些引用和转义处理约定。

所以:当你使用 Unix shell 启动程序时,Windows 或不启动,shell 将扫描命令行进入程序名称及其参数,之后处理任何程序特定的变量赋值和 I/O 重新布线;然后它将搜索 运行 那个程序的程序文件,并使用扫描的参数和环境向 运行 它发出 execve 系统调用。

但是,如果被调用程序的加载程序是针对 Windows 程序的,它知道目标未设置为接收已解析的参数字符串:它期待未消化的命令行。因此 unix-to-windows 兼容层 中的 windows 程序加载器必须重建一个命令行 windows 程序将解析为提供的参数.

这就是你被咬的地方:你(最终)做到了

/c/Program\ Files/Git/cmd/git.exe "$@"

和 bash 传递程序路径,后跟您作为字符串 commit-ma human comment with "important" hints 提供的参数。由于 .exe 是一个 windows 程序,Windows 程序加载器启动它,并且必须重建一个命令行,接收程序将扫描该命令行以识别那些相同的参数。 . .没有任何办法知道它将如何做到这一点。

似乎 shim 确实依赖于使用双引号字符串约定的程序,但不依赖于处理内部转义的程序,因此在重建时它不会将它们添加到 "important" 周围的引号中命令行。

你必须这样做。由于您正在使用 bash,这意味着让 bash 为您完成。

你可以可能使用

/c/Program\ Files/Git/cmd/git.exe commit -m 'A human comment with \"important\" hints'

但您必须尝试一下才能确定。在所有引用处理之后,该命令行被 shell 解析为 /c/Program Files/Git/cmd/git.execommit-mA human comment with \"important\" hints;如果 Microsoft 提供的 windows 程序加载器 shim 只是将它们重新粘贴在一起,并在带有嵌入白色 space 的任何内容周围加上双引号,那就行得通了。

如果垫片连那么多作用都没有,请使用

/c/Program\ Files/Git/cmd/git.exe commit -m '"A human comment with \"important\" hints"'

相反。

然后,当然,您必须以某种方式说服 bash 对您要传递的任何参数应用适当的转换。对于我认为 git.exe 使用的默认扫描仪,您可以

/c/Program\ Files/Git/cmd/git.exe "${@//\"/\"\"}"

以它能识别的方式转义双引号。