要在作为 git 存储库的所有子目录中运行"git gc"的 Powershell 命令



我不知道任何PowerShell,但我知道如何搜索、复制和粘贴。我认为下面的命令将为所有属于git-reos的子目录运行"git-gc"。

dir -recurse -include .git | %{ git -C "$_.FullName.." gc }

有什么问题吗?

我使用"git-C"功能来设置要运行的目录。如何在特定目录中执行命令?

% { Set-Location $_.FullName; git gc }

% { Start-Process git -WorkingDirectory $_.FullName -ArgumentList gc -NoNewWindow -Wait}

第一个变体将更改您当前的工作目录,请注意这一点。

tl;dr

断串插值

dir -recurse -directory -force -filter .git | % { git -C "$($_.FullName).." gc }

也就是说,简单地将$_.FullName封装在双引号字符串内的$(...)中就足以解决问题(再加上将-Force添加到dir(Get-ChildItem)以确保它找到隐藏的.git子目录
添加-directory以限制与目录的匹配,并使用-filter而不是-include并不是严格需要的,但可以获得更好的性能)。

了解双引号字符串中$(...)的需求通常很重要-请参阅下文。

对于手头的任务,以上是一个简单、稳健且高效的解决方案:通过-C选项将所需目录的目标留给git,而无需更改调用会话的当前目录。

调用命令前更改到其他目录

4c74356b41的答案通过关注OP问题的第二部分——如何在调用命令之前更改到不同的位置——绕过了这个问题,同时避免了断串插值("$_.FullName..")。然而,他们的(第一个)解决方案是有效的,因为git也通过其.git子文件夹来识别存储库,因此不需要引用父目录的..部分。

尽管如此,他们的第一个命令有更改会话当前目录的副作用,对于控制台应用程序(如git),最好避免使用Start-Processcmdlet。

请参阅这篇文章的底部,以获得解释和更有力的习语。


断串插值

"..."(双引号字符串)中展开$_.FullName将无法按原样运行:由于您正在访问$_变量引用的对象的属性,因此必须将表达式包含在子表达式运算符$(...)中,如下所示:

"$($_.FullName).."

顺便说一句:在您的情况下,实际上根本不需要字符串插值,因为git也通过其.git子文件夹来识别repo,所以不需要引用父目录的..组件;直接引用$_.FullName就足够了:
... | % { git -C $_.FullName gc }

"$_.FullName.."是如何展开(插值)的,为什么会被破坏

  • 管道变量$_通过调用值的.ToString()方法,由自身扩展

    • $_在这种情况下是类型[System.IO.DirectoryInfo]的实例,对其调用.ToString()只会产生给定目录的名称,而不是其路径(例如,如果$_表示C:Usersjdoe,则"$_"扩展为仅joe)
  • .FullName..被解释为字符串的文字部分。

假设$_表示目录C:Usersjdoe;因此,"$_.FullName.."扩展到以下内容,这显然不是目的:

jdoe.FullName..

换句话说:在双引号字符串中,$_.FullName不会被识别为单个表达式,除非它被包含在子表达式运算符$(...)中。

字符串的校正形式"$($_.FullName)/.."产生预期结果:

C:Usersjdoe..

以下是可以按原样运行的测试命令,说明了区别:

  • dir -recurse $HOME | % { "$_.FullName.." } | Select -First 2 # WRONG
  • dir -recurse $HOME | % { "$($_.FullName).." } | Select -First 2 # OK

有关PowerShell字符串插值(扩展)规则的完整讨论,请参阅我的答案。


此外,PowerShell(一般为Windows)处理文件系统路径的一个怪癖可能会掩盖字符串插值的问题:

..附加到不存在的路径组件仍然会产生有效路径-这两个组件有效地相互抵消。

如果我们使用上面的字符串插值错误示例:

Set-Location jdoe.FullName..

是一个有效的无操作,因为PowerShell从词法上得出结论,您指的是当前目录(假定jdoe.FullName..相对路径),即使不存在名为jdoe.FullName的子目录。

因此,在使用类似的东西的上下文中:

... | % { Set-Location "$_.FullName.."; git gc }

Set-Location命令无效,所有git调用都在当前目录中运行


在管道的每次迭代中更改为不同的目录

对于控制台(命令行)实用程序(如git)的同步调用,以下技术是最佳选择:

dir -recurse -directory -force -filter .git | % { pushd $_.FullName; git gc; popd }

请注意git调用是如何夹在pushd(Push-Location)和popd(Pop-Location)调用之间的,这可确保主叫会话的当前位置最终不会更改。

注意:如果在pushd调用之后发生终止错误,则当前位置仍将更改,但请注意,调用外部实用程序永远不会导致终止错误(只有PowerShell本机调用和.NET框架方法可以生成终止错误,可以使用try ... catchtrap语句处理)。

为GUI应用程序或新控制台窗口设置工作目录

Start-Process及其-WorkingDirectory参数在以下情况下是正确的工具:

  • 显式控制台窗口中运行命令

    • 示例:Start-Process cmd -WorkingDir $HOME在当前用户的主目录中异步打开一个新的cmd.exe控制台("命令提示符")
  • 启动带有特定工作目录的GUI应用程序

    • 示例:Start-Process notepad.exe -WorkingDir $HOME异步启动记事本,将当前用户的主目录设置为工作目录

注意:

默认情况下,
  • Start-Process异步,这意味着它启动指定的命令,但不等待完成后再返回PowerShell提示符(或转到脚本中的下一个命令)
    如果您确实想等待进程终止(通常在窗口关闭时发生),请添加-Wait

  • 使用Start-Process -NoNewWindow -Wait当前控制台窗口中运行控制台应用程序通常没有什么意义(如果没有-Wait,输出将异步到达,因此在控制台中不可预测):

    • 调用的控制台应用程序不会连接到当前会话的输入和输出流,因此它不会从PowerShell管道接收输入,并且它的输出不会被发送到PowerShell的成功和错误流(这意味着你不能通过管道发送输出,也不能用>>>捕获它;但是,你可以通过-RedirectStandardInput发送一个文件作为输入,类似地,用-RedirectStandardOutput-RedirectStandardError捕获文件中的stdout和stderr输出)

    • 此外,直接调用不仅在语法上更简单(与git gcStart-Process gc -ArgumentList gc -NoNewWindow -Wait相比),而且明显更快。

    • 在控制台应用程序中使用Start-Process唯一有意义的场景是:(a)在新控制台窗口中启动它(省略-NoNewWindow),(b)作为不同的用户运行它(使用-Verb-Credential参数),或(c)在原始环境中运行它(用-UseNewEnvironment)。

设置后台命令的工作目录

最后,Start-Job是的正确工具

  • 异步启动无UI(非交互式)后台进程

    • 示例:Start-Job { Set-Location $HOME; $PWD };注意工作目录必须如何在定义要运行的后台命令的脚本块{ ... }内显式设置
      Receive-Job必须随后调用才能获得后台命令的输出
dir -recurse -directory -force -filter .git | % { 
pushd "$($_.FullName).."
write-output '___'
$_.FullName
git count-objects -vH
git submodule foreach --recursive git count-objects -vH
git  gc 
git submodule foreach --recursive git gc 
git count-objects -vH
git submodule foreach --recursive git count-objects -vH
write-output  '___'
popd
}

更详细一点,也处理子模块。

最新更新