Search code examples
powershellcommand-linecloud-foundrytext-parsing

How do I extract fields from the tabular text output of the Cloud Foundry CLI?


I have a Cloud Foundry CLI command, cf apps, that outputs:

name       requested state   processes   routes
appname_1   started           web:2/2    route1.com
appname_2   started           web:1/1    route2.com

How do I access all name column values, and then loop over them?

The idea is to get the list of all app names and run the cf delete <app_name> so that I can avoid the hassle of having to delete the apps one by one.

I tried the following script earlier

Fetch apps

$apps = cf apps | Where-Object { 
    $_ -match 'appname'
} | % { $_.Trim() } | ConvertFrom-String -PropertyNames 'Name','Value'

And then use for-loop to delete the apps using cf delete <appname> -f command

for ($i = 0; $i -lt $apps.Length; $i++) {
    $char = $apps.Name[$i]
    cf delete $char -f
}

Solution

  • Preface:

    • The solution below uses text parsing to convert the tabular text output to objects, reflecting all the fields in the table, not just name.

      • A simpler solution that extracts the name fields only is:

        $names =  
          cf apps |
          select -Skip 1 | 
          foreach { ($_ -split '  ', 2)[0] }
        
    • Such an approach is best avoided, but cannot always be, namely if the source CLI lacks support for outputting a structured text format, such as JSON or CSV; indeed that seems to be the case for the CloudFoundry cf, as of this writing.


    Use the following, which makes the following assumptions, which are consistent with your sample data:

    • Column-header as well as data-row fields must be separated by two or more spaces.
    # Initialize helper variables.
    $propNames = $null
    $ohtTemplate = [ordered] @{}
    
    # Parse the tabular CLI output into objects.
    $appInfos = 
      cf apps | 
        ForEach-Object {
          $fields = ($_ -split ' {2,}').Trim()
          if ($null -eq $propNames) { 
            $propNames = $fields
            foreach ($propName in $propNames) {
              $ohtTemplate[$propName] = $null
            }
            return 
          }
          foreach ($i in 0..($propNames.Count-1)) {
            if ($i -lt $fields.Count) {
              $ohtTemplate[$propNames[$i]] = $fields[$i]
            }
            else {
              $ohtTemplate[$propNames[$i]] = $null
            }
          }
          [pscustomobject] $ohtTemplate
        }
    
    # Output the resulting objects, for visual inspection.
    $appInfos
    

    The above emits custom objects whose property names correspond to the table header, and whose values contain a row's column values (which are all stored as strings).

    PowerShell's for-display output-formatting system conveniently renders the resulting objects as follows:

    name         requested state   processes     routes
    ----         ---------------   ---------     ------
    appname_1    started           web:2/2       route1.com
    appname_2    started           web:1/1       route2.com
    

    To get just the names, for instance, as an array, using member-access enumeration, use $appInfos.name:

    # Output from `$appInfos.name`
    appname_1
    appname_2
    

    To get just the first app's name, use $appInfos[0].name.

    Of course, you can use $appInfos | ForEach-Object { <# work with $_ #> }, foreach ($app in $appInfos) { <# work with $app #> } or $appInfos.ForEach({ <# work with $_ #> }), as usual, to iterate over all app infos.