HyperioN
HyperioN

Reputation: 3928

Extract value from key value pair using powershell

I have a file having key-value data in it. I have to get the value of a specific key in that file. I have the Linux equivalent command:

File:

    key1=val1
    key2=val2
    ..

Command:

cat path/file | grep 'key1' | awk -F '=' '{print $2}' 

Output:

val1

I want to achieve the same output on windows as well. I don't have any experience working in power shell but I tried with the following:

Get-Content "path/file" | Select-String -Pattern 'key1' -AllMatches

But I'm getting output like this:

key1=val1

What am i doing wrong here?

Upvotes: 2

Views: 7752

Answers (2)

mklement0
mklement0

Reputation: 439692

Note:

  • With your specific input format (key=value lines), Алексей Семенов's helpful answer offers the simplest solution, using ConvertFrom-StringData; note that it ignores whitespace around = and trailing whitespace after the value.

  • The answer below focuses generally on how to implement grep and awk-like functionality in PowerShell.


It is not the direct equivalent of your approach, but a faster and PowerShell-idiomatic solution using a switch statement:

# Create a sample file
@'
key1=val1
key2=val2
'@ > sample.txt

# -> 'val1'
switch -Regex -File ./sample.txt { '^\s*key1=(.*)' { $Matches[1]; break } }
  • The -Regex option implicitly performs a -match operation on each line of the input file (thanks to -File), and the results are available in the automatic $Matches variable.

  • $Matches[1] therefore returns what the first (and only) capture group ((...)) in the regex matched; break stops processing instantly.


A more concise, but slower option is to combine the -match and -split operators, but note that this will only work as intended if only one line matches:

((Get-Content ./sample.txt) -match '^\s*key1=' -split '=')[1]

Also note that this invariably involves reading the entire file, by loading all lines into an array up front via Get-Content.


A comparatively slow version - due to using a cmdlet and thereby implicitly the pipeline - that fixes your attempt:

(Select-String -List '^\s*key1=(.*)' ./sample.txt).Matches[0].Groups[1].Value

Note:

  • Select-String outputs wrapper objects of type Microsoft.PowerShell.Commands.MatchInfo that wrap metadata around the matching strings rather than returning them directly (the way that grep does); .Matches is the property that contains the details of the match, which allows accessing what the capture group ((...)) in the regex captured, but it's not exactly obvious how to access that information.

  • The -List switch ensures that processing stops at the first match, but note that this only works with a direct file argument rather than with piping a file's lines individually via Get-Content.

  • Note that -AllMatches is for finding multiple matches in a single line (input object), and therefore not necessary here.


Another slow solution that uses ForEach-Object with a script block in which each line is -split into the key and value part, as suggested by Jeroen Mostert:

Get-Content ./sample.txt | ForEach-Object { 
  $key, $val = $_ -split '='
  if ($key -eq 'key1') { $val } 
}

Caveat: This invariably processes all lines, even after the key of interest was found.

  • To prevent that, you can append | Select-Object -First 1 to the command.

  • Unfortunately, as of PowerShell 7.1 there is no way to directly exit a pipeline on demand from a script block; see long-standing GitHub feature request #3821.

    • Note that break does not work as intended - except if you wrap your pipeline in a dummy loop statement (such as do { ... } while ($false)) to break out of.

Upvotes: 0

<# required powershell version 5.1 or later
@'
key1=val1
key2=val2
'@ | out-file d:\temp.txt
#>

(Get-Content d:\temp.txt | ConvertFrom-StringData).key1

Upvotes: 3

Related Questions