Kanrisuru

Parsing Command Output

When running a command on a remote host with Kanrisuru, the results are returned in a predictable and well formated fashion.

Kanrisuru Result Class

Every result is returned in a Kanrisuru::Result class. This encapsulates the raw data from the command, exit status and with commands that return data, parsed and formatted structs with readable fields.

As an example, when running a simple command such as whoami:

require 'kanrisuru'

host = Kanrisuru::Remote::Host.new(host: 'localhost', username: 'ubuntu', keys: ['~/.ssh/id_rsa'])

result = host.whoami
result.success?
true

result.status
0

result.user
'ubuntu'


Standard across the result class is success?, failure?, status, data, and command. The first three methods are used to detect if the command ran succesfully or not on the remote host.

Note: Most commands consider an exist status of 0 to be succesful, and anything that's non-zero, to be considered a failed exit status. Some commands have exceptions to this; you can find what the success and failure exit statuses are for each command in the Kanrisuru docs, as well as their codes and an explanation of what these mean.

Kanrisuru Command Class

When a command is run with Kanrisuru, it’s first instantiated with the Kanrisuru::Command class. This encapsulates the underlying Linux command run on the remote machine, the raw data printed to stdout, as well as raw exit status returned from the host.

For the whoami command, the underlying command instance looks like:

result.command
#<Kanrisuru::Command:0x00005578c149d0d8 
  @valid_exit_codes=[0],
  @raw_command="whoami", 
  @raw_result=["ubuntu\n"], 
  @exit_status=0, 
  @remote_user="ubuntu", 
  @remote_shell="/bin/bash", 
  @remote_path="", 
  @remote_env=""
>


Even with this simple command and return result, there’s a few things going on under the hood with preparing and parsing data from the remote server.

With a more complex example, consider the ps command

result = host.ps(user: 0)

result.command
#<Kanrisuru::Command:0x00005578c15959b8 
  @valid_exit_codes=[0], 
  @raw_command="ps ww --user 0 -o uid,user,gid,group,ppid,pid,pcpu,pmem,stat,pri,flags,policy,time,cmd --no-headers", 
  @raw_result=["    0 root         0 root           0       1  0.0  0.5 Ss    19 4 TS  00:01:19 /sbin/init\n    0 root         0 root           0       2  0.0  0.0 S     19 1 TS  00:00:00 [kthreadd]\n    0 root         0 root           2       3  0.0  0.0 I<    39 1 TS  00:00:00 [rcu_gp]\n    0 root         0 root           2       4  0.0  0.0 I<    39 1 TS  00:00:00 [rcu_par_gp]\n    0 root         0 root           2       6  0.0  0.0 I<    39 1 TS  00:00:00 [kworker/0:0H-kblockd]\n    0 root         0 root           2       9  0.0  0.0 I<    39 1 TS  00:00:00 [mm_percpu_wq]\n    0 root         0 root           2      10  0.0  0.0 S     19 1 TS  00:00:01 [ksoftirqd/0]\n    0 root         0 root           2      11  0.0  0.0 I     19 1 TS  00:00:07 [rcu_sched]\n    0 root         0 root           2      12  0.0  0.0 S    139 1 FF  00:00:13 [migration/0]\n    0 root         0 root           2      13  0.0  0.0 S     90 5 FF  00:00:00 [idle_inject/0]\n    0 root         0 root           2      14  0.0  0.0 S     19 1 TS  00:00:00 [cpuhp/0]\n    0 root         0 root           2      15  0.0  0.0 S     19 5 TS  00:00:00 [kdevtmpfs]\n    0 root         0 root           2      16  0.0  0.0 I<    39 1 TS  00:00:00 [netns]\n    0 root         0 root           2      17  0.0  0.0 S     19 1 TS  00:00:00 [rcu_tasks_kthre]\n    0 root         0 root           2      18  0.0  0.0 S     19 1 TS  00:00:00 [kauditd]\n    0 root         0 root           2      19  0.0  0.0 S     19 1 TS  00:00:13 [khungtaskd]\n    0 root         0 root           2      20  0.0  0.0 S     19 1 TS  00:00:00 [oom_reaper]\n    0 root         0 root           2      21  0.0  0.0 I<    39 1 TS  00:00:00 [writeback]\n    0 root         0 root           2      22  0.0  0.0 S     19 1 TS  00:00:00 [kcompactd0]\n    0 root         0 root           2      23  0.0  0.0 SN    14 1 TS  00:00:00 [ksmd]\n    0 root         0 root           2      24  0.0  0.0 SN     0 1 TS  00:00:10 [khugepaged]\n    0 root         0 root           2      70  0.0  0.0 I<    39 1 TS  00:00:00 [kintegrityd]\n    0 root         0 root           2      71  0.0  0.0 I<    39 1 TS  00:00:00 [kblockd]\n    0 root         0 root           2      72  0.0  0.0 I<    39 1 TS  00:00:00 [blkcg_punt_bio]\n    0 root         0 root           2      73  0.0  0.0 I<    39 1 TS  00:00:00 [tpm_dev_wq]\n    0 root         0 root           2      74  0.0  0.0 I<    39 1 TS  00:00:00 [ata_sff]\n    0 root         0 root           2      75  0.0  0.0 I<    39 1 TS  00:00:00 [md]\n    0 root         0 root           2      76  0.0  0.0 I<    39 1 TS  00:00:00 [edac-poller]\n    0 root         0 root           2      77  0.0  0.0 I<    39 1 TS  00:00:00 [devfreq_wq]\n    0 root         0 root           2      78  0.0  0.0 S    139 1 FF  00:00:00 [watchdogd]\n    0 root         0 root           2      81  0.0  0.0 S     19 1 TS  00:00:00 [kswapd0]\n    0 root         0 root           2      82  0.0  0.0 S     19 1 TS  00:00:00 [ecryptfs-kthrea]\n    0 root         0 root           2      84  0.0  0.0 I<    39 1 TS  00:00:00 [kthrotld]\n    0 root         0 root           2      85  0.0  0.0 I<    39 1 TS  00:00:00 [acpi_thermal_pm]\n    0 root         0 root           2      86  0.0  0.0 S     19 1 TS  00:00:00 [scsi_eh_0]\n    0 root         0 root           2      87  0.0  0.0 I<    39 1 TS  00:00:00 [scsi_tmf_0]\n    0 root         0 root           2      88  0.0  0.0 S     19 1 TS  00:00:00 [scsi_eh_1]\n    0 root         0 root           2      89  0.0  0.0 I<    39 1 TS  00:00:00 [scsi_tmf_1]\n    0 root         0 root           2      91  0.0  0.0 I<    39 1 TS  00:00:00 [vfio-irqfd-clea]\n    0 root         0 root           2      93  0.0  0.0 I<    39 1 TS  00:00:00 [ipv6_addrconf]\n    0 root         0 root           2     102  0.0  0.0 I<    39 1 TS  00:00:00 [kstrp]\n    0 root         0 root           2     105  0.0  0.0 I<    39 1 TS  00:00:00 [kworker/u3:0-xprtiod]\n    0 root         0 root           2     118  0.0  0.0 I<    39 1 TS  00:00:00 [charger_manager]\n    0 root         0 root           2     158  0.0  0.0 I<    39 1 TS  00:00:00 [cryptd]\n  ", "  0 root         0 root           2     174  0.0  0.0 I<    39 1 TS  00:00:02 [kworker/0:1H-kblockd]\n    0 root         0 root           2     222  0.0  0.0 I<    39 1 TS  00:00:00 [raid5wq]\n    0 root         0 root           2     265  0.0  0.0 S     19 1 TS  00:00:03 [jbd2/vda1-8]\n    0 root         0 root           2     266  0.0  0.0 I<    39 1 TS  00:00:00 [ext4-rsv-conver]\n    0 root         0 root           2     302  0.0  0.0 S     19 1 TS  00:00:00 [hwrng]\n    0 root         0 root           1     350  0.0  0.9 S<s   20 4 TS  00:00:06 /lib/systemd/systemd-journald\n    0 root         0 root           2     353  0.0  0.0 I<    39 1 TS  00:00:00 [rpciod]\n    0 root         0 root           2     355  0.0  0.0 I<    39 1 TS  00:00:00 [xprtiod]\n    0 root         0 root           1     368  0.0  0.0 Ss    19 1 TS  00:00:00 /usr/sbin/blkmapd\n    0 root         0 root           1     379  0.0  0.2 Ss    19 4 TS  00:00:18 /lib/systemd/systemd-udevd\n    0 root         0 root           2     449  0.0  0.0 I<    39 1 TS  00:00:00 [kaluad]\n    0 root         0 root           2     450  0.0  0.0 I<    39 1 TS  00:00:00 [kmpath_rdacd]\n    0 root         0 root           2     451  0.0  0.0 I<    39 1 TS  00:00:00 [kmpathd]\n    0 root         0 root           2     452  0.0  0.0 I<    39 1 TS  00:00:00 [kmpath_handlerd]\n    0 root         0 root           1     453  0.0  0.8 SLsl 139 4 RR  00:02:39 /sbin/multipathd -d -s\n    0 root         0 root           2     464  0.0  0.0 S<    39 1 TS  00:00:00 [loop0]\n    0 root         0 root           2     465  0.0  0.0 S<    39 1 TS  00:00:00 [loop1]\n    0 root         0 root           2     469  0.0  0.0 S<    39 1 TS  00:00:00 [loop3]\n    0 root         0 root           2     473  0.0  0.0 S<    39 1 TS  00:00:00 [loop5]\n    0 root         0 root         0 root           2     564  0.0  0.0 I<    39 1 TS  00:00:00 [kworker/u3:1-xprtiod]\n    0 root         0 root           2     565  0.0  0.0 S     19 1 TS  00:00:00 [lockd]\n    0 root         0 root           2     570  0.0  0.0 S     19 1 TS  00:00:00 [nfsd]\n    0 root         0 root           2     571  0.0  0.0 S     19 1 TS  00:00:00 [nfsd]\n    0 root         0 root           2     572  0.0  0.0 S     19 1 TS  00:00:00 [nfsd]\n    0 root         0 root           2     573  0.0  0.0 S     19 1 TS  00:00:00 [nfsd]\n    0 root         0 root           2     574  0.0  0.0 S     19 1 TS  00:00:00 [nfsd]\n    0 root         0 root           2     575  0.0  0.0 S     19 1 TS  00:00:00 [nfsd]\n    0 root         0 root           2     576  0.0  0.0 S     19 1 TS  00:00:01 [nfsd]\n    0 root         0 root           2     577  0.0  0.0 S     19 1 TS  00:00:04 [nfsd]\n    0 root         0 root           1     610  0.0  0.4 Ssl   19 4 TS  00:01:01 /usr/lib/accountsservice/accounts-daemon\n    0 root         0 root           1     613  0.0  0.1 Ss    19 4 TS  00:00:15 /usr/sbin/cron -f\n    0 root         0 root           1     621  0.0  0.6 Ss    19 4 TS  00:00:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers\n    0 root         0 root           1     636  0.0  0.3 Ss    19 4 TS  00:00:15 /lib/systemd/systemd-logind\n    0 root         0 root           1     642  0.0  0.1 Ss+   19 4 TS  00:00:00 /sbin/agetty -o -p -- \\u --keep-baud 115200,38400,9600 ttyS0 vt220\n    0 root         0 root           1     644  0.0  0.0 Ss+   19 4 TS  00:00:00 /sbin/agetty -o -p -- \\u --noclear tty1 linux\n    0 root         0 root           1     667  0.0  0.3 Ss    19 4 TS  00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups\n    0 root         0 root           1     691  0.0  0.8 Ssl   19 4 TS  00:00:00 /usr/bin/python3 /usr/share/unattended-upgrades/unattended-upgrade-shutdown --wait-for-signal\n    0 root         0 root           1     704  0.0  0.3 Ssl   19 4 TS  00:00:00 /usr/lib/policykit-1/polkitd --no-debug\n    0 root         0 ro", "ot           2     970  0.0  0.0 S<    39 1 TS  00:00:00 [loop6]\n    0 root         0 root           1    1012  0.0  1.5 Ssl   19 4 TS  00:04:07 /usr/lib/snapd/snapd\n    0 root         0 root           2    1311  0.0  0.0 S<    39 1 TS  00:00:00 [loop4]\n    0 root         0 root           2    1411  0.0  0.0 S<    39 1 TS  00:00:00 [loop7]\n    0 root         0 root           2   16709  0.0  0.0 I<    39 1 TS  00:00:00 [xfsalloc]\n    0 root         0 root           2   16715  0.0  0.0 I<    39 1 TS  00:00:00 [xfs_mru_cache]\n    0 root         0 root           2   60839  0.0  0.0 S<    39 1 TS  00:00:00 [loop2]\n    0 root         0 root           2   64646  0.0  0.0 I     19 1 TS  00:00:00 [kworker/u2:2-events_unbound]\n    0 root         0 root           2   65983  0.0  0.0 I     19 1 TS  00:00:05 [kworker/0:1-events]\n    0 root         0 root         667   66973  0.0  0.4 Ss    19 4 TS  00:00:00 sshd: ubuntu [priv]\n    0 root         0 root           2   66985  0.0  0.0 I     19 1 TS  00:00:00 [kworker/0:0-cgroup_destroy]\n    0 root         0 root           2   67194  0.0  0.0 I     19 1 TS  00:00:00 [kworker/u2:0-events_power_efficient]\n    0 root         0 root         667   67198  0.0  0.4 Ss    19 4 TS  00:00:00 sshd: ubuntu [priv]\n"], 
  @exit_status=0
>


This is a pretty gnarly result. Luckily all commands run with Kanrisuru are parsed with the data extracted in a manner that’s easy for other applications to use.

Result Data

If data is returned from the remote host, Kanrisuru will parse this and provide it in the Kanrisuru::Result object.

For the whoami command, the data is returned as a custom struct instance, with the appropriate fields set on that instance.

result = host.whoami

result.data
#<Struct:Kanrisuru::Core::Path::UserName:0x00001810
  user = "ubuntu"
>

result.data.class
Kanrisuru::Core::Path::UserName


Kanrisuru also provides method mapping for struct fields tied to the result object. What this means is you can call direct getter methods on the underlying data object, eg

result.user
'ubuntu'


With data items that are mapped to an array of structs, like the ps example from earlier, the Kanrisuru::Result class also includes Enumerable for quick enumeration methods like each, map, and other filtering / selection methods.

result = host.ps(user: 'ubuntu')
result.to_a
[
  #<Struct:Kanrisuru::Core::System::ProcessInfo:0x00001888
    pid = 1,
    command = "/lib/systemd/systemd --user",
    cpu_time = "00:00:00",
    cpu_usage = 0.0,
    flags = 4,
    gid = 1000,
    group = "ubuntu",
    memory_usage = 0.4,
    pid = 66984,
    policy = "SCHED_OTHER",
    policy_abbr = "TS",
    priority = 19,
    stat = "Ss",
    uid = 1000,
    user = "ubuntu"
  >,
  #<Struct:Kanrisuru::Core::System::ProcessInfo:0x00001d10
    pid = 67088,
    command = "irb",
    cpu_time = "00:00:02",
    cpu_usage = 0.0,
    flags = 0,
    gid = 1000,
    group = "ubuntu",
    memory_usage = 2.2,
    pid = 67195,
    policy = "SCHED_OTHER",
    policy_abbr = "TS",
    priority = 19,
    stat = "S+",
    uid = 1000,
    user = "ubuntu"
  >,
  [2] .. [4],
  #<Struct:Kanrisuru::Core::System::ProcessInfo:0x00001e28
    pid = 67198,
    command = "sshd: ubuntu@notty",
    cpu_time = "00:00:00",
    cpu_usage = 0.0,
    flags = 5,
    gid = 1000,
    group = "ubuntu",
    memory_usage = 0.2,
    pid = 67281,
    policy = "SCHED_OTHER",
    policy_abbr = "TS",
    priority = 19,
    stat = "S",
    uid = 1000,
    user = "ubuntu"
  >,
  #<Struct:Kanrisuru::Core::System::ProcessInfo:0x00001f40
    pid = 67281,
    command = "ps ww --user ubuntu -o uid,user,gid,group,ppid,pid,pcpu,pmem,stat,pri,flags,policy,time,cmd --no-headers",
    cpu_time = "00:00:00",
    cpu_usage = 0.0,
    flags = 0,
    gid = 1000,
    group = "ubuntu",
    memory_usage = 0.1,
    pid = 67347,
    policy = "SCHED_OTHER",
    policy_abbr = "TS",
    priority = 19,
    stat = "Rs",
    uid = 1000,
    user = "ubuntu"
  >
]


As an example, to fetch the pids that are using the most memory on the system, run the ps command through the sort and map methods:

pids = host.ps.sort { |pa, pb| pb.memory_usage <=> pa.memory_usage }.map(&:pid)
pids[0..5]
[791, 67195, 1012, 561, 350, 453]


Checkout the core documentation for a more in-depth explanation on each command, their return fields and an description of what each field means.