Converting hashtable to array of strings

后端 未结 5 2058
一整个雨季
一整个雨季 2021-02-18 23:33

How can I convert a hashtable to an array of strings? Suppose $l_table is a hashtable. If I try

$l_array = $l_table | format-table

then $l_arra

相关标签:
5条回答
  • 2021-02-18 23:55
    [string[]]$l_array = $l_table | out-string -stream
    
    0 讨论(0)
  • 2021-02-19 00:00

    David I. McIntosh's own answer works well, but it should be noted that the elements of the resulting array correspond to all lines of the default output, which includes:

    • the empty leading and trailing lines
    • the two header lines (the line with the column names and the separator line)

    Out-String simply sends what you'd normally see in the console (terminal) to a string, by default as a single string, and with -Stream as an array of strings.

    Here's a variation of David's command that removes both the header and the empty lines:

    [string[]] $l_array = ($l_table | Out-String -Stream) -ne '' | select -Skip 2
    

    The rest of this answer shows how to control the specifics of the string representation obtained; it uses PS v3+ syntax and built-in alias % for ForEach-Object for brevity.

    Note: For clarity, the sample input hashtable is called $ht (not $l_table) in the following examples; the PSv4+ .ForEach() variants perform better.

    • Get all keys as a string array:

        $ht.Keys | % ToString # or (PSv4+): $ht.Keys.ForEach('ToString')
      
    • Get all values as a string array:

        $ht.Values | % ToString # or (PSv4+): $ht.Values.ForEach('ToString')
      
    • Get a custom representation of key-value pairs, in format <key>=<value>; note that .GetEnumerator() is needed to send the key-value pairs individually through the pipeline; by default, PowerShell passes a hashtable as a whole:

        $ht.GetEnumerator() | % { "$($_.Name)=$($_.Value)" }
      
        # or (PSv4+):
        $ht.GetEnumerator().ForEach({ "$($_.Name)=$($_.Value)" })
      

    Note that while .ToString(), which is also applied implicitly during string interpolation (inside "...", an expandable string), works well with primitive .NET types (as well as additional numeric types such as [decimal] and [bigint]); generally, types will just print their full type name, unless their .ToString() method is explicitly overridden to return a more meaningful custom representation (which is what the primitive types do and which is the case with only some of the non-primitive types returned by PowerShell cmdlets).
    Also note that using an array(-like data structure) inside an expandable string expands to the (stringified) elements concatenation of its elements with the value of the $OFS preference variable, which defaults to a space char. (e.g., $a='one', 'two'; "$a" expands to 'one two') - see this answer for more information on expandable strings (string interpolation) in PowerShell.

    A simple example of choosing a property of a value to represent it in the string:

    # Sample hashtable containing a value of a non-built-in type,
    # [System.Diagnostics.Process]    
    $ht = @{ one = 1; two = Get-Process -ID $PID }
    
    # Use the `.Path` property to represent the value.
    $ht.GetEnumerator()  | % { "$($_.Name)=$($_.Value.Path)" }
    
    0 讨论(0)
  • 2021-02-19 00:00

    This is a common problem; Hashtable must be retrieved by either keys or values but you can't get a collection of pairs - that is what the original hashtable is, after all. In my example, the hashtable is represented by a "Dictionary" (you will remember from VBS :-). However you will need to determine a separator to delimit keys & values, in this case "Key<->Value"

    $ARRAY = ( $DICT.Keys | foreach-object { "$_<->$($DICT[$_])"})
    

    You can even sort the keys first if you want. In this case I reversed to Value<--->Key and sorted by values. I've also expanded the string a little to make more verbose but legible:

    $ARRAY = ( $DICT.Keys | foreach-object { $DICT[$_] + "<--->" + $_} | sort-object)
    
    0 讨论(0)
  • 2021-02-19 00:03

    While the original poster might have wanted an array of strings corresponding to the output of Format-Table, as the supplied attempts and poster's own answer/comments might suggest, the wording of the question heading is more typical of someone wishing to convert the hash table itself into an array of each item's value (converted to String if not already a String). For those looking for a solution to this problem I present the following (since there doesn't seem to be a similar question as yet):-

    The simplistic answer is to use

    [string[]]$l_table.values
    

    and this does give an array of strings as desired.

    Note: $l_table.values is a [System.Collections.Hashtable+ValueCollection] which is why the conversion to [string[]] is necessary even if the values are already strings. For example,

    'Found {0} with submatch {1}' -f [string[]]$matches.values
    

    won't work without it. (The other possible problem is mentioned next.)

    The major flaw with $l_table.values is that, unless the hash table was defined to be [ordered] (PSv3+), the order of the items is undefined (and can change as the hash table is modified). Often, when converting a hash table to an array, it is desired to have a certain ordering of the elements in that array. Indeed, sometimes the keys are (positive) integers and the intent is that the resultant array use the same values as array indexes (see $matches example above). To create this type of array, use

    [string[]]$l_table[($l_table.keys | sort)]
    

    or, if the values are already strings, just

    $l_table[($l_table.keys | sort)]
    

    to invoke Powershell's collection slicing functionality (whereby a single expression can produce an array of independently selected collection items by using an array expression as the index. E.g. $array[1,3,5,9], $array[1,2,3,4,5] or $array[1..5]). Note that this will only produce an array with the keys as the indexes if the keys form a contiguous range starting at 0. However, since the indexing expression is a pipeline, it is possible to get just about anything as the required array of keys. To get a resultant array from a 'sparse' hash table (of non-string values) use

    [string[]]$l_table[0..($l_table.keys | sort -descending)[0]]
    #
    # ($l_table.keys | sort -descending) produces a sorted array of key values
    # with the largest value in element 0
    #
    

    Now the resultant array will have the integer keys correctly corresponding to the appropriate index value by having any intervening (unused) array items set to "" (i.e. [string]$null). If this is a problem then a two step process can be used to leave the 'missing' entries as $null. Firstly, convert a non-string hash table to strings without adding entries by building a new hash table one dictionary pair at a time (using GetEnumerator()). Secondly, don't use [string[]] in order to leave $null in the 'unused' items when converting the (now string) hash table to an array as follows

    ($l_table.getenumerator() | foreach -begin {$str_table = @{}} -process {$str_table.add($_.key,[string]$_.value)} -end {$str_table[0..($str_table.keys | sort -descending)[0]]})
    

    or perhaps more efficiently (by removing the potentially costly sort)

    ($l_table.getenumerator() | foreach -begin {$maxkey = 0; $str_table = @{}} -process {$str_table.add($_.key,[string]$_.value); if ($maxkey -lt $_.key){$maxkey = $_.key}} -end {$str_table[0..$maxkey]})
    

    Note: for a sparse hash table of string values the initial form without conversion

    $l_table[0..($l_table.keys | sort -descending)[0]]
    

    will work but the use of [string[]] will change any missing entries from $null to "" if desired.

    0 讨论(0)
  • 2021-02-19 00:05

    The hash is just a Hashtable so it has a keys and a values property.

    $hash = @{}
    
    $hash["foo"] = "bob"
    
    $hash.Values
    
    $hash.Values | Out-string
    

    If you want to get the enumerator it will return you the keyvaluepair

    $hash.GetEnumerator() |%{$_ | out-string}
    
    0 讨论(0)
提交回复
热议问题