Splitting strings

Hi.

I’m trying to extract substrings and values from a table. I need to extract the Size, Used and Avail figures so that I end up with the following:

Raw: 292.2
Size: 286.4
Used: 66.8
PctUsed: 23
Avail: 219.6
PctAvail 77

I’ve gotten this far:

$tbl = "Cluster Name: Isilon01",
"Cluster Health:     [ ATTN]",
"Cluster Storage:  HDD                 SSD Storage",    
"Size:             286.4T (292.2T Raw) 0 (0 Raw)",      
"VHS Size:         5.8T",                
"Used:             66.8T (23%)         0 (n/a)",        
"Avail:            219.6T (77%)        0 (n/a)"    

$OutTbl = @()
ForEach($t in $tbl) {
    If($t -match "Size") {
      $OutObj = "" | Select Size, Used, Avail
      $OutObj.Size = $t.Split(":",2)[1].Trim()
    }
    ElseIf($t -match "Used") {
      $OutObj.Used = $t.Split(":",2)[1].Trim()
    }
    ElseIf($t -match "Avail") {
      $OutObj.Avail = $t.Split(":",2)[1].Trim()  
      $OutTbl += $OutObj
    }
}
$OutTbl | FL

This generates the following:

Size  : 5.8T
Used  : 66.8T (23%)         0 (n/a)
Avail : 219.6T (77%)        0 (n/a)

Now I need to process further to take out the “T’s” and get split on the percentages. I thought of using the substring but the length of the values vary.

Any help or suggestions much appreciated.

If split worked once why not continuing? :wink:

ForEach ($t in $tbl) {
    If ($t -match '^Size') {
        $OutObj = "" | Select-Object -Property Size, Used, Avail
        $OutObj.Size = (($t  -split ':').trim() -split 'T')[1]
    }
    ElseIf ($t -match '^Used') {
        $OutObj.Used = (($t -split ':').trim() -split 'T')[1]
    }
    ElseIf ($t -match '^Avail') {
        $OutObj.Avail = (($t -split ':').trim() -split 'T')[1]
        $OutTbl += $OutObj
    }
}
$OutTbl | Format-List

Result would be:

Size  : 286.4
Used  : 66.8
Avail : 219.6

This will also give you the percentages. Olaf’s approach is probably better but this one worked for me as well.

$tbl = "Cluster Name: Isilon01",
"Cluster Health:     [ ATTN]",
"Cluster Storage:  HDD                 SSD Storage",    
"Size:             286.4T (292.2T Raw) 0 (0 Raw)",      
"VHS Size:         5.8T",                
"Used:             66.8T (23%)         0 (n/a)",        
"Avail:            219.6T (77%)        0 (n/a)"    

$OutTbl = @()
ForEach($t in $tbl) {
    If($t -match "Size") {
      $OutObj = "" | Select Size, Used, Avail
      $OutObj.Size = $t.Split(":",2)[1].Trim()
      $OutObj.Size =  $OutObj.Size.Substring(0,($OutObj.Size.IndexOf('T')))
    }
    ElseIf($t -match "Used") {
      $OutObj.Used = $t.Split(":",2)[1].Trim()
      $OutObj.Used =  $OutObj.Used.Substring(0,($OutObj.Used.IndexOf('T')))
      Add-Member -InputObject $OutObj -MemberType NoteProperty -Name PctUsed -Value $t.Substring($t.IndexOf('(') + 1,($t.IndexOf('%')) - ($t.IndexOf('(') + 1))
    }
    ElseIf($t -match "Avail") {
      $OutObj.Avail = $t.Split(":",2)[1].Trim()  
      $OutObj.Avail =  $OutObj.Avail.Substring(0,($OutObj.Avail.IndexOf('T')))
      Add-Member -InputObject $OutObj -MemberType NoteProperty -Name PctAvail -Value $t.Substring($t.IndexOf('(') + 1,($t.IndexOf('%')) - ($t.IndexOf('(') + 1))
      $OutTbl += $OutObj
    }
}
$OutTbl | FL

better” depends on the point of view. If the result fits who cares about the way? :wink:

1 Like

Agree with you @Olaf.

In fact, I did not notice there were two strings that match ‘Size’. Adding the ^ anchor to specify the beginning of the line was clever. I missed that one.

Olaf, Ferc,

Double splits is something that for whatever reason did not occur to me. I guess I thought it would throw a syntax error. When I saw your answer, a big light bulb went on in my head :smiley:

I borrowed from both of your suggestions and now have the following working code:

Function Get-Storage-Metrics() {
    param($tbl)
    $OutTbl = @()
    ForEach($t in $tbl) {
        If($t -match '^Size') {
            $OutObj = "" | Select Raw, Size, Used, Avail, UsedPct
            $OutObj.Raw     = (($t -split '\(').trim() -split 'T Raw')[1]
            $OutObj.Size    = (($t -split ':' ).trim() -split 'T')[1]
        }
    ElseIf($t -match "^Used") {
            $OutObj.Used    = (($t -split ':' ).trim() -split 'T')[1]
            $OutObj.UsedPct = (($t -split '\(').trim() -split '%')[1]
        }
    ElseIf($t -match "^Avail") {
            $OutObj.Avail   = (($t -split ':' ).trim() -split 'T')[1]  
            $OutTbl += $OutObj
        }
    }
    $OutTbl | FL
}

This generates the output I’m looking for:

Raw     : 292.2
Size    : 286.4
Used    : 66.8
Avail   : 219.6
UsedPct : 23

Olaf, what is the caret (^) character at the start of each -match string (i.e. ‘^Size’, etc.)?

Thank you so much to both of you for your great suggestions. Saved me a lot of time/work.

Did you try to search for it? :wink:
https://www.regular-expressions.info/anchors.html

BTW: did you notice that you actually only need to extract 2 of the values from the input data to get all the info you need? You could simply calculate the rest of it. If you have the size and used size you could calculate the free space and used percentage.

EDIT:
Speaking of this … if you treat your input file (I assumed it is an input file) as raw input you could do something like this:

$RawContent = Get-Content -Path D:\temp\rawinput.txt -Raw
$Size, $Used = [regex]::Matches($RawContent, '(?<=^Size:\s)\s+([\d.]+)(?=T)[\s\S]+(?<=^Used:\s)\s+([\d.]+)(?=T)', 3).groups[1..2].value
[PSCustomObject]@{
    Size    = $Size -as [float]
    Used    = $Used -as [float]
    Avail   = [Math]::Round(($Size -as [float]) - ($Used -as [float]), 2)
    UsedPct = [Math]::Round(($Used -as [float]) / ($Size -as [float]) * 100, 2)
} | 
Format-List

… so no loop needed, no if needed … and the result would be this:

Size    : 286,4
Used    : 66,8
Avail   : 219,6
UsedPct : 23,32

(I am on a German system. That’s why you see decimal commas instead of decimal points. :wink: )

1 Like

Glad you got it solved. I just wanted to add a few more ways to solve it. Each of these uses a switch statement to read the variable line by line. Another benefit of switch statement is being able to read a file directly.

The result of each of these is setting the five variables with the correct values and at the end of each they would be used the same way to construct the object. The first two examples are very similar and the last two are the same except the structure.

switch -Regex ($tbl){
    '^size' {
        $size,$raw = (-split $_).trim('()%T') -match '^\d.+'
    }
    '^used' {
        $used,$usedpct = (-split $_).trim('()%T') -match '^\d.+'
    }
    '^Avail' {
        $avail = (-split $_ -match '^\d.+T').trim('T')
    }
}
$size,$raw,$used,$usedpct,$avail = switch -Regex ($tbl){
                                        '^size|^used' {
                                            (-split $_).trim('()%T') -match '^\d.+'
                                        }
                                        '^Avail' {
                                            (-split $_).trim('T') -match '^\d.+'
                                        }
                                    }
switch -Regex ($tbl){
    '^size' {
        [regex]$pattern = '\d{1,}\.\d(?=T)'
        $size,$raw = $pattern.Matches($_).value
    }
    '^used.+?(\d{1,}\.\d)T.+(\d{2})' {
        $used,$usedpct = $matches.1,$matches.2
    }
    '^avail.+?(\d{1,}\.\d)(?=T)' {
        $avail = $matches.1
    }
}

$size,
$raw,
$used,
$usedpct,
$avail = switch -Regex ($tbl){
             '^size' {
                 [regex]$pattern = '\d{1,}\.\d{1,}(?=T)'
                 $pattern.Matches($_).value
             }
             '^used.+?(\d{1,}\.\d)T.+?(\d{1,})' {
                 $matches.1,$matches.2
             }
             '^avail.+?(\d{1,}\.\d)(?=T)' {
                 $matches.1
             }
         }

After the variables are set

$OutTbl = [PSCustomObject]@{
              Size    = $size
              Raw     = $raw
              Used    = $used
              UsedPct = $usedpct
              Avail   = $avail
          }

Hi Olaf,

I did several web searches for the ^ anchor, but didn’t get any clear hits. I always try to find a resolution before asking. Also, I was already here, and thanks to you I now have an answer and a new web link for future questions.

Thanks again.

The input comes from a CLI plink command used to extract storage metrics from an Isilon array.

The math functions will definitely come in handy since my next script is to extract similar metrics from a 3PAR appliance. The CLI command for this one returns only storage allocated and storage consumed.

Krzydoug,

I’ve always preferred switch to if/elseif. I will work your solution into the script.

All this helps me further my understanding of PS, so it’s greatly appreciated.

I added a link to my answer. You have seen this, didn’t you? :wink: If you missed it … there are a lot of specialized sites on the internet offering information about regex.One of them is:
https://www.regular-expressions.info
Another one where you can test your regex patterns is

Have a lot of fun! :wink:

1 Like