【问题标题】:How do I create function that runs a command?如何创建运行命令的函数?
【发布时间】:2018-12-18 17:05:55
【问题描述】:

所以我要做的是重新创建一个与我在 Bash 中使用的类似的函数,但在 Powershell 中:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "FAILED: $*"; }

目前我最感兴趣的部分是这里的 try() 函数。本质上它的作用是允许我用这个函数包装一个命令并让它管理退出代码。效果是这样的:

try doSomething -args

如果doSomething 以非零值退出,它会将命令输出到stderr 并停止脚本执行。

我意识到 Powershell 有一个可用于中止脚本的错误操作,但它似乎只适用于命令行开关。我需要一些可以在整个脚本中使用的东西。我还希望避免大量冗长的 try/catch 逻辑使脚本变得混乱,因此需要像 try/yell/die 这样优雅的东西。这样,我可以单独在这个函数中编写处理,然后用它来调用我想要处理的任何东西。

我找到了$MyInvocation 并认为这可能是其中的方法,但我似乎无法找到从函数中实际执行它的方法。例如:

function run() {
    $MyInvocation # ?? what do??
}

run doSomething -args

我想我可以自己解决剩下的问题,我只是不太清楚如何编写这个包装函数。有什么想法吗?

更新

所以我做了一些俗气的事情,我对命令进行了子串化,并对剩下的内容做了一个Invoke-Expression,它似乎有效。感觉超级hacky,所以我仍然对想法持开放态度:

function attempt() {
    $thisCommand = $MyInvocation.Line.Trim()
    Write-Output $thisCommand
    Invoke-Expression $thisCommand.Substring(8)
    if($LASTEXITCODE -ne 0) { 
        throw "Command failed $thisCommand" 
        exit 111
    }
}

attempt doSomething -args

【问题讨论】:

    标签: linux powershell-core powershell-v6.0


    【解决方案1】:

    如果您希望函数运行任意命令并在该命令失败时抛出错误,您可以执行以下操作:

    function Test-Command {
        try {
            $cmd, $params = $args
            $params = @($params)
            $global:LastExitCode = 0
            $output = & $cmd @params 2>&1
            if ($global:LastExitCode -ne 0) {
                throw $output
            }
            $output
        } catch {
            throw $_
        }
    }
    

    细分:

    $cmd, $params = $args 采用automatic variable $args(传递给函数的参数数组)并将其第一个元素分配给变量$cmd,其余的分配给变量$params

    $params = @($params) 确保 $params 包含一个数组(下一步需要),即使该变量为空或仅包含一个值。

    & $cmd @params 使用call operator &splatting 参数调用命令。 Do NOT use Invoke-Expression.

    redirection operator2>&1 将错误输出与正常输出合并,以便在变量$output 中捕获两个输出流。

    如果$cmdPowerShell cmdlet 错误将引发异常,该异常被try 语句捕获。然后将跳过 try 块中的其余代码。但是请注意,并非所有 PowerShell cmdlet 引发的错误都会自动终止错误(例如,请参阅 Scripting Guy 博客上的 "An Introduction to Error Handling in PowerShell")。要将非终止错误转换为终止错误,您需要设置 $ErrorActionPreference = 'Stop'(并在完成后将其重置为原始值)。

    如果$cmd外部命令,错误不会引发异常,但自动变量$LastExitCode 会使用命令的退出代码进行更新。返回非零退出代码的命令将触发if 条件并使用命令输出作为异常消息引发自定义异常。然后,try 语句也会捕获该异常。再次跳过try 块中的其余代码。

    $global:LastExitCode = 0 在每次运行前重置变量$LastExitCode。这是必要的,因为只有外部命令会返回退出代码,而 PowerShell cmdlet 不会。由于$LastExitCode 保留了当前会话中最后一次运行的外部命令的退出代码,因此不重置变量会影响在外部命令之后运行的 PowerShell cmdlet 的状态检测。

    try 块中的最后一行回显捕获的命令输出,只有在命令既不引发异常也不返回非零退出代码时才到达。

    任何捕获的异常都在catch 块中处理,它只是将异常传递给函数的调用者。当然,您也可以输出错误并退出,而不是抛出。

    【讨论】:

    • 当我用Test-Command echo foo 测试它时,它似乎为“foo”中的每个字符运行一次命令,所以我在单独的行上得到了 'f' 'o' 'o' 每个字符。对于大多数命令来说,这似乎是正确的。例如Test-Command dotnet --info 也不起作用,因为它认为每个破折号都是一个参数
    • 我假设我说错了。这是否需要我将参数作为哈希表发送?
    • 哦,我知道如果我通过它调用 powershell 函数,它会起作用。有没有办法让它与其他命令一起工作?
    • FWIW 在实际的 linux 环境中,$global:LastExitCode$LASTEXITCODE 似乎都无法在 PWSH 中工作。我不得不使用$? 代替它返回真/假,以确定最后一个退出代码是否是成功代码。
    • @Sinaesthetic 你的问题没有提到你打算在 Linux 上运行它,所以我只在我的 Windows 测试盒上测试了代码。请不要在您的问题中省略类似的相关细节。
    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-13
    • 2021-01-03
    相关资源
    最近更新 更多