【问题标题】:Split text by columns in PowerShell在 PowerShell 中按列拆分文本
【发布时间】:2015-03-18 14:55:30
【问题描述】:

我是 PowerShell 新手(Bash 通常是我的事),我目前正试图获取 qwinsta 输出以显示谁以“rdpwd”(rdesktop)用户身份登录,以便我可以根据用户名列表检查每个用户名,如果不匹配,请注销。

我目前正在解决两个问题:

  1. 我无法将 qwinsta 输出拆分为仅保留用户名 - 我尝试了“拆分”功能,但目前遇到语法问题或奇怪的结果;一个抱怨似乎是 '\s+' 匹配字母 S 而不是空格;其他时候我设法拆分到第二列,但只出现第 1 行的输出
  2. 虽然我还没有到那里,但我感觉我在第二步也会遇到问题,即遍历不可注销用户数组(将从本地用户组获取)

我现在将专注于问题 1!

我得到的文字是:

SESSIONNAME       USERNAME        ID     STATE   TYPE      DEVICE
services                          0      Disc
console                           1      Conn
rdp-tcp#0         user.name1      2      Active  rdpwd
rdp-tcp#1         user.name2      3      Active  rdpwd
rdp-tcp#1         user.name3      4      Active  rdpwd
rdp-tcp                           65536  Listen

我想要的输出是:

user.name1
user.name2
user.name3

(然后创建一个循环,简而言之,“foreach user in list, if not in localgroup, logoff user”。)

到目前为止,我已经使用 'rdpwd' 选择文本,但是使用“split”的各种变体,我没有比这更进一步。

我很高兴分享我已经拥有的东西,但是我认为这对任何人都没有帮助!

任何帮助将不胜感激。 :)

【问题讨论】:

  • 我假设你上面提到的文本是一个 psobject 数组?在这种情况下,您只需执行 $users = $array | select Username 即可为您提供一个仅包含 user.name1... 的数组
  • @DaneBoulton OP 说输出来自qwinsta.exe 所以这行不通

标签: regex powershell select awk


【解决方案1】:

老实说,我会寻找更好的方法来做到这一点,但您可以通过一些文本操作和 ConvertFrom-Csv cmdlet 来伪造它:

$(qwinsta.exe) -replace "^[\s>]" , "" -replace "\s+" , "," | ConvertFrom-Csv | select username

首先将所有前导空格或> 字符替换为空,然后将所有空格替换为逗号。然后您可以通过管道发送到ConvertFrom-Csv 并将数据作为对象处理。

编辑

实际上,上面有一些问题,主要是\s+,因为如果一列是空白的,它不会被正确识别为空白字段,并且下一个文本被错误地提升到当前字段。

以下是此命令的完整解析器,可能适用于本机 Windows exe 的任何类型的表格输出:

$o = @()
$op = $(qwinsta.exe)

$ma = $op[0] | Select-String "(?:[\s](\w+))" -AllMatches
$ErrorActionPreference = "Stop"

for($j=1; $j -lt $op.length; $j++) {
    $i = 0
    $obj = new-object pscustomobject
    while ($i -lt $ma.matches.count) { 
      $prop = $ma.matches[$i].groups[1].value; 
      $substrStart = $ma.matches[$i].index 
      $substrLen = $ma.matches[$i+1].index - $substrStart
      try {
        $obj | Add-Member $prop -notepropertyvalue $op[$j].substring($substrStart,$substrLen).trim() 
      }
      catch [ArgumentOutOfRangeException] {
        $substrLen = $op[$j].length - $substrStart 
        if($substrLen -gt 0) {
          $obj | Add-Member $prop -notepropertyvalue $op[$j].substring($substrStart,$substrLen).trim()
        }
        else {
          $obj | Add-Member $prop -notepropertyvalue ""
        }
      }
      $i++
    }
    $o += ,$obj
}

$o | ? { $_.type -eq 'rdpwd'} | select username

USERNAME
--------
user.name1
user.name2
user.name3

【讨论】:

    【解决方案2】:

    无法确定,但听起来您正在尝试使用字符串 .split() 方法进行正则表达式拆分。那是行不通的。使用 Powershell -split 运算符进行正则表达式拆分:

    (@'
    SESSIONNAME       USERNAME        ID     STATE   TYPE      DEVICE
    services                          0      Disc
    console                           1      Conn
    rdp-tcp#0         user.name1      2      Active  rdpwd
    rdp-tcp#1         user.name2      3      Active  rdpwd
    rdp-tcp#1         user.name3      4      Active  rdpwd
    rdp-tcp                           65536  Liste
    '@).split("`n") |
    foreach {$_.trim()} | sv x
    
    
    $x -match 'rdpwd' |
    foreach { ($_ -split '\s+')[1] }
    
    user.name1
    user.name2
    user.name3
    

    【讨论】:

      【解决方案3】:

      我对基于位置的分隔符的看法。所有其他答案都可以为您提供您正在寻找的信息,但就像 Arco 我正在寻找基于 PowerShell 对象的答案一样。这假设$data 填充了新行分隔的文本,就像您从get-content 得到的一样,可以轻松地从qwinsta.exe 拆分输出(例如$data = (qwinsta.exe) -split "`r`n"

      $headerString = $data[0]
      $headerElements = $headerString -split "\s+" | Where-Object{$_}
      $headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
      
      $results = $data | Select-Object -Skip 1  | ForEach-Object{
          $props = @{}
          $line = $_
          For($indexStep = 0; $indexStep -le $headerIndexes.Count - 1; $indexStep++){
              $value = $null            # Assume a null value 
              $valueLength = $headerIndexes[$indexStep + 1] - $headerIndexes[$indexStep]
              $valueStart = $headerIndexes[$indexStep]
              If(($valueLength -gt 0) -and (($valueStart + $valueLength) -lt $line.Length)){
                  $value = ($line.Substring($valueStart,$valueLength)).Trim()
              } ElseIf ($valueStart -lt $line.Length){
                  $value = ($line.Substring($valueStart)).Trim()
              }
              $props.($headerElements[$indexStep]) = $value    
          }
          [pscustomobject]$props
      } 
      
      $results | Select-Object sessionname,username,id,state,type,device | Format-Table -auto
      

      此方法基于标头字段的位置。没有什么是硬编码的,它都是基于这些索引和字段名称的自定义构建。使用那些$headerIndexes,我们分割每一行并将结果(如果存在)放入其各自的列中。有逻辑可以确保我们不会尝试抓取可能不存在的部分字符串并将最后一个字段视为特殊字段。

      $results 不会将您的文本包含为自定义 psobject。现在您可以像处理任何其他对象集合一样进行过滤。

      上述样本的输出

      SESSIONNAME USERNAME   ID    STATE  TYPE  DEVICE
      ----------- --------   --    -----  ----  ------
      services               0     Disc               
      console                1     Conn               
      rdp-tcp#0   user.name1 2     Active rdpwd       
      rdp-tcp#1   user.name2 3     Active rdpwd       
      rdp-tcp#1   user.name3 4     Active rdpwd       
      rdp-tcp                65536 Listen             
      

      现在我们显示type 为 rdpwd 的所有用户名

      $results | Where-Object{$_.type -eq "rdpwd"} | Select-Object -ExpandProperty username
      

      【讨论】:

        【解决方案4】:

        在第二列中打印字段 4,5 和 6。

        awk 'NR>3&&NR<7{print $2}' file
        
            user.name1
            user.name2
            user.name3
        

        【讨论】:

        • 请不要发布裸代码,同时提供解释。
        【解决方案5】:

        这里的一些答案值得称赞的是尝试将输入解析为对象,然而,这是 (a) 不平凡的努力,并且 (b) 是以牺牲性能为代价的。

        作为替代方案,考虑使用 PowerShell 的 -split 运算符进行文本解析,它在一元形式中通过类似于标准 awk 实用程序的空格将行拆分为字段在 Unix 平台上:

        在Windows上,如果先安装awk端口如Gawk for Windows,则可以直接调用awk,如Ed Morton's answer所示。在 Unix 上(使用 PowerShell Core),awk 默认可用。
        下面的解决方案与 Ed 的类似,只是它的性能不太好。

        qwinsta | % { if (($fields = -split $_)[4] -eq 'rdpwd') { $fields[1] } }
        
        • -split $_ 将手边的输入行 ($_) 拆分为一系列字段,忽略前导和尾随空格。

        • (...)[4] -eq 'rdpwd' 测试第 5 个字段(与往常一样,索引是基于 0)的感兴趣值。

        • 如果匹配,$fields[1] 然后输出第二个字段,即(假定为非空)用户名。

        【讨论】:

          【解决方案6】:

          看起来有几个答案,但这是另一个。

          你可以像这样根据位置从每一行中提取子字符串。

          $Sessions=qwinsta.exe
          $SessionCount=$Sessions.count
          [int]$x=1
          do
              {$x++
               if(($Sessions[$x]) -ne $null){$Sessions[$x].subString(19,21).Trim()}
              }until($x -eq $SessionCount)
          

          【讨论】:

            【解决方案7】:

            如果您的 shell 是 bash,请按照您应该使用的方式进行操作:

            $ awk '$NF=="rdpwd"{print $2}' file 
            user.name1
            user.name2
            user.name3
            

            警告:我不知道“powershell”是什么,但你用 awk 标记了这个问题,所以我认为“powershell”是某种 shell,并且从中调用 awk 是一种选择。

            【讨论】:

            • 几乎 可以工作(假设您在 Unix 上运行 PowerShell Core,或者在 Windows 上,您安装了 Awk 端口,例如用于 Windows 的 Gawk - 我们现在知道OP 正在使用仅限 Windows 的实用程序,qwinsta):可悲的是,PowerShell 在涉及到外部世界时遇到了引用挑战,并且需要明确的 \ -escaping 嵌入式"awk '$NF==\"rdpwd\"{print $2}' file 但是,在 PowerShell 本身中也可以使用类似的解决方案——虽然不是那么简洁而且速度肯定更慢;看我的回答。 P.S:要了解 PowerShell,请参阅microsoft.com/powershell
            【解决方案8】:

            如何使用正在运行的进程为已登录用户查找资源管理器实例? (或您知道您的用户正在运行的其他一些进程):

            Get-WmiObject -ComputerName "Machine" -Class win32_process | Where-Object {$_.Name -match "explorer"} | ForEach-Object {($_.GetOwner()).User}
            

            将提供与正在运行的资源管理器进程关联的所有用户名。

            【讨论】:

              【解决方案9】:

              [编辑:我喜欢 Matt 动态确定列名的想法,因此我将答案更新为更强大的解决方案。]

              这是一种方法:

              # Get-SessionData.ps1
              $sessionData = qwinsta
              $headerRow = $sessionData | select-object -first 1
              # Get column names
              $colNames = $headerRow.Split(' ',[StringSplitOptions]::RemoveEmptyEntries)
              # First column position is zero
              $colPositions = @(0)
              # Get remainder of column positions
              $colPositions += $colNames | select-object -skip 1 | foreach-object {
                $headerRow.IndexOf($_)
              }
              $sessionData | select-object -skip 1 | foreach-object {
                # Create output object
                $output = new-object PSCustomObject
                # Create and populate properties for all except last column
                for ( $i = 0; $i -lt $colNames.Count - 1; $i++ ) {
                  $output | add-member NoteProperty $colNames[$i] ($_[$($colPositions[$i])..$($colPositions[$i + 1] - 1)] -join "").Trim()
                }
                # Create property for last column
                $output | add-member NoteProperty $colNames[$colNames.Count - 1] ""
                # Remainder of text on line, if any, is last property
                if ( ($_.Length - 1) -gt ($colPositions[$colPositions.Count - 1]) ) {
                  $output.$($colNames[$colNames.Count - 1]) = $_.Substring($colPositions[$colPositions.Count - 1]).Trim()
                }
                $output
              }
              

              这会将命令的输出转换为您可以过滤、排序等的自定义对象。

              这意味着您可以运行以下命令来仅获取TYPE 列为rdpwd 的用户名:

              Get-SessionData | where-object { $_.TYPE -eq "rdpwd" } |
                select-object -expandproperty USERNAME
              

              输出:

              user.name1
              user.name2
              user.name3
              

              【讨论】:

                【解决方案10】:

                我喜欢Matt's answer,但是它在列标题中存在空格问题(它们通常是有问题的,但有时你无能为力)。这是一个经过调整的功能化版本来提供帮助。请注意,您可能会调整 preproc 以包括例如制表符或其他分隔符,但仍依赖于每行索引是恒定的。

                function Convert-TextColumnsToObject([String]$data)
                {
                    $splitLinesOn=[Environment]::NewLine
                    $columnPreproc="\s{2,}"
                    $headerString = $data.Split($splitLinesOn) | select -f 1
                    #Preprocess to handle headings with spaces
                    $headerElements = ($headerString -replace "$columnPreproc", "|") -split "\|" | Where-Object{$_}
                    $headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
                    $results = $data.Split($splitLinesOn) | Select-Object -Skip 1  | ForEach-Object{
                        $props = @{}
                        $line = $_
                        For($indexStep = 0; $indexStep -le $headerIndexes.Count - 1; $indexStep++){
                            $value = $null            # Assume a null value 
                            $valueLength = $headerIndexes[$indexStep + 1] - $headerIndexes[$indexStep]
                            $valueStart = $headerIndexes[$indexStep]
                            If(($valueLength -gt 0) -and (($valueStart + $valueLength) -lt $line.Length)){
                                $value = ($line.Substring($valueStart,$valueLength)).Trim()
                            } ElseIf ($valueStart -lt $line.Length){
                                $value = ($line.Substring($valueStart)).Trim()
                            }
                            $props.($headerElements[$indexStep]) = $value    
                        }
                        [pscustomobject]$props
                    }
                
                    return $results
                } 
                

                例子:

                $data= @"
                    DRIVER              VOLUME NAME
                    local               004e9c5f2ecf96345297965d3f98e24f7a6a69f5c848096e81f3d5ba4cb60f1e
                    local               081211bd5d09c23f8ed60fe63386291a0cf452261b8be86fc154b431280c0c11
                    local               112be82400a10456da2e721a07389f21b4e88744f64d9a1bd8ff2379f54a0d28
                    "@ 
                
                $obj=Convert-TextColumnsToObject $data
                $obj | ?{ $_."VOLUME NAME" -match "112be" }
                

                【讨论】:

                  【解决方案11】:

                  我编写了一个可重复使用的 ConvertFrom-SourceTable cmdlet,可在 PowerShell 库下载,源代码来自 GitHub iRon7/ConvertFrom-SourceTable 存储库。

                  $Object = ConvertFrom-SourceTable '
                  SESSIONNAME       USERNAME        ID     STATE   TYPE      DEVICE
                  services                          0      Disc
                  console                           1      Conn
                  rdp-tcp#0         user.name1      2      Active  rdpwd
                  rdp-tcp#1         user.name2      3      Active  rdpwd
                  rdp-tcp#1         user.name3      4      Active  rdpwd
                  rdp-tcp                           65536  Listen
                  '
                  

                  它非常灵活,能够读取很多表格格式,包括读取结果的输出。或者即使例如ID 列是右对齐的,这意味着它将关注整数而不是字符串:

                  $Object = ConvertFrom-SourceTable '
                     ID TYPE  USERNAME   STATE  DEVICE SESSIONNAME
                     -- ----  --------   -----  ------ -----------
                      0                  Disc          services
                      1                  Conn          console
                      2 rdpwd user.name1 Active        rdp-tcp#0
                      3 rdpwd user.name2 Active        rdp-tcp#1
                      4 rdpwd user.name3 Active        rdp-tcp#1
                  65536                  Listen        rdp-tcp
                  '
                  

                  详情见:ConvertFrom-SourceTable -?

                  【讨论】:

                    【解决方案12】:

                    一个简单的方法

                    仅获取活跃用户列表

                    $logonusers = qwinsta /server:ts33 | Out-String -Stream | Select-String "Active"
                    

                    使用 -replace 命令清除除用户之外的所有信息

                    $logonusers = $logonusers -replace("rdp-tcp") -replace("Active") -
                    replace("rdpwd") -replace("#") -replace '\s+', ' ' -replace '[0-9]',' '
                    
                    $logonusers
                    

                    然后将列出所有活跃用户。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-02-11
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多