我不知道任何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-Process
cmdlet。
请参阅这篇文章的底部,以获得解释和更有力的习语。
断串插值
在"..."
(双引号字符串)中展开$_.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 ... catch
或trap
语句处理)。
为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 gc
和Start-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
}
更详细一点,也处理子模块。