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

Is there a way to prevent percent expansion of env variable in Windows command line?

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

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

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

虽然它在 git bash 中工作正常,但在 cmd 中效果不佳:%cd% 被 Windows [=77 扩展=] 进入当前工作目录(相当于 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

命令:

D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg

实际上,我自己从来没有直接使用过 cmd,我在编写一个包含

的 nodejs (0.12) 脚本时发现了这种行为
require('child_process').execSync('git log --format=...', {stdio: 'inherit'})

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

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

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

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

但是这引入了多余的 space(我在 %s 之前删除了另一个 space,但它仍然是 hack,需要手动干预)。

有没有办法阻止 Windows shell 的扩展?

我有 found 有关使用 %%^% 转义 % 的信息,但它们不是这里的解决方案:

# 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:\git\someFolder commit msg
%D:\git\someFolder commit msg
%D:\git\someFolder commit msg
%D:\git\someFolder commit msg
%D:\git\someFolder commit msg

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

总之,你做不到或者至少没有通用的方法

注意我错了。 指出了通用解决方案的方法。

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

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

但是正如您所指出的那样,插入符号不会被解析器使用,而是包含在输出字符串中。

因此,任何 "escape" 字符都将包含在 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 解析器执行变量时扩展,正确的值在命令行结束。

但是,也许(我没有检查 how/what git 代码)更改 cd 变量可能会产生干扰。因此,同样对于这种情况,您可以使用

而不是更改 cd 变量的值
process.env['C(cyan)'] = '%C(cyan)%';

为什么?当 cmd 解析器处理命令行时,它会将格式字符串的开头与现有变量进行匹配,进行扩展,并在同一过程中使用 %cd% 变量中的起始百分号,所以它没有展开。

提供 的替代方案:

如果你真的需要来让shell参与(这不太可能,因为您声明您希望命令与 Windows' cmd.exe Bash) 一起使用,请考虑下面的解决方案;对于shell-less替代方案绕过问题,请参阅解决方案底部.

感谢 MC ND 通过建议在 % 个实例周围放置双引号而不是 之间的潜在变量名来改进我原来的方法 % 个实例,并建议对 execFileSync.

进行澄清

"Escaping" % 个字符。 cmd.exe

所述,从技术上讲,您无法在 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-like(POSIX-like shell如Bash):shell 本身负责在将双引号传递给目标程序之前删除双引号。

    • 警告:在您的命令中使用 双引号 通常意味着您需要注意 POSIX-比如 shells 对 $ 前缀的标记执行可能不需要的扩展(这里不是问题);但是,为了保持 Windows 兼容,您 必须 使用双引号。

从技术上讲,将此技术应用于双引号字符串会将其分解为一系列双引号 子字符串 穿插 未引号 % 个实例。 POSIX-like shells 仍然将其识别为 单个 字符串 - 子字符串是双引号并直接邻接 % 实例。 (如果将该技术应用于 未加引号的 字符串,则逻辑相反:您有效地拼接了双引号 % 实例。)子字符串周围的双引号, 被认为是句法元素而不是字符串的一部分,然后在将子字符串连接在一起形成单个文字以传递给目标程序时将其删除。


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

注意:以下内容基于execFile[Sync],仅适用于调用外部可执行文件(在 OP 的情况下为真:git.exe) - 相比之下,用于调用 shell builtins(内部命令)或 Windows 批处理文件,你无法避免exec[Sync]并因此被cmd.exe解释(在Windows)。[1]

如果您使用execFileSync而不是execSyncshell(cmd.exe在Windows) 将不会涉及 因此您 不必担心转义 % 个字符。 或任何其他 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 line 并标记为可执行。
execFile[Sync] 可以 用于调用 Windows 批处理文件 ,但 cmd.exe 总是插入参数,因为exec[Sync].