diff --git a/docs/parsers/proc_pid_fdinfo.md b/docs/parsers/proc_pid_fdinfo.md
new file mode 100644
index 00000000..4f266668
--- /dev/null
+++ b/docs/parsers/proc_pid_fdinfo.md
@@ -0,0 +1,127 @@
+[Home](https://kellyjonbrazil.github.io/jc/)
+
+
+# jc.parsers.proc\_pid\_fdinfo
+
+jc - JSON Convert `/proc//fdinfo` file parser
+
+Usage (cli):
+
+ $ cat /proc/1/fdinfo/5 | jc --proc
+
+or
+
+ $ jc /proc/1/fdinfo/5
+
+or
+
+ $ cat /proc/1/fdinfo/5 | jc --proc-pid-fdinfo
+
+Usage (module):
+
+ import jc
+ result = jc.parse('proc', proc_pid_fdinfo_file)
+
+or
+
+ import jc
+ result = jc.parse('proc_pid_fdinfo', proc_pid_fdinfo_file)
+
+Schema:
+
+Any unspecified fields are strings.
+
+ {
+ "pos": integer,
+ "flags": integer,
+ "mnt_id": integer,
+ "scm_fds": string,
+ "ino": integer,
+ "lock": string,
+ "epoll": {
+ "tfd": integer,
+ "events": string,
+ "data": string,
+ "pos": integer,
+ "ino": string,
+ "sdev": string
+ },
+ "inotify": {
+ "wd": integer,
+ "ino": string,
+ "sdev": string,
+ "mask": string,
+ "ignored_mask": string,
+ "fhandle-bytes": string,
+ "fhandle-type": string,
+ "f_handle": string
+ },
+ "fanotify": {
+ "flags": string,
+ "event-flags": string,
+ "mnt_id": string,
+ "mflags": string,
+ "mask": string,
+ "ignored_mask": string,
+ "ino": string,
+ "sdev": string,
+ "fhandle-bytes": string,
+ "fhandle-type": string,
+ "f_handle": string
+ },
+ "clockid": integer,
+ "ticks": integer,
+ "settime flags": integer,
+ "it_value": [
+ integer
+ ],
+ "it_interval": [
+ integer
+ ]
+ }
+
+Examples:
+
+ $ cat /proc/1/fdinfo/5 | jc --proc -p
+ {
+ "pos": 0,
+ "flags": 2,
+ "mnt_id": 9,
+ "ino": 63107,
+ "clockid": 0,
+ "ticks": 0,
+ "settime flags": 1,
+ "it_value": [
+ 0,
+ 49406829
+ ],
+ "it_interval": [
+ 1,
+ 0
+ ]
+ }
+
+
+
+### parse
+
+```python
+def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict
+```
+
+Main text parsing function
+
+Parameters:
+
+ data: (string) text data to parse
+ raw: (boolean) unprocessed output if True
+ quiet: (boolean) suppress warning messages if True
+
+Returns:
+
+ Dictionary. Raw or processed structured data.
+
+### Parser Information
+Compatibility: linux
+
+Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
diff --git a/jc/lib.py b/jc/lib.py
index 0a01105a..f2506197 100644
--- a/jc/lib.py
+++ b/jc/lib.py
@@ -112,6 +112,7 @@ parsers = [
'proc-vmallocinfo',
'proc-vmstat',
'proc-zoneinfo',
+ 'proc-pid-fdinfo',
'proc-pid-numa-maps',
'ps',
'route',
diff --git a/jc/parsers/proc.py b/jc/parsers/proc.py
index 0c6bdd5c..cfe59399 100644
--- a/jc/parsers/proc.py
+++ b/jc/parsers/proc.py
@@ -195,15 +195,15 @@ def parse(
net_route_p = re.compile(r'^Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\s+\n')
net_unix_p = re.compile(r'^Num RefCount Protocol Flags Type St Inode Path\n')
- pid_status_p = re.compile(r'^Name:\t.+\nUmask:\t\d+\nState:\t.+\nTgid:\t\d+\n')
- pid_statm_p = re.compile(r'^\d+ \d+ \d+\s\d+\s\d+\s\d+\s\d+$')
- pid_stat_p = re.compile(r'^\d+ \(.{1,16}\) \w \d+ \d+ \d+ \d+ -?\d+ (?:\d+ ){43}\d+$')
- pid_smaps_p = re.compile(r'^[0-9a-f]{12}-[0-9a-f]{12} [rwxsp\-]{4} [0-9a-f]{8} [0-9a-f]{2}:[0-9a-f]{2} \d+ [^\n]+\nSize:\s+\d+ \S\S\n')
- pid_maps_p = re.compile(r'^[0-9a-f]{12}-[0-9a-f]{12} [rwxsp\-]{4} [0-9a-f]{8} [0-9a-f]{2}:[0-9a-f]{2} \d+ ')
- pid_numa_maps_p = re.compile(r'^[a-f0-9]{12} default [^\n]+\n')
- pid_io_p = re.compile(r'^rchar: \d+\nwchar: \d+\nsyscr: \d+\n')
- pid_mountinfo_p = re.compile(r'^\d+ \d+ \d+:\d+ /.+\n')
pid_fdinfo_p = re.compile(r'^pos:\t\d+\nflags:\t\d+\nmnt_id:\t\d+\n')
+ pid_io_p = re.compile(r'^rchar: \d+\nwchar: \d+\nsyscr: \d+\n')
+ pid_maps_p = re.compile(r'^[0-9a-f]{12}-[0-9a-f]{12} [rwxsp\-]{4} [0-9a-f]{8} [0-9a-f]{2}:[0-9a-f]{2} \d+ ')
+ pid_mountinfo_p = re.compile(r'^\d+ \d+ \d+:\d+ /.+\n')
+ pid_numa_maps_p = re.compile(r'^[a-f0-9]{12} default [^\n]+\n')
+ pid_smaps_p = re.compile(r'^[0-9a-f]{12}-[0-9a-f]{12} [rwxsp\-]{4} [0-9a-f]{8} [0-9a-f]{2}:[0-9a-f]{2} \d+ [^\n]+\nSize:\s+\d+ \S\S\n')
+ pid_statm_p = re.compile(r'^\d+ \d+ \d+\s\d+\s\d+\s\d+\s\d+$')
+ pid_status_p = re.compile(r'^Name:\t.+\nUmask:\t\d+\nState:\t.+\nTgid:\t\d+\n')
+ pid_stat_p = re.compile(r'^\d+ \(.{1,16}\) \w \d+ \d+ \d+ \d+ -?\d+ (?:\d+ ){43}\d+$')
scsi_device_info = re.compile(r"^'\w+' '.+' 0x\d+")
scsi_scsi_p = re.compile(r'^Attached devices:\nHost: \w+ ')
diff --git a/jc/parsers/proc_pid_fdinfo.py b/jc/parsers/proc_pid_fdinfo.py
new file mode 100644
index 00000000..b752c30f
--- /dev/null
+++ b/jc/parsers/proc_pid_fdinfo.py
@@ -0,0 +1,217 @@
+"""jc - JSON Convert `/proc//fdinfo` file parser
+
+Usage (cli):
+
+ $ cat /proc/1/fdinfo/5 | jc --proc
+
+or
+
+ $ jc /proc/1/fdinfo/5
+
+or
+
+ $ cat /proc/1/fdinfo/5 | jc --proc-pid-fdinfo
+
+Usage (module):
+
+ import jc
+ result = jc.parse('proc', proc_pid_fdinfo_file)
+
+or
+
+ import jc
+ result = jc.parse('proc_pid_fdinfo', proc_pid_fdinfo_file)
+
+Schema:
+
+Any unspecified fields are strings.
+
+ {
+ "pos": integer,
+ "flags": integer,
+ "mnt_id": integer,
+ "scm_fds": string,
+ "ino": integer,
+ "lock": string,
+ "epoll": {
+ "tfd": integer,
+ "events": string,
+ "data": string,
+ "pos": integer,
+ "ino": string,
+ "sdev": string
+ },
+ "inotify": {
+ "wd": integer,
+ "ino": string,
+ "sdev": string,
+ "mask": string,
+ "ignored_mask": string,
+ "fhandle-bytes": string,
+ "fhandle-type": string,
+ "f_handle": string
+ },
+ "fanotify": {
+ "flags": string,
+ "event-flags": string,
+ "mnt_id": string,
+ "mflags": string,
+ "mask": string,
+ "ignored_mask": string,
+ "ino": string,
+ "sdev": string,
+ "fhandle-bytes": string,
+ "fhandle-type": string,
+ "f_handle": string
+ },
+ "clockid": integer,
+ "ticks": integer,
+ "settime flags": integer,
+ "it_value": [
+ integer
+ ],
+ "it_interval": [
+ integer
+ ]
+ }
+
+Examples:
+
+ $ cat /proc/1/fdinfo/5 | jc --proc -p
+ {
+ "pos": 0,
+ "flags": 2,
+ "mnt_id": 9,
+ "ino": 63107,
+ "clockid": 0,
+ "ticks": 0,
+ "settime flags": 1,
+ "it_value": [
+ 0,
+ 49406829
+ ],
+ "it_interval": [
+ 1,
+ 0
+ ]
+ }
+"""
+import re
+from typing import Dict
+import jc.utils
+
+
+class info():
+ """Provides parser metadata (version, author, etc.)"""
+ version = '1.0'
+ description = '`/proc/pid-fdinfo` file parser'
+ author = 'Kelly Brazil'
+ author_email = 'kellyjonbrazil@gmail.com'
+ compatible = ['linux']
+ hidden = True
+
+
+__version__ = info.version
+
+
+def _process(proc_data: Dict) -> Dict:
+ """
+ Final processing to conform to the schema.
+
+ Parameters:
+
+ proc_data: (List of Dictionaries) raw structured data to process
+
+ Returns:
+
+ Dictionary. Structured to conform to the schema.
+ """
+ root_int_list = {'pos', 'flags', 'mnt_id', 'ino', 'clockid', 'ticks',
+ 'settime flags', 'size', 'count'}
+ epoll_int_list = {'tfd', 'pos'}
+ inotify_int_list = {'wd'}
+
+ for key, val in proc_data.items():
+ if key in root_int_list:
+ proc_data[key] = int(val)
+
+ if 'epoll' in proc_data:
+ for key, val in proc_data['epoll'].items():
+ if key in epoll_int_list:
+ proc_data['epoll'][key] = int(val)
+
+ if 'inotify' in proc_data:
+ for key, val in proc_data['inotify'].items():
+ if key in inotify_int_list:
+ proc_data['inotify'][key] = int(val)
+
+ return proc_data
+
+
+def parse(
+ data: str,
+ raw: bool = False,
+ quiet: bool = False
+) -> Dict:
+ """
+ Main text parsing function
+
+ Parameters:
+
+ data: (string) text data to parse
+ raw: (boolean) unprocessed output if True
+ quiet: (boolean) suppress warning messages if True
+
+ Returns:
+
+ Dictionary. Raw or processed structured data.
+ """
+ jc.utils.compatibility(__name__, info.compatible, quiet)
+ jc.utils.input_type_check(data)
+
+ raw_output: Dict = {}
+ split_me = {'it_value:', 'it_interval:'}
+
+ if jc.utils.has_data(data):
+
+ for line in filter(None, data.splitlines()):
+
+ # epoll files
+ if line.startswith('tfd:'):
+ line_match = re.findall(r'(?P\S+):(?:\s+)?(?P\S+s*)', line)
+ if line_match:
+ raw_output.update({'epoll': {k.strip(): v.strip() for k, v in line_match}})
+ continue
+
+ # inotify files
+ if line.startswith('inotify'):
+ split_line = line[8:].split()
+ raw_output['inotify'] = {}
+ for item in split_line:
+ k, v = item.split(':', maxsplit=1)
+ raw_output['inotify'][k] = v
+ continue
+
+ # fanotify files
+ if line.startswith('fanotify'):
+ split_line = line[9:].split()
+
+ if not 'fanotify' in raw_output:
+ raw_output['fanotify'] = {}
+
+ for item in split_line:
+ k, v = item.split(':', maxsplit=1)
+ raw_output['fanotify'][k] = v
+ continue
+
+ # timerfd files
+ if line.split()[0] in split_me:
+ split_line = line.replace(':', '').replace('(', '').replace(')', '').replace(',', '').split()
+ raw_output[split_line[0]] = [int(x) for x in split_line[1:]]
+ continue
+
+ key, val = line.split(':', maxsplit=1)
+ raw_output[key.strip()] = val.strip()
+ continue
+
+ return raw_output if raw else _process(raw_output)
diff --git a/man/jc.1 b/man/jc.1
index 614b5b62..2c073c3a 100644
--- a/man/jc.1
+++ b/man/jc.1
@@ -1,4 +1,4 @@
-.TH jc 1 2022-09-17 1.21.2 "JSON Convert"
+.TH jc 1 2022-09-19 1.21.2 "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings
.SH SYNOPSIS
@@ -545,6 +545,11 @@ PLIST file parser
\fB--proc-zoneinfo\fP
`/proc/zoneinfo` file parser
+.TP
+.B
+\fB--proc-pid-fdinfo\fP
+`/proc/pid-fdinfo` file parser
+
.TP
.B
\fB--proc-pid-numa-maps\fP