4

我在 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 扩展为当前工作目录(相当于$PWDbash 中)时效果不佳。

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

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'})

它由节点使用cmdWindows 上的 when 执行)。

一个简单的解决方法可能是引入一个空间以防止%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:\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 命令以防止扩展(如果可以创建这样的转义函数)。

4

2 回答 2

7

要提供MC ND 的有用答案的替代方案:

如果您确实需要shell参与(这不太可能,因为您声明您希望该命令同时适用于 Windowscmd.exe Bash),请立即考虑下面的解决方案;对于绕过问题的无替代方案,请参阅底部的解决方案。

MC ND 致敬,通过建议在实例周围放置双引号%而不是实例之间 %的潜在变量名称来改进我的原始方法,并建议澄清 re execFileSync


“转义”%字符。为了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 对$-prefixed 标记执行可能不需要的扩展(这里不是问题);但是,为了保持 Windows 兼容,您必须使用双引号。

从技术上讲,将此技术应用于双引号字符串会将其分解为一系列双引号字符串,其中散布着未引用的实例。类似 POSIX 的 shell 仍将其识别为单个字符串 - 子字符串被双引号括起来并直接与实例相邻。(如果您将该技术应用于未引用的字符串,则逻辑相反:您实际上是在双引号实例中进行拼接。)子字符串周围的双引号被认为是句法元素而不是字符串的一部分,然后当子字符串连接在一起形成单个文字以传递给目标程序时删除。 %%%


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

注意:以下构建基于execFile[Sync],仅适用于调用外部可执行文件(在 OP 的情况下是正确的:git.exe)-相比之下,对于调用shell 内置命令(内部命令)或 Windows批处理文件,您无法避免exec[Sync]并因此解释为cmd.exe(在视窗)。[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 行并标记为可执行。
execFile[Sync] 用于调用 Windows批处理文件,但cmd.exe总是插入参数,如exec[Sync].

于 2015-10-08T21:35:53.243 回答
4

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

注意我错了。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%,因此它不会被扩展。

于 2015-10-08T18:21:11.937 回答