有没有办法防止在Windows命令行中env变量的百分比扩展?



我在Windows上的git bash中使用以下git命令:

git log --format="%C(cyan)%cd%Creset %s" --date=short -5

显示提交日期(%cd)和提交消息(%s)。提交日期用彩色标记包裹:%C(cyan)开始彩色输出,%Creset停止彩色输出。

虽然它在git bash中工作得很好,但它在cmd中工作得不好:%cd%被Windows shell扩展到当前工作目录(相当于bash中的$PWD)。

因此,当通过cmd运行该命令时,我在第一列中看到的是当前工作目录,而不是提交日期!git bash:

2015-10-08 commit msg
2015-10-08 commit msg
2015-10-07 commit msg
2015-10-06 commit msg
2015-10-06 commit msg

cmd:

D:gitsomeFolderCreset commit msg
D:gitsomeFolderCreset commit msg
D:gitsomeFolderCreset commit msg
D:gitsomeFolderCreset commit msg
D:gitsomeFolderCreset commit msg

实际上,我自己从来没有直接使用过cmd,我在编写nodejs(0.12)脚本时发现了这种行为,其中我有

require('child_process').execSync('git log --format=...', {stdio: 'inherit'})

在Windows上由使用cmd的节点执行

一个简单的解决方法是引入一个空格来防止%cd%被发现,即更改

git log --format="%C(cyan)%cd%Creset %s" --date=short -5

git log --format="%C(cyan)%cd %Creset%s" --date=short -5

然而,这引入了一个冗余空间(我在%s之前删除了另一个空间,但它仍然是一个hack,需要人工干预)。

有没有办法防止Windows shell扩展?

我找到了关于使用%%^%来逃避%的信息,但它们不是这里的解决方案:

# produces superfluous ^ characters
git log --format="%C(cyan)^%cd^%Creset %s" --date=short -5
^2015-10-08^ commit msg
^2015-10-08^ commit msg
^2015-10-07^ commit msg
^2015-10-06^ commit msg
^2015-10-06^ commit msg
# seems the expansion is done at command parse time
git log --format="%C(cyan)%%cd%%Creset %s" --date=short -5
%D:gitsomeFolder commit msg
%D:gitsomeFolder commit msg
%D:gitsomeFolder commit msg
%D:gitsomeFolder commit msg
%D:gitsomeFolder commit msg

理想的解决方案应该与bash和cmd兼容,而不产生多余的字符,或者在javascript中使用转义函数来转义Windows的通用UNIX-y命令,以防止展开(如果可以创建这样的转义函数)。

提供一个替代MC ND的有用答案:

如果您真的需要shell参与(这不太可能,因为您声明您希望该命令同时与Windows的cmd.exe Bash一起工作),请考虑下面的解决方案;对于非shell 替代方案,绕过问题,请参阅底部的解决方案。

向MC ND致敬,因为它改进了我的原始方法,建议在%实例周围加上双引号,而不是在 %实例之间加上潜在的变量名,并建议对execFileSync进行澄清。


"转义" %字符。for cmd.exe

正如MC ND的回答所述,从技术上讲,你不能在Windows命令提示符中逃避%(在批处理文件中,你可以使用%%,但这在从其他环境(如Node.js)调用shell命令时不起作用,并且通常不能跨平台工作)。

然而,解决方法是在每个%实例:

周围放置双引号。
// Input shell command.
var shellCmd = 'git log --format="%C(cyan)%cd%Creset %s" --date=short -5'
// Place a double-quote on either end of each '%'
// This yields (not pretty, but it works):
//   git log --format=""%"C(cyan)"%"cd"%"Creset "%"s" --date=short -5
var escapedShellCmd = shellCmd.replace(/%/g, '"%"')
// Should work on both Windows and Unix-like platforms:
console.log(require('child_process').execSync(escapedShellCmd).toString())

插入的双引号防止cmd.exe将诸如%cd%之类的标记识别为变量引用("%"cd"%"不会被展开)。

这是有效的,因为在目标程序处理时,额外的双引号最终会从字符串中删除:

  • Windows: git.exe(可能是通过C运行时)然后负责从组合字符串中去掉额外的双引号。

  • 类unix (类posix shell,如Bash): shell自己会在将双引号传递给目标程序之前删除它们。

    • 警告:在命令中使用双引号通常意味着您需要注意类似posix的shell对$前缀的令牌执行潜在的不必要的扩展(这里不是问题);但是,为了与windows兼容,必须使用双引号。

从技术上讲,将此技术应用于双引号字符串将其分解为双引号子字符串序列,其中穿插着未引号 %实例。类posix shell仍然将其识别为单个字符串-子字符串是双引号,并且直接关于%实例。(如果将该技术应用于未加引号的字符串,则逻辑相反:您实际上是在双引号的%实例中进行拼接。)子字符串周围的双引号被认为是语法元素,而不是字符串的一部分,当子字符串连接在一起形成传递给目标程序的单个字面值时,将被删除。


通过完全避免shell来绕过这个问题

注意:以下构建在execFile[Sync]之上,它只适用于调用外部可执行文件(在OP的情况下:git.exe) -相反,对于调用shell内置文件(内部命令)或Windows 批处理文件,您不能避免exec[Sync],因此由cmd.exe(在Windows上)解释。[1]

如果使用execFileSync 而不是execSync,则shell (Windows上的cmd.exe)将不涉及,因此您不必担心转义%字符。或任何其他shell元字符:

require('child_process').execFileSync('git', 
   [ 'log', 
     '--format=%C(cyan)%cd%Creset %s',
     '--date=short',
     '-5' ], {stdio: 'inherit'})

注意参数必须单独作为数组的元素,并且没有嵌入引号


[1]在Windows上,脚本文件(例如,Python脚本)不能用execFile[Sync]直接调用,但您可以将解释器可执行文件(例如,python)作为要执行的文件,并将脚本文件作为参数传递。在类unix平台上,可以直接调用脚本,只要它们有shebang行并被标记为可执行。
execFile[Sync] 可以用来调用Windows 批处理文件,但是cmd.exe总是插入参数,就像exec[Sync]一样。

总之,

你不能这样做或者至少没有一个通用的方法

注释我错了。Mklement0的回答为一般解决方案指明了一条道路。

当然,您可以使用^,而不是转义百分号(您不能在命令行级别转义百分号),而是将百分号与变量名分开,这样就不会将其检测为要展开的变量。也就是说,对于您的情况,使用

就足够了。
git log --format="%C(cyan)%cd^%Creset %s" --date=short -5

,但是当您指向时,插入符号不会被解析器使用,而是包含在输出字符串中。

因此,在cmdbash中,任何"转义"字符都将包含在输出中,但是可以使用 解决这种特殊(或类似)情况。
process.env.cd = '%cd%'
require('child_process').execSync(
    'git log --format="%C(cyan)%cd%Creset %s" --date=short -5'
    , {stdio: 'inherit'}
)

基本上,代码所做的是为cd环境变量分配一个假值,将其更改为文字%cd%,因此,当cmd解析器执行变量展开时,正确的值将在命令行结束。

但是,也许(我没有检查git代码如何/做什么)改变cd变量可能会干扰。同样,在这种情况下,不需要改变cd变量的值你可以使用

process.env['C(cyan)'] = '%C(cyan)%';

为什么?当cmd解析器处理命令行时,它将根据现有变量匹配格式字符串的开头,进行展开,并在相同的过程中消耗%cd%变量中的起始百分号,因此它不会展开。

最新更新