如何在powershell中按列对winget列表的输出进行排序?

not*_*bit 4 powershell

尝试对 powershell 中的输出进行排序时,我没有得到预期的输出winget list。该Id列未排序。

\n
\n# winget list | Sort-Object -Property Id\n\nScreenToGif                            NickeManarin.ScreenToGif               2.37.1                             winget\nMicrosoft Visual C++ 2015-2019 Redist\xe2\x80\xa6 Microsoft.VCRedist.2015+.x64           14.28.29325.2           14.34.318\xe2\x80\xa6 winget\npaint.net                              {28718A56-50EF-4867-B4C8-0860228B5EC9} 4.3.8\nPython 3.10.0 (64-bit)                 {21b42743-c8f9-49d7-b8b6-b5855317c7ed} 3.10.150.0\nMicrosoft Support and Recovery Assist\xe2\x80\xa6 0527a644a4ddd31d                       17.0.7018.4\n-----------------------------------------------------------------------------------------------------------------------\nName                                   Id                                     Version                 Available  Source\nPaint 3D                               Microsoft.MSPaint_8wekyb3d8bbwe        6.2009.30067.0\nMicrosoft .NET SDK 6.0.402 (x64)       Microsoft.DotNet.SDK.6                 6.0.402                            winget\n3D Viewer                              Microsoft.Microsoft3DViewer_8wekyb3d8\xe2\x80\xa6 7.2010.15012.0\nMicrosoft Sticky Notes                 Microsoft.MicrosoftStickyNotes_8wekyb\xe2\x80\xa6 3.8.8.0\n
Run Code Online (Sandbox Code Playgroud)\n
\n

问:如何在 powershell 中按列winget list对输出进行排序Id

\n

我希望看到类似于 Bash 的 powershell 解决方案sort -k <column-number>,可以对任何列进行排序。我不明白为什么这个明显的功能在 powershell 中不可用?

\n

js2*_*010 7

它输出文本,而不是具有“Id”等属性的对象。这个程序的输出不是很聪明。看起来它也输出一些特殊字符,例如\xe2\x80\xa6(U+2026 HORIZONTAL ELLIPSIS)。我首先想到的是截掉前 39 个字符,然后按第 40 列(Id 开始的位置)对其进行排序。这应该就像unix中的sort -k 一样。我相信 winget 的 powershell 版本将会在未来推出。用空格替换非 ascii 并跳过前 4 行。

\n
# or -creplace \'\\P{IsBasicLatin}\'\n(winget list) -replace \'[^ -~]\',\' \' | select-object -skip 4 | \n  sort-object { $_.substring(39) }\n\nPython 3.10.0 (64-bit)                 {21b42743-c8f9-49d7-b8b6-b5855317c7ed} 3.10.150.0\npaint.net                              {28718A56-50EF-4867-B4C8-0860228B5EC9} 4.3.8\nMicrosoft Support and Recovery Assist  0527a644a4ddd31d                       17.0.7018.4\nName                                   Id                                     Version                 Available  Source\nScreenToGif                            NickeManarin.ScreenToGif               2.37.1                             winget\nMicrosoft .NET SDK 6.0.402 (x64)       Microsoft.DotNet.SDK.6                 6.0.402                            winget\n3D Viewer                              Microsoft.Microsoft3DViewer_8wekyb3d8  7.2010.15012.0\nMicrosoft Sticky Notes                 Microsoft.MicrosoftStickyNotes_8wekyb  3.8.8.0\nPaint 3D                               Microsoft.MSPaint_8wekyb3d8bbwe        6.2009.30067.0\nMicrosoft Visual C++ 2015-2019 Redist  Microsoft.VCRedist.2015+.x64           14.28.29325.2           14.34.318  winget\n
Run Code Online (Sandbox Code Playgroud)\n

尝试使用 Crescendo 解析 Winget 的Cobalt模块。没有名称属性,版本只是一个字符串(显然这些东西更具挑战性)。顶部有很多指南。

\n
install-module cobalt -scope currentuser\nget-wingetpackage | sort id\n\nID                                      Version             Available Source\n--                                      -------             --------- ------\n{04F3299A-F322-45A6-8281-046777B9C736}  21.0.3\n{0E8670B8-3965-4930-ADA6-570348B67153}  11.0.2100.60\n{0EDB70B6-EEA7-413B-BBC4-89E2CD36EFDE}  11.5.18\n#...\n7zip.7zip                               21.07               22.01     winget\nAcrylic Suite\nAcrylic Wi-Fi Home\n
Run Code Online (Sandbox Code Playgroud)\n

  • 第一列的宽度不一定是 39 个字符。这取决于控制台窗口的大小。更好的方法是从标题行确定列宽。 (2认同)
  • @zett42 这不是我的经验。另外,开头有 2 个空行,其中有 3 或 4 个退格键。我知道有时列数会有所不同。 (2认同)
  • 有趣的是,winget 仅在重定向时才使用固定的列宽。所以“substring(39)”实际上似乎有效。我只建议现在从排序的输出中过滤标题行。`温盖特| select -skip 4` 对我有用。 (2认同)

mkl*_*nt0 7

\n

为了补充现有的、有用的答案:

\n
\n

我希望看到类似于 Bash 的 powershell 解决方案sort -k <column-number>,可以对任何列进行排序。
\n我不明白为什么这个明显的功能在 powershell 中不可用?

\n
\n
    \n
  • sort实用程序不会带有( ) 的排序;它按fields排序,默认情况下,任何非空的空格都充当字段分隔符-k--key

    \n
  • \n
  • 鉴于这里不可能基于字段的解决方案 - 字段具有固定宽度,因此您无法指定分隔符-t, --field-separator) - 您必须使用它-k 1.40实现基于的排序,即(a) 远非显而易见,(b) 相当于传递{ $_.substring(39) }Sort-Object参数-Property,如 js2010 的答案所示。

    \n
  • \n
\n
\n
\n

winget list | Sort-Object -Property Id

\n
\n

虽然-Property Id如果它能工作确实会很棒,但不能指望它能与外部程序输出的文本表示一起工作:PowerShell 在管道中看到的是strings,其内容一无所知,因此不能期望它们拥有财产。 winget.exe.Id

\n

如果 提供的功能以PowerShell 原生winget.exe方式公开,[1]即通过cmdlet,它们确实会生成具有允许您使用.Sort-Object -Property Id

\n
\n

winget.exe由于其非标准行为,直接处理会带来以下挑战(另见底部部分):

\n
    \n
  • 它不尊重当前控制台的代码页,而是始终输出UTF-8编码的输出。

    \n
      \n
    • 为了弥补这一点,[Console]::OutputEncoding必须(暂时)设置为[System.Text.UTF8Encoding]::new()
    • \n
    \n
  • \n
  • 它不会根据其标准输出流是否直接连接到控制台(终端)来修改其进度显示行为;也就是说,它应该在捕获或重定向其输出时抑制进度信息,但目前还没有。

    \n
      \n
    • winget.exe为了弥补这一点,必须过滤掉作为 \ 进度显示结果的初始输出行。
    • \n
    \n
  • \n
\n

因此, js2010 的答案的改编版本将如下所示:

\n
# Make PowerShell interpret winget.exe\'s output as UTF-8.\n# You may want to restore the original [Console]::OutputEncoding afterwards.\n[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new() \n\n(winget list) -match \'^\\p{L}\' | # filter out progress-display and header-separator lines\n  Select-Object -Skip 1 |       # skip the header line\n  Sort-Object { $_.Substring(39) }\n
Run Code Online (Sandbox Code Playgroud)\n
\n

将输出winget.exe list解析为对象

\n

文本输出揭示了一个固有的限制,PowerShell 本机命令将数据输出与其表示形式分离,不会受到这种限制:用 截断属性值表示遗漏无法恢复的信息。winget.exe list\xe2\x80\xa6

\n

因此,以下解决方案受到winget.exe\ 文本输出中存在的任何信息的限制。

\n

假设已经定义了辅助函数(下面的源代码),您可以使用它将ConvertFrom-FixedColumnTable固定列文本输出转换为属性与表的列相对应的对象[pscustomobject]实例),然后允许您按属性(列),并且通常启用输出的 OOP 处理

\n
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new() \n\n(winget list) -match \'^(\\p{L}|-)\' | # filter out progress-display lines\n  ConvertFrom-FixedColumnTable |    # parse output into objects\n  Sort-Object Id |                  # sort by the ID property (column)\n  Format-Table                      # display the objects in tabular format\n
Run Code Online (Sandbox Code Playgroud)\n
\n

ConvertFrom-FixedColumnTable源代码

\n
# Note:\n#  * Accepts input only via the pipeline, either line by line, \n#    or as a single, multi-line string.\n#  * The input is assumed to have a header line whose column names\n#    mark the start of each field\n#    * Column names are assumed to be *single words* (must not contain spaces).\n#  * The header line is assumed to be followed by a separator line\n#    (its format doesn\'t matter).\nfunction ConvertFrom-FixedColumnTable {\n  [CmdletBinding()]\n  param(\n    [Parameter(ValueFromPipeline)] [string] $InputObject\n  )\n  \n  begin {\n    Set-StrictMode -Version 1\n    $lineNdx = 0\n  }\n  \n  process {\n    $lines = \n      if ($InputObject.Contains("`n")) { $InputObject.TrimEnd("`r", "`n") -split \'\\r?\\n\' }\n      else { $InputObject }\n    foreach ($line in $lines) {\n      ++$lineNdx\n      if ($lineNdx -eq 1) { \n        # header line\n        $headerLine = $line \n      }\n      elseif ($lineNdx -eq 2) { \n        # separator line\n        # Get the indices where the fields start.\n        $fieldStartIndices = [regex]::Matches($headerLine, \'\\b\\S\').Index\n        # Calculate the field lengths.\n        $fieldLengths = foreach ($i in 1..($fieldStartIndices.Count-1)) { \n          $fieldStartIndices[$i] - $fieldStartIndices[$i - 1] - 1\n        }\n        # Get the column names\n        $colNames = foreach ($i in 0..($fieldStartIndices.Count-1)) {\n          if ($i -eq $fieldStartIndices.Count-1) {\n            $headerLine.Substring($fieldStartIndices[$i]).Trim()\n          } else {\n            $headerLine.Substring($fieldStartIndices[$i], $fieldLengths[$i]).Trim()\n          }\n        } \n      }\n      else {\n        # data line\n        $oht = [ordered] @{} # ordered helper hashtable for object constructions.\n        $i = 0\n        foreach ($colName in $colNames) {\n          $oht[$colName] = \n            if ($fieldStartIndices[$i] -lt $line.Length) {\n              if ($fieldLengths[$i] -and $fieldStartIndices[$i] + $fieldLengths[$i] -le $line.Length) {\n                $line.Substring($fieldStartIndices[$i], $fieldLengths[$i]).Trim()\n              }\n              else {\n                $line.Substring($fieldStartIndices[$i]).Trim()\n              }\n            }\n          ++$i\n        }\n        # Convert the helper hashable to an object and output it.\n        [pscustomobject] $oht\n      }\n    }\n  }\n  \n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

可选阅读:潜在的winget.exe改进:

\n
    \n
  • 事实上,winget.exe不遵守控制台代码页(如chcp/所报告的[Console]::OutputEncoding)并且总是输出 UTF-8 是有问题的,但现在有些合理,因为 UTF-8 已成为跨所有平台使用最广泛的字符编码,并且能够对所有Unicode 字符进行编码,而旧版 Windows 代码页仅限于 256 个字符。其他实用程序也做出了类似的决定,尤其是node.exeNodeJS CLI(Python 也是非标准的,但选择了旧版ANSI代码页作为默认值,但可以配置为使用 UTF-8)。

    \n
      \n
    • 事实上,正是使用 UTF-8 才可以在输出中使用\xe2\x80\xa6(水平省略号字符),这是一种节省空间的方式来指示数据的省略(ASCII 替代方案是使用,即三个( )人物。U+2026....

      \n
    • \n
    • winget.exe如果您已将系统(Windows 10 及更高版本)配置为在系统范围内使用 UTF-8,则编码行为不是问题,但这会产生深远的影响 - 请参阅此答案

      \n
    • \n
    • 既然PowerShell(核心)本身始终默认为 UTF-8,您可能会争辩说,即使整个系统不使用 UTF-8 PowerShell 控制台窗口也应该 - 请参阅GitHub 问题 #7233

      \n
    • \n
    \n
  • \n
  • winget.exe应测试其stdout流是否连接到控制台(终端),然后才输出进度信息,以避免污染其stdout数据输出。

    \n
  • \n
  • 当前不可避免的超过固定列宽的列值截断可以通过选择加入机制来避免,以适合编程处理的结构化文本格式(例如 CSV)提供输出,类似于现已弃用)实用程序始终提供其选择。wmic.exe/format

    \n
      \n
    • 如前所述,如果将来 PowerShell cmdlet 提供与现有功能相同的功能,那么考虑到 PowerShell 在(强类型)数据与其可选择显示winget.exe之间的基本分离,问题甚至不会出现。代表
    • \n
    \n
  • \n
\n
\n

[1] WinGet for PackageManagement是一个针对此目的的第三方模块的示例。

\n

  • 那里有很多细节,但为什么 `winget` 不遵循现代 powershell 本机行为?毕竟,据我所知,它在每个 Windows 版本中都是默认提供的。并修复非常烦人的省略号“...”截断问题,随着使用的长命名工具的变体越来越多,该问题变得更加明显,主要是由微软... (2认同)