Git 克隆:将 stderr 重定向到 stdout,但将错误写入 stderr
Git clone: Redirect stderr to stdout but keep errors being written to stderr
git clone
将其输出写入 stderr
,如 here 所述。我可以使用以下命令重定向它:
git clone https://myrepo c:\repo 2>&1
但这会将所有输出(包括错误)从 stderr
重定向到 stdout
。有没有办法将进度消息重定向到 stdout
但错误消息仍然写入 stderr
。
您可以摆脱 stderr
。
通过这个命令:
git clone https://myrepo c:\repo 2>$null
这样一来,所有 stderr
都不会显示。
您不能显示进度,只能丢弃错误。如果命令失败,所有输出都是 stderr
如果成功 stdout
编辑:
看起来 git 命令输出将始终是 stderr
,即使命令仅在 Windows 上成功也是如此。
T.
我使用此脚本来执行 运行 git 命令。由于 git 即使成功也会写入 stderr(例如同步时拉取),这会处理这些情况并写出第一行输出,这通常是您需要知道的内容。
<#
.Synopsis
Invoke git, handling its quirky stderr that isn't error
.Outputs
Git messages, and lastly the exit code
.Example
Invoke-Git push
.Example
Invoke-Git "add ."
#>
function Invoke-Git
{
param(
[Parameter(Mandatory)]
[string] $Command )
try {
$exit = 0
$path = [System.IO.Path]::GetTempFileName()
Invoke-Expression "git $Command 2> $path"
$exit = $LASTEXITCODE
if ( $exit -gt 0 )
{
Write-Error (Get-Content $path).ToString()
}
else
{
Get-Content $path | Select-Object -First 1
}
$exit
}
catch
{
Write-Host "Error: $_`n$($_.ScriptStackTrace)"
}
finally
{
if ( Test-Path $path )
{
Remove-Item $path
}
}
}
MingW 更新提供了一种处理重定向的新方法 Git 2.15.x/2.16(2018 年第一季度)
参见 commit b2f5571, commit 1a172e4, commit 3f94442 (01 Nov 2017) by Johannes Schindelin (dscho
)。
(由 Junio C Hamano -- gitster
-- in commit 421f21c 合并,2017 年 11 月 9 日)
mingw
: add experimental feature to redirect standard handles
Particularly when calling Git from applications, such as Visual Studio's Team Explorer, it is important that stdin/stdout/stderr are closed properly.
However, when spawning processes on Windows, those handles must be marked as inheritable if we want to use them, but that flag is a global flag and may very well be used by other spawned processes which then do not know to close those handles.
Let's introduce a set of environment variables (GIT_REDIRECT_STDIN
and friends) that specify paths to files, or even better, named pipes (which are similar to Unix sockets) and that are used by the spawned Git process.
This helps work around above-mentioned issue: those named pipes will be opened in a non-inheritable way upon startup, and no handles are passed around (and therefore no inherited handles need to be closed by any spawned child).
This feature shipped with Git for Windows (marked as experimental) since v2.11.0(2), so it has seen some serious testing in the meantime.
Git documentation 现在包括:
GIT_REDIRECT_STDIN:
GIT_REDIRECT_STDOUT:
GIT_REDIRECT_STDERR:
Windows-only: allow redirecting the standard input/output/error handles to paths specified by the environment variables.
This is particularly useful in multi-threaded applications where the canonical way to pass standard handles via CreateProcess()
is not an option because it would require the handles to be marked inheritable (and consequently every spawned process would inherit them, possibly blocking regular Git operations).
The primary intended use case is to use named pipes for communication (e.g. \.\pipe\my-git-stdin-123
).
并添加:
mingw
: optionally redirect stderr/stdout via the same handle
The "2>&1
" notation in Powershell and in Unix shells implies that stderr
is redirected to the same handle into which stdout
is already written.
Let's use this special value to allow the same trick with GIT_REDIRECT_STDERR
and GIT_REDIRECT_STDOUT
: if the former's value is
2>&1
, then stderr
will simply be written to the same handle as stdout
.
The functionality was suggested by Jeff Hostetler.
用法示例:$env:GIT_REDIRECT_STDERR = '2>&1'
我修改了 Invoke-Git 以更好地满足我的需要。
根据我在寻找解决方案时阅读的许多帖子,我猜不止少数人可以使用它。
尽情享受吧。
此版本将:
- 执行传入的Git命令(假设Git已经在执行路径中。)
- 如果一切顺利,那么所有输出(stdout 和 stderr)都会显示在主机上,而不是通过 stderr。
- 检查 $LASTEXITCODE 以查看是否确实存在错误。如果出现错误,则将所有输出抛给调用者处理。
<#
.Synopsis
Invoke git, handling its quirky stderr that isn't error
.Outputs
Git messages
.Example
Invoke-Git push
.Example
Invoke-Git "add ."
#>
function Invoke-Git
{
param(
[Parameter(Mandatory)]
[string] $Command )
try {
# I could do this in the main script just once, but then the caller would have to know to do that
# in every script where they use this function.
$old_env = $env:GIT_REDIRECT_STDERR
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Host -ForegroundColor Green "`nExecuting: git $Command "
$output = Invoke-Expression "git $Command "
if ( $LASTEXITCODE -gt 0 )
{
# note: No catch below (only the try/finally). Let the caller handle the exception.
Throw "Error Encountered executing: 'git $Command '"
}
else
{
# because $output probably has miultiple lines (array of strings), by piping it to write-host we get multiple lines.
$output | Write-Host
}
}
# note: No catch here. Let the caller handle it.
finally
{
$env:GIT_REDIRECT_STDERR = $old_env
}
}
一般来说:
控制台(终端)应用程序 - 无论是在 Windows 还是类 Unix 平台上 - 只有 两个 输出流供他们使用:
- stdout(标准输出)- 这是 data(return 值") 去。
- stderr(标准错误)- 这是 其中 错误消息 并且 - 由于缺少其他流 - 任何其他不是数据的东西 都会,例如进度和状态信息。
因此,您不能也不应该根据 stderr 输出 的存在来推断成功与失败。
相反,您必须完全依赖应用程序的进程退出代码:
0
表示成功
- 任何非零值表示失败
在PowerShell中,进程退出代码反映在自动变量$LASTEXITCODE
中。
具体来说,意思是:
给定 git
的 stderr 输出行,您无法推断它们是否代表 error 消息或其他类型的非-数据信息,例如git
经常使用的进度或状态消息。
- 告诉
git
明确地将其 stderr 输出重定向到 stdout(通过在 PowerShell 中将环境变量 GIT_REDIRECT_STDERR
设置为字符串 2>&1
;$env:GIT_REDIRECT_STDERR = '2>&1'
)确实 没有 帮助,因为错误消息和 progress/status 类似的消息随后会发送到那里。
如前所述,您应该仅从 非零退出代码 .
推断失败
一个务实的方法是做以下事情:
# Invoke git and capture both its stdout and stderr streams.
$result = git clone https://myrepo c:\repo 2>&1
# Throw an error, if git indicated failure.
if ($LASTEXITCODE) { Throw "git failed (exit code: $LASTEXITCODE):`n$($result -join "`n")" }
# Output the captured result, as an array of strings
$result | % ToString
注:
| % ToString
确保只有 strings 被输出,假定 stderr 行(通过流 2
)重定向到 (>
) 成功输出流 (&1
) 包装在 System.Management.Automation.ErrorRecord
个实例中。
2>&1
在 PowerShell 7.1 及以下版本中可能会产生意想不到的副作用 - 请参阅 this answer 了解背景信息。
更好地将外部程序调用集成到 PowerShell 的错误处理中是 this RFC draft 的主题,特别是可以选择 自动中止执行 遇到非零退出代码时。
我只是想补充一点,如果像我一样,您更关心在 stderr 中获取错误且仅出现错误并且不关心任何一种方式的进展,那么有一个非常简单的解决方法 - 您只需在命令中添加 --quiet(或 -q)即可。
这告诉 git 完全停止报告进度,除非发生 实际 错误。
这是另一张可能对某些人有所启发的照片。正如其他人指出的那样,重定向和检查退出代码效果很好。与其他答案不同的地方:
- 重定向到一个文件有点烦人,因为只要 git 是 运行ning 你就看不到任何事情发生,所以只需 return 输出并使用单独的错误信息。这是一个权衡,但我更喜欢它。
- 使用 Write-Verbose 而不是 Write-Host 因为前者是可配置的
- 使用 Write-Error 产生错误而不是严厉的 Throw。使 ErrorAction、ErrorVariable 之类的东西按预期工作。
- 支持将目录设置为 运行,就像您使用
-C
而不是必须 cd 进入目录一样,但也支持通常没有该功能的命令。所以你可以做igit -dir some/path stash
。我主要在自动化脚本中使用它,否则必须 cd 进入目录很烦人。
- 使用 ValueFromRemainingArguments 等,这样就可以像直接编写 git 命令一样传递命令,因此不需要字符串,但仍然允许它。所以
igit checkout master
的工作方式类似于 igit 'checkout master'
。几乎,也就是说,因为标准 PS 警告适用:引用所以如果引号需要传递给底层命令,你仍然需要一个实际的字符串,即 igit log
'--format="%h %d "'. And PS doesn't require you to type full parameter names meaning
igit push -vwill be interpreted as
igit push -Verboseinstead of passing
-vi.e. verbose push to git. Use double dash to deal with that
igit --最后按 -v` 或写引号。
代码:
<#
.SYNOPSIS
Run git, Powershell-style.
.DESCRIPTION
By default some git commands (clone, checkout, ...) write a part of their
output to stderr, resulting in PS treating that as an error.
Here we work around that by redirecting stderr and using git's exit code
to check if something was actually wrong, and use Write-Error if that's the case,
i.e. standard PS error handling which works with -ErrorAction/-ErrorVariable etc.
The command can be passed as a string or as separate strings.
Additionally takes a $Directory argument which when used has the same effect as git -C,
but also works for clone/stash/submodule/... commands making it easier to automate those.
The $Git argument can be used to specify the executable.
.EXAMPLE
Invoke-Git status
Invoke-Git -Directory some/path status
Invoke-Git 'push -v'
Invoke-Git -Verbose -- push -v # Pass that last -v to git.
#>
function Invoke-Git {
[CmdletBinding()]
param(
[Parameter()] [Alias('Dir')] [String] $Directory = $null,
[Parameter()] [String] $Git = 'git',
[Parameter(Mandatory, Position=0, ValueFromRemainingArguments=$true)] [string] $Command
)
try {
$commandParts = $Command.Split(' ')
$subCommand = $commandParts[0]
if ($Directory -and $subCommand -eq 'clone') {
# To make all commands look alike handle this one as well.
$Command = ($commandParts + @($Directory)) -join ' '
} elseif ($Directory -and @('submodule', 'stash', 'init') -eq $subCommand) {
# These currently require one to be in the git directory so go there.
$currentDir = Get-Location
cd $Directory
} elseif ($Directory) {
if ($commandParts -eq '-C') {
# Not an error, git will pick the last one, but unexpected.
Write-Warning 'Better use either -Directory or -C, not both'
}
$Command = "-C $Directory " + $Command
}
Write-Verbose "Invoke-Git on '$Directory' with command '$Command'"
$gitRedirection = $env:GIT_REDIRECT_STDERR
$env:GIT_REDIRECT_STDERR = '2>&1'
# Deliberately not getting output here: while this means we cannot pass the actual error to Write-Error,
# it does result in all commands being shown 'live'. Otherwise when doing a clone for instance,
# nothing gets displayed while git is doing it's thing which is unexepected and too different from normal usage.
Invoke-Expression "$Git $Command"
if ($LASTEXITCODE -ne 0) {
Write-Error "git exited with code $LASTEXITCODE"
}
} finally {
$env:GIT_REDIRECT_STDERR = $gitRedirection
if ($currentDir) {
cd $currentDir
}
}
}
New-Alias -Name IGit -Value Invoke-Git -ErrorAction SilentlyContinue
git clone
将其输出写入 stderr
,如 here 所述。我可以使用以下命令重定向它:
git clone https://myrepo c:\repo 2>&1
但这会将所有输出(包括错误)从 stderr
重定向到 stdout
。有没有办法将进度消息重定向到 stdout
但错误消息仍然写入 stderr
。
您可以摆脱 stderr
。
通过这个命令:
git clone https://myrepo c:\repo 2>$null
这样一来,所有 stderr
都不会显示。
您不能显示进度,只能丢弃错误。如果命令失败,所有输出都是 stderr
如果成功 stdout
编辑:
看起来 git 命令输出将始终是 stderr
,即使命令仅在 Windows 上成功也是如此。
T.
我使用此脚本来执行 运行 git 命令。由于 git 即使成功也会写入 stderr(例如同步时拉取),这会处理这些情况并写出第一行输出,这通常是您需要知道的内容。
<#
.Synopsis
Invoke git, handling its quirky stderr that isn't error
.Outputs
Git messages, and lastly the exit code
.Example
Invoke-Git push
.Example
Invoke-Git "add ."
#>
function Invoke-Git
{
param(
[Parameter(Mandatory)]
[string] $Command )
try {
$exit = 0
$path = [System.IO.Path]::GetTempFileName()
Invoke-Expression "git $Command 2> $path"
$exit = $LASTEXITCODE
if ( $exit -gt 0 )
{
Write-Error (Get-Content $path).ToString()
}
else
{
Get-Content $path | Select-Object -First 1
}
$exit
}
catch
{
Write-Host "Error: $_`n$($_.ScriptStackTrace)"
}
finally
{
if ( Test-Path $path )
{
Remove-Item $path
}
}
}
MingW 更新提供了一种处理重定向的新方法 Git 2.15.x/2.16(2018 年第一季度)
参见 commit b2f5571, commit 1a172e4, commit 3f94442 (01 Nov 2017) by Johannes Schindelin (dscho
)。
(由 Junio C Hamano -- gitster
-- in commit 421f21c 合并,2017 年 11 月 9 日)
mingw
: add experimental feature to redirect standard handlesParticularly when calling Git from applications, such as Visual Studio's Team Explorer, it is important that stdin/stdout/stderr are closed properly.
However, when spawning processes on Windows, those handles must be marked as inheritable if we want to use them, but that flag is a global flag and may very well be used by other spawned processes which then do not know to close those handles.Let's introduce a set of environment variables (
GIT_REDIRECT_STDIN
and friends) that specify paths to files, or even better, named pipes (which are similar to Unix sockets) and that are used by the spawned Git process.
This helps work around above-mentioned issue: those named pipes will be opened in a non-inheritable way upon startup, and no handles are passed around (and therefore no inherited handles need to be closed by any spawned child).This feature shipped with Git for Windows (marked as experimental) since v2.11.0(2), so it has seen some serious testing in the meantime.
Git documentation 现在包括:
GIT_REDIRECT_STDIN:
GIT_REDIRECT_STDOUT:
GIT_REDIRECT_STDERR:
Windows-only: allow redirecting the standard input/output/error handles to paths specified by the environment variables. This is particularly useful in multi-threaded applications where the canonical way to pass standard handles via
CreateProcess()
is not an option because it would require the handles to be marked inheritable (and consequently every spawned process would inherit them, possibly blocking regular Git operations).The primary intended use case is to use named pipes for communication (e.g.
\.\pipe\my-git-stdin-123
).
并添加:
mingw
: optionally redirect stderr/stdout via the same handleThe "
2>&1
" notation in Powershell and in Unix shells implies thatstderr
is redirected to the same handle into whichstdout
is already written.Let's use this special value to allow the same trick with
GIT_REDIRECT_STDERR
andGIT_REDIRECT_STDOUT
: if the former's value is2>&1
, thenstderr
will simply be written to the same handle asstdout
.The functionality was suggested by Jeff Hostetler.
用法示例:$env:GIT_REDIRECT_STDERR = '2>&1'
我修改了 Invoke-Git 以更好地满足我的需要。
根据我在寻找解决方案时阅读的许多帖子,我猜不止少数人可以使用它。
尽情享受吧。
此版本将:
- 执行传入的Git命令(假设Git已经在执行路径中。)
- 如果一切顺利,那么所有输出(stdout 和 stderr)都会显示在主机上,而不是通过 stderr。
- 检查 $LASTEXITCODE 以查看是否确实存在错误。如果出现错误,则将所有输出抛给调用者处理。
<#
.Synopsis
Invoke git, handling its quirky stderr that isn't error
.Outputs
Git messages
.Example
Invoke-Git push
.Example
Invoke-Git "add ."
#>
function Invoke-Git
{
param(
[Parameter(Mandatory)]
[string] $Command )
try {
# I could do this in the main script just once, but then the caller would have to know to do that
# in every script where they use this function.
$old_env = $env:GIT_REDIRECT_STDERR
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Host -ForegroundColor Green "`nExecuting: git $Command "
$output = Invoke-Expression "git $Command "
if ( $LASTEXITCODE -gt 0 )
{
# note: No catch below (only the try/finally). Let the caller handle the exception.
Throw "Error Encountered executing: 'git $Command '"
}
else
{
# because $output probably has miultiple lines (array of strings), by piping it to write-host we get multiple lines.
$output | Write-Host
}
}
# note: No catch here. Let the caller handle it.
finally
{
$env:GIT_REDIRECT_STDERR = $old_env
}
}
一般来说:
控制台(终端)应用程序 - 无论是在 Windows 还是类 Unix 平台上 - 只有 两个 输出流供他们使用:
- stdout(标准输出)- 这是 data(return 值") 去。
- stderr(标准错误)- 这是 其中 错误消息 并且 - 由于缺少其他流 - 任何其他不是数据的东西 都会,例如进度和状态信息。
因此,您不能也不应该根据 stderr 输出 的存在来推断成功与失败。
相反,您必须完全依赖应用程序的进程退出代码:
0
表示成功- 任何非零值表示失败
在PowerShell中,进程退出代码反映在自动变量
$LASTEXITCODE
中。
具体来说,意思是:
给定
git
的 stderr 输出行,您无法推断它们是否代表 error 消息或其他类型的非-数据信息,例如git
经常使用的进度或状态消息。- 告诉
git
明确地将其 stderr 输出重定向到 stdout(通过在 PowerShell 中将环境变量GIT_REDIRECT_STDERR
设置为字符串2>&1
;$env:GIT_REDIRECT_STDERR = '2>&1'
)确实 没有 帮助,因为错误消息和 progress/status 类似的消息随后会发送到那里。
- 告诉
如前所述,您应该仅从 非零退出代码 .
推断失败
一个务实的方法是做以下事情:
# Invoke git and capture both its stdout and stderr streams.
$result = git clone https://myrepo c:\repo 2>&1
# Throw an error, if git indicated failure.
if ($LASTEXITCODE) { Throw "git failed (exit code: $LASTEXITCODE):`n$($result -join "`n")" }
# Output the captured result, as an array of strings
$result | % ToString
注:
| % ToString
确保只有 strings 被输出,假定 stderr 行(通过流2
)重定向到 (>
) 成功输出流 (&1
) 包装在System.Management.Automation.ErrorRecord
个实例中。2>&1
在 PowerShell 7.1 及以下版本中可能会产生意想不到的副作用 - 请参阅 this answer 了解背景信息。更好地将外部程序调用集成到 PowerShell 的错误处理中是 this RFC draft 的主题,特别是可以选择 自动中止执行 遇到非零退出代码时。
我只是想补充一点,如果像我一样,您更关心在 stderr 中获取错误且仅出现错误并且不关心任何一种方式的进展,那么有一个非常简单的解决方法 - 您只需在命令中添加 --quiet(或 -q)即可。
这告诉 git 完全停止报告进度,除非发生 实际 错误。
这是另一张可能对某些人有所启发的照片。正如其他人指出的那样,重定向和检查退出代码效果很好。与其他答案不同的地方:
- 重定向到一个文件有点烦人,因为只要 git 是 运行ning 你就看不到任何事情发生,所以只需 return 输出并使用单独的错误信息。这是一个权衡,但我更喜欢它。
- 使用 Write-Verbose 而不是 Write-Host 因为前者是可配置的
- 使用 Write-Error 产生错误而不是严厉的 Throw。使 ErrorAction、ErrorVariable 之类的东西按预期工作。
- 支持将目录设置为 运行,就像您使用
-C
而不是必须 cd 进入目录一样,但也支持通常没有该功能的命令。所以你可以做igit -dir some/path stash
。我主要在自动化脚本中使用它,否则必须 cd 进入目录很烦人。 - 使用 ValueFromRemainingArguments 等,这样就可以像直接编写 git 命令一样传递命令,因此不需要字符串,但仍然允许它。所以
igit checkout master
的工作方式类似于igit 'checkout master'
。几乎,也就是说,因为标准 PS 警告适用:引用所以如果引号需要传递给底层命令,你仍然需要一个实际的字符串,即igit log
'--format="%h %d "'. And PS doesn't require you to type full parameter names meaning
igit push -vwill be interpreted as
igit push -Verboseinstead of passing
-vi.e. verbose push to git. Use double dash to deal with that
igit --最后按 -v` 或写引号。
代码:
<#
.SYNOPSIS
Run git, Powershell-style.
.DESCRIPTION
By default some git commands (clone, checkout, ...) write a part of their
output to stderr, resulting in PS treating that as an error.
Here we work around that by redirecting stderr and using git's exit code
to check if something was actually wrong, and use Write-Error if that's the case,
i.e. standard PS error handling which works with -ErrorAction/-ErrorVariable etc.
The command can be passed as a string or as separate strings.
Additionally takes a $Directory argument which when used has the same effect as git -C,
but also works for clone/stash/submodule/... commands making it easier to automate those.
The $Git argument can be used to specify the executable.
.EXAMPLE
Invoke-Git status
Invoke-Git -Directory some/path status
Invoke-Git 'push -v'
Invoke-Git -Verbose -- push -v # Pass that last -v to git.
#>
function Invoke-Git {
[CmdletBinding()]
param(
[Parameter()] [Alias('Dir')] [String] $Directory = $null,
[Parameter()] [String] $Git = 'git',
[Parameter(Mandatory, Position=0, ValueFromRemainingArguments=$true)] [string] $Command
)
try {
$commandParts = $Command.Split(' ')
$subCommand = $commandParts[0]
if ($Directory -and $subCommand -eq 'clone') {
# To make all commands look alike handle this one as well.
$Command = ($commandParts + @($Directory)) -join ' '
} elseif ($Directory -and @('submodule', 'stash', 'init') -eq $subCommand) {
# These currently require one to be in the git directory so go there.
$currentDir = Get-Location
cd $Directory
} elseif ($Directory) {
if ($commandParts -eq '-C') {
# Not an error, git will pick the last one, but unexpected.
Write-Warning 'Better use either -Directory or -C, not both'
}
$Command = "-C $Directory " + $Command
}
Write-Verbose "Invoke-Git on '$Directory' with command '$Command'"
$gitRedirection = $env:GIT_REDIRECT_STDERR
$env:GIT_REDIRECT_STDERR = '2>&1'
# Deliberately not getting output here: while this means we cannot pass the actual error to Write-Error,
# it does result in all commands being shown 'live'. Otherwise when doing a clone for instance,
# nothing gets displayed while git is doing it's thing which is unexepected and too different from normal usage.
Invoke-Expression "$Git $Command"
if ($LASTEXITCODE -ne 0) {
Write-Error "git exited with code $LASTEXITCODE"
}
} finally {
$env:GIT_REDIRECT_STDERR = $gitRedirection
if ($currentDir) {
cd $currentDir
}
}
}
New-Alias -Name IGit -Value Invoke-Git -ErrorAction SilentlyContinue