Manipulating tabular output from ps (emulating Murex's select)

Zach asked in the chatroom:

I was looking at another interactive shell, murex, and one of the examples given in the README is a pretty cool one liner;

ps aux | select (USER, PID, “%MEM”, COMMAND order by “%MEM” DESC) | head -n 10

Which then takes the output of aux and returns only what we select, and cuts all but 10. Is there any way to reasonably replicate this with Elvish?

I feel the answer is sufficiently interesting and long-form to warrant a forum post:

I looked at Murex’s select and it’s a very cool feature - it supports the full range of SQL syntax because it uses an in-memory SQLite database for it :exploding_head:

There isn’t quite something like that in Elvish, but you can always write some good ol’ code :tm: to:

  1. Parse the tabular output of ps into a stream of maps
  2. Use Elvish’s functional programming primitives like order and take to manipulate the data
  3. Format the data back into a table

Something like this:

use re
use str

fn parse-table {
  var field-names
  each {|line|
    if (eq $field-names $nil) {
      set @field-names = (re:split '[ \t]+' $line)
    } else {
      var n = (count $field-names)
      # use &max because the last column of ps's output
      # (COMMAND) could contain internal whitespaces
      var @fields = (re:split '[ \t]+' $line &max=$n)
      for i [(range $n)] {
        put [$field-names[$i] $fields[$i]]
      } | make-map
    }
  }
}

fn write-table {|fields|
  echo (str:join "\t" $fields)
  each {|m|
    each {|f| put $m[$f]} $fields | str:join "\t" | echo (one)
  }
}

ps aux | parse-table |
  order &key={|x| put $x[%MEM]} &reverse=$true |
  take 10 |
  write-table [USER PID %MEM COMMAND]
1 Like