diff --git a/README.md b/README.md index bb7a2806..f5cd1281 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # JC JSON CLI output utility -`jc` is used to JSONify the output of many standard linux cli tools for easier parsing in scripts. Parsers for `ls`, `ifconfig`, `ps`, `route`, and `netstat` are currently included and more can be added via modules. +`jc` is used to JSONify the output of many standard linux cli tools for easier parsing in scripts. See the **Parsers** section for supported commands. This allows further command line processing of output with tools like `jq` simply by piping commands: @@ -59,17 +59,93 @@ jc [parser] [options] `jc` accepts piped input from `STDIN` and outputs a JSON representation of the previous command's output to `STDOUT`. The JSON output can be compact or pretty formatted. -Parsers: +### Parsers +- `--df` enables the `df` parser +- `--env` enables the `env` parser +- `--free` enables the `free` parser - `--ifconfig` enables the `ifconfig` parser +- `--iptables` enables the `iptables` parser - `--ls` enables the `ls` parser +- `--lsblk` enables the `lsblk` parser +- `--mount` enables the `mount` parser - `--netstat` enables the `netstat` parser - `--ps` enables the `ps` parser - `--route` enables the `route` parser +- `--uname` enables the `uname -a` parser -Options: +### Options - `-p` specifies whether to pretty format the JSON output ## Examples +### df +``` +$ df | jc --df -p +[ + { + "Filesystem": "udev", + "1K-blocks": "977500", + "Used": "0", + "Available": "977500", + "Use%": "0%", + "Mounted": "/dev" + }, + { + "Filesystem": "tmpfs", + "1K-blocks": "201732", + "Used": "1180", + "Available": "200552", + "Use%": "1%", + "Mounted": "/run" + }, + { + "Filesystem": "/dev/sda2", + "1K-blocks": "20508240", + "Used": "5747284", + "Available": "13696152", + "Use%": "30%", + "Mounted": "/" + }, + { + "Filesystem": "tmpfs", + "1K-blocks": "1008648", + "Used": "0", + "Available": "1008648", + "Use%": "0%", + "Mounted": "/dev/shm" + }, + ... +] +``` +### env +``` +$ env | jc --env -p +[ + { + "TERM": "xterm-256color" + }, + { + "SHELL": "/bin/bash" + }, + { + "USER": "root" + }, + { + "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + }, + { + "PWD": "/bin" + }, + { + "LANG": "en_US.UTF-8" + }, + { + "HOME": "/root" + }, + { + "_": "/usr/bin/env" + } +] +``` ### ifconfig ``` $ ifconfig | jc --ifconfig -p @@ -154,6 +230,325 @@ $ ifconfig | jc --ifconfig -p } ] ``` +### iptables +``` +$ sudo iptables -L -t nat | jc --iptables -p +[ + { + "chain": "PREROUTING", + "rules": [ + { + "target": "PREROUTING_direct", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "PREROUTING_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "PREROUTING_ZONES", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "DOCKER", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere", + "options": "ADDRTYPE match dst-type LOCAL" + } + ] + }, + { + "chain": "INPUT", + "rules": [] + }, + { + "chain": "OUTPUT", + "rules": [ + { + "target": "OUTPUT_direct", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "DOCKER", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "!loopback/8", + "options": "ADDRTYPE match dst-type LOCAL" + } + ] + }, + ... +] +``` +``` +$ sudo iptables -vnL -t filter | jc --iptables -p +[ + { + "chain": "INPUT", + "rules": [ + { + "pkts": "1571", + "bytes": "3394K", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate RELATED,ESTABLISHED" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "lo", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "711", + "bytes": "60126", + "target": "INPUT_direct", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "711", + "bytes": "60126", + "target": "INPUT_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "711", + "bytes": "60126", + "target": "INPUT_ZONES", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "DROP", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate INVALID" + }, + { + "pkts": "710", + "bytes": "60078", + "target": "REJECT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "reject-with icmp-host-prohibited" + } + ] + }, + { + "chain": "FORWARD", + "rules": [ + { + "pkts": "0", + "bytes": "0", + "target": "DOCKER-ISOLATION", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "DOCKER", + "prot": "all", + "opt": "--", + "in": "*", + "out": "docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate RELATED,ESTABLISHED" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "docker0", + "out": "!docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "docker0", + "out": "docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate RELATED,ESTABLISHED" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "lo", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_direct", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_IN_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_IN_ZONES", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_OUT_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_OUT_ZONES", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "DROP", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate INVALID" + }, + { + "pkts": "0", + "bytes": "0", + "target": "REJECT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "reject-with icmp-host-prohibited" + } + ] + }, + ... +] +``` ### ls ``` $ ls -l /bin | jc --ls -p @@ -188,6 +583,98 @@ $ ls -l /bin | jc --ls -p ... ] ``` +### lsblk +``` +$ lsblk | jc --lsblk -p +[ + { + "NAME": "loop0", + "MAJ:MIN": "7:0", + "RM": "0", + "SIZE": "54.5M", + "RO": "1", + "TYPE": "loop", + "MOUNTPOINT": "/snap/core18/1223" + }, + { + "NAME": "sda", + "MAJ:MIN": "8:0", + "RM": "0", + "SIZE": "20G", + "RO": "0", + "TYPE": "disk" + }, + { + "NAME": "sda1", + "MAJ:MIN": "8:1", + "RM": "0", + "SIZE": "1M", + "RO": "0", + "TYPE": "part" + }, + { + "NAME": "sda2", + "MAJ:MIN": "8:2", + "RM": "0", + "SIZE": "20G", + "RO": "0", + "TYPE": "part", + "MOUNTPOINT": "/" + }, + { + "NAME": "sr0", + "MAJ:MIN": "11:0", + "RM": "1", + "SIZE": "64.8M", + "RO": "0", + "TYPE": "rom" + } +] +``` +### mount +``` +$ mount | jc --mount -p +[ + { + "filesystem": "sysfs", + "mount_point": "/sys", + "type": "sysfs", + "access": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + { + "filesystem": "proc", + "mount_point": "/proc", + "type": "proc", + "access": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + { + "filesystem": "udev", + "mount_point": "/dev", + "type": "devtmpfs", + "access": [ + "rw", + "nosuid", + "relatime", + "size=977500k", + "nr_inodes=244375", + "mode=755" + ] + }, + ... +] +``` ### netstat ``` $ netstat -p | jc --netstat -p @@ -394,6 +881,20 @@ $ route -n | jc --route -p } ] ``` +### uname -a +``` +$ uname -a | jc --uname -p +{ + "kernel_name": "Linux", + "node_name": "user-ubuntu", + "kernel_release": "4.15.0-65-generic", + "operating_system": "GNU/Linux", + "hardware_platform": "x86_64", + "processor": "x86_64", + "machine": "x86_64", + "kernel_version": "#74-Ubuntu SMP Tue Sep 17 17:06:04 UTC 2019" +} +``` ## Contributions Feel free to add/improve code or parsers! diff --git a/changelog.txt b/changelog.txt index 98ff7973..57f7fa74 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,14 @@ jc changelog +20191022 v0.8.1 +- Add env parser +- Add df parser +- Add free parser +- Add lsblk parser +- Add mount parser +- Add uname parser +- Add iptables parser + 20191021 v0.6.4 - Flatten netstat parser output - Clean up argument parsing diff --git a/jc/jc.py b/jc/jc.py index 9adba38c..2dc20eb5 100755 --- a/jc/jc.py +++ b/jc/jc.py @@ -6,11 +6,18 @@ Main input module import sys import json +import jc.parsers.df +import jc.parsers.env +import jc.parsers.free import jc.parsers.ifconfig +import jc.parsers.iptables import jc.parsers.ls +import jc.parsers.lsblk +import jc.parsers.mount import jc.parsers.netstat import jc.parsers.ps import jc.parsers.route +import jc.parsers.uname def main(): @@ -20,12 +27,30 @@ def main(): if '-p' in sys.argv: pretty = True - if '--ifconfig' in sys.argv: + if '--df' in sys.argv: + result = jc.parsers.df.parse(data) + + elif '--env' in sys.argv: + result = jc.parsers.env.parse(data) + + elif '--free' in sys.argv: + result = jc.parsers.free.parse(data) + + elif '--ifconfig' in sys.argv: result = jc.parsers.ifconfig.parse(data) + elif '--iptables' in sys.argv: + result = jc.parsers.iptables.parse(data) + elif '--ls' in sys.argv: result = jc.parsers.ls.parse(data) + elif '--lsblk' in sys.argv: + result = jc.parsers.lsblk.parse(data) + + elif '--mount' in sys.argv: + result = jc.parsers.mount.parse(data) + elif '--netstat' in sys.argv: result = jc.parsers.netstat.parse(data) @@ -35,15 +60,25 @@ def main(): elif '--route' in sys.argv: result = jc.parsers.route.parse(data) + elif '--uname' in sys.argv: + result = jc.parsers.uname.parse(data) + else: - print('jc: missing arguments', file=sys.stderr) + print('jc: missing arguments\n', file=sys.stderr) print('Usage: jc [parser] [options]\n', file=sys.stderr) print('Parsers:', file=sys.stderr) + print(' --df df parser', file=sys.stderr) + print(' --env env parser', file=sys.stderr) + print(' --free free parser', file=sys.stderr) print(' --ifconfig iconfig parser', file=sys.stderr) + print(' --iptables iptables parser', file=sys.stderr) print(' --ls ls parser', file=sys.stderr) + print(' --lsblk lsblk parser', file=sys.stderr) + print(' --mount mount parser', file=sys.stderr) print(' --netstat netstat parser', file=sys.stderr) print(' --ps ps parser', file=sys.stderr) - print(' --route route parser\n', file=sys.stderr) + print(' --route route parser', file=sys.stderr) + print(' --uname uname parser\n', file=sys.stderr) print('Options:', file=sys.stderr) print(' -p pretty print output\n', file=sys.stderr) print('Example:', file=sys.stderr) diff --git a/jc/parsers/df.py b/jc/parsers/df.py new file mode 100644 index 00000000..32a9af01 --- /dev/null +++ b/jc/parsers/df.py @@ -0,0 +1,55 @@ +"""jc - JSON CLI output utility df Parser + +Usage: + specify --df as the first argument if the piped input is coming from df + +Example: + +$ df | jc --df -p +[ + { + "Filesystem": "udev", + "1K-blocks": "977500", + "Used": "0", + "Available": "977500", + "Use%": "0%", + "Mounted": "/dev" + }, + { + "Filesystem": "tmpfs", + "1K-blocks": "201732", + "Used": "1180", + "Available": "200552", + "Use%": "1%", + "Mounted": "/run" + }, + { + "Filesystem": "/dev/sda2", + "1K-blocks": "20508240", + "Used": "5747284", + "Available": "13696152", + "Use%": "30%", + "Mounted": "/" + }, + { + "Filesystem": "tmpfs", + "1K-blocks": "1008648", + "Used": "0", + "Available": "1008648", + "Use%": "0%", + "Mounted": "/dev/shm" + }, + ... +] +""" + + +def parse(data): + + # code adapted from Conor Heine at: + # https://gist.github.com/cahna/43a1a3ff4d075bcd71f9d7120037a501 + + cleandata = data.splitlines() + headers = [h for h in ' '.join(cleandata[0].strip().split()).split() if h] + raw_data = map(lambda s: s.strip().split(None, len(headers) - 1), cleandata[1:]) + return [dict(zip(headers, r)) for r in raw_data] diff --git a/jc/parsers/env.py b/jc/parsers/env.py new file mode 100644 index 00000000..2cebc027 --- /dev/null +++ b/jc/parsers/env.py @@ -0,0 +1,53 @@ +"""jc - JSON CLI output utility env Parser + +Usage: + specify --env as the first argument if the piped input is coming from env + +Example: +$ env | jc --env -p +[ + { + "TERM": "xterm-256color" + }, + { + "SHELL": "/bin/bash" + }, + { + "USER": "root" + }, + { + "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + }, + { + "PWD": "/bin" + }, + { + "LANG": "en_US.UTF-8" + }, + { + "HOME": "/root" + }, + { + "_": "/usr/bin/env" + } +] +""" + + +def parse(data): + output = [] + + linedata = data.splitlines() + + # Clear any blank lines + cleandata = list(filter(None, linedata)) + + if cleandata: + + for entry in cleandata: + output_line = {} + parsed_line = entry.split('=', maxsplit=1) + output_line[parsed_line[0]] = parsed_line[1] + output.append(output_line) + + return output diff --git a/jc/parsers/free.py b/jc/parsers/free.py new file mode 100644 index 00000000..fb206746 --- /dev/null +++ b/jc/parsers/free.py @@ -0,0 +1,45 @@ +"""jc - JSON CLI output utility free Parser + +Usage: + specify --free as the first argument if the piped input is coming from free + +Example: + +$ free | jc --free -p +[ + { + "type": "Mem", + "total": "2017300", + "used": "213104", + "free": "1148452", + "shared": "1176", + "buff/cache": "655744", + "available": "1622204" + }, + { + "type": "Swap", + "total": "2097148", + "used": "0", + "free": "2097148" + } +] +""" + + +def parse(data): + + # code adapted from Conor Heine at: + # https://gist.github.com/cahna/43a1a3ff4d075bcd71f9d7120037a501 + + cleandata = data.splitlines() + headers = [h for h in ' '.join(cleandata[0].strip().split()).split() if h] + + headers.insert(0, "type") + + raw_data = map(lambda s: s.strip().split(None, len(headers) - 1), cleandata[1:]) + output = [dict(zip(headers, r)) for r in raw_data] + + for entry in output: + entry['type'] = entry['type'].rstrip(':') + + return output diff --git a/jc/parsers/iptables.py b/jc/parsers/iptables.py new file mode 100644 index 00000000..2d6756e2 --- /dev/null +++ b/jc/parsers/iptables.py @@ -0,0 +1,366 @@ +"""jc - JSON CLI output utility ipables Parser + +Usage: + Specify --iptables as the first argument if the piped input is coming from iptables + + Supports -vLn for all tables + +Examples: + +$ sudo iptables -L -t nat | jc --iptables -p +[ + { + "chain": "PREROUTING", + "rules": [ + { + "target": "PREROUTING_direct", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "PREROUTING_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "PREROUTING_ZONES", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "DOCKER", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere", + "options": "ADDRTYPE match dst-type LOCAL" + } + ] + }, + { + "chain": "INPUT", + "rules": [] + }, + { + "chain": "OUTPUT", + "rules": [ + { + "target": "OUTPUT_direct", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "anywhere" + }, + { + "target": "DOCKER", + "prot": "all", + "opt": "--", + "source": "anywhere", + "destination": "!loopback/8", + "options": "ADDRTYPE match dst-type LOCAL" + } + ] + }, + ... +] + +$ sudo iptables -vnL -t filter | jc --iptables -p +[ + { + "chain": "INPUT", + "rules": [ + { + "pkts": "1571", + "bytes": "3394K", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate RELATED,ESTABLISHED" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "lo", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "711", + "bytes": "60126", + "target": "INPUT_direct", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "711", + "bytes": "60126", + "target": "INPUT_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "711", + "bytes": "60126", + "target": "INPUT_ZONES", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "DROP", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate INVALID" + }, + { + "pkts": "710", + "bytes": "60078", + "target": "REJECT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "reject-with icmp-host-prohibited" + } + ] + }, + { + "chain": "FORWARD", + "rules": [ + { + "pkts": "0", + "bytes": "0", + "target": "DOCKER-ISOLATION", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "DOCKER", + "prot": "all", + "opt": "--", + "in": "*", + "out": "docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate RELATED,ESTABLISHED" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "docker0", + "out": "!docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "docker0", + "out": "docker0", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate RELATED,ESTABLISHED" + }, + { + "pkts": "0", + "bytes": "0", + "target": "ACCEPT", + "prot": "all", + "opt": "--", + "in": "lo", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_direct", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_IN_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_IN_ZONES", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_OUT_ZONES_SOURCE", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "FORWARD_OUT_ZONES", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0" + }, + { + "pkts": "0", + "bytes": "0", + "target": "DROP", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "ctstate INVALID" + }, + { + "pkts": "0", + "bytes": "0", + "target": "REJECT", + "prot": "all", + "opt": "--", + "in": "*", + "out": "*", + "source": "0.0.0.0/0", + "destination": "0.0.0.0/0", + "options": "reject-with icmp-host-prohibited" + } + ] + }, + ... +] +""" + + +class state(): + output = [] + chain = {} + headers = [] + + +def parse(data): + cleandata = data.splitlines() + + for line in cleandata: + + if line.find('Chain') == 0: + state.output.append(state.chain) + state.chain = {} + state.headers = [] + + parsed_line = line.split() + + state.chain['chain'] = parsed_line[1] + state.chain['rules'] = [] + + continue + + if line.find('target') == 0 or line.find('pkts') == 1: + state.headers = [] + state.headers = [h for h in ' '.join(line.strip().split()).split() if h] + state.headers.append("options") + + continue + + else: + rule = line.split(maxsplit=len(state.headers) - 1) + temp_rule = dict(zip(state.headers, rule)) + if temp_rule: + state.chain['rules'].append(temp_rule) + + state.output = list(filter(None, state.output)) + + return state.output diff --git a/jc/parsers/ls.py b/jc/parsers/ls.py index 24a8d841..318153aa 100644 --- a/jc/parsers/ls.py +++ b/jc/parsers/ls.py @@ -72,7 +72,7 @@ $ ls -al /usr/bin | jc --ls -p ... ] -$ $ ls -l /usr/bin | jc --ls | jq .[] | jq 'select(.bytes > 50000000)' +$ $ ls -l /usr/bin | jc --ls | jq '.[] | 'select(.bytes > 50000000)' { "filename": "emacs", "flags": "-r-xr-xr-x", diff --git a/jc/parsers/lsblk.py b/jc/parsers/lsblk.py new file mode 100644 index 00000000..eb78975f --- /dev/null +++ b/jc/parsers/lsblk.py @@ -0,0 +1,70 @@ +"""jc - JSON CLI output utility lsblk Parser + +Usage: + specify --lsblk as the first argument if the piped input is coming from lsblk + +Example: + +$ lsblk | jc --lsblk -p +[ + { + "NAME": "loop0", + "MAJ:MIN": "7:0", + "RM": "0", + "SIZE": "54.5M", + "RO": "1", + "TYPE": "loop", + "MOUNTPOINT": "/snap/core18/1223" + }, + { + "NAME": "sda", + "MAJ:MIN": "8:0", + "RM": "0", + "SIZE": "20G", + "RO": "0", + "TYPE": "disk" + }, + { + "NAME": "sda1", + "MAJ:MIN": "8:1", + "RM": "0", + "SIZE": "1M", + "RO": "0", + "TYPE": "part" + }, + { + "NAME": "sda2", + "MAJ:MIN": "8:2", + "RM": "0", + "SIZE": "20G", + "RO": "0", + "TYPE": "part", + "MOUNTPOINT": "/" + }, + { + "NAME": "sr0", + "MAJ:MIN": "11:0", + "RM": "1", + "SIZE": "64.8M", + "RO": "0", + "TYPE": "rom" + } +] +""" + + +def parse(data): + + # code adapted from Conor Heine at: + # https://gist.github.com/cahna/43a1a3ff4d075bcd71f9d7120037a501 + + cleandata = data.splitlines() + headers = [h for h in ' '.join(cleandata[0].strip().split()).split() if h] + + raw_data = map(lambda s: s.strip().split(None, len(headers) - 1), cleandata[1:]) + output = [dict(zip(headers, r)) for r in raw_data] + + for entry in output: + entry['NAME'] = entry['NAME'].encode('ascii', errors='ignore').decode() + + return output diff --git a/jc/parsers/mount.py b/jc/parsers/mount.py new file mode 100644 index 00000000..650882e8 --- /dev/null +++ b/jc/parsers/mount.py @@ -0,0 +1,75 @@ +"""jc - JSON CLI output utility mount Parser + +Usage: + specify --mount as the first argument if the piped input is coming from mount + +Example: + +$ mount | jc --mount -p +[ + { + "filesystem": "sysfs", + "mount_point": "/sys", + "type": "sysfs", + "access": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + { + "filesystem": "proc", + "mount_point": "/proc", + "type": "proc", + "access": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + { + "filesystem": "udev", + "mount_point": "/dev", + "type": "devtmpfs", + "access": [ + "rw", + "nosuid", + "relatime", + "size=977500k", + "nr_inodes=244375", + "mode=755" + ] + }, + ... +] +""" + + +def parse(data): + output = [] + + linedata = data.splitlines() + + # Clear any blank lines + cleandata = list(filter(None, linedata)) + + if cleandata: + for entry in cleandata: + output_line = {} + parsed_line = entry.split() + + output_line['filesystem'] = parsed_line[0] + output_line['mount_point'] = parsed_line[2] + output_line['type'] = parsed_line[4] + + access = parsed_line[5].lstrip('(').rstrip(')').split(',') + + output_line['access'] = access + + output.append(output_line) + + return output diff --git a/jc/parsers/uname.py b/jc/parsers/uname.py new file mode 100644 index 00000000..892ec042 --- /dev/null +++ b/jc/parsers/uname.py @@ -0,0 +1,42 @@ +"""jc - JSON CLI output utility uname Parser + +Usage: + specify --uname as the first argument if the piped input is coming from uname + +Limitations: + must use 'uname -a' + +Example: + +$ uname -a | jc --uname -p +{ + "kernel_name": "Linux", + "node_name": "user-ubuntu", + "kernel_release": "4.15.0-65-generic", + "operating_system": "GNU/Linux", + "hardware_platform": "x86_64", + "processor": "x86_64", + "machine": "x86_64", + "kernel_version": "#74-Ubuntu SMP Tue Sep 17 17:06:04 UTC 2019" +} +""" + + +def parse(data): + output = {} + parsed_line = data.split(maxsplit=3) + + output['kernel_name'] = parsed_line.pop(0) + output['node_name'] = parsed_line.pop(0) + output['kernel_release'] = parsed_line.pop(0) + + parsed_line = parsed_line[-1].rsplit(maxsplit=4) + + output['operating_system'] = parsed_line.pop(-1) + output['hardware_platform'] = parsed_line.pop(-1) + output['processor'] = parsed_line.pop(-1) + output['machine'] = parsed_line.pop(-1) + + output['kernel_version'] = parsed_line.pop(0) + + return output diff --git a/setup.py b/setup.py index ac5088ab..43fef882 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open('README.md', 'r') as f: setuptools.setup( name='jc', - version='0.6.4', + version='0.8.1', author='Kelly Brazil', author_email='kellyjonbrazil@gmail.com', description='This tool serializes the output of popular command line tools to structured JSON output.',