diff --git a/lib/index.js b/lib/index.js index 893c57b..d97ea9f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,8 @@ var ChildProcess = require('child_process'); var IS_WIN = process.platform === 'win32'; var TableParser = require('table-parser'); +var parsePSOutput = require('./parse-output'); + /** * End of line. * Basically, the EOL should be: @@ -157,7 +159,7 @@ exports.lookup = function (query, callback) { return callback(err); } else { - var processList = parseGrid(output); + var processList = IS_WIN ? parseGrid(output) : parsePSOutput(output); var resultList = []; processList.forEach(function (p) { diff --git a/lib/parse-output.js b/lib/parse-output.js new file mode 100644 index 0000000..b1856bb --- /dev/null +++ b/lib/parse-output.js @@ -0,0 +1,47 @@ +var split = require('split-string'); +var EOL = /(\r\n)|(\n\r)|\n|\r/; + +module.exports = parsePSOutput; + +/** + * Parse the stdout into readable object. + * @param {String} output + */ +function parsePSOutput(output) { + var lines = (output || '') + .split(EOL) + .filter(function(p) { + return p !== undefined; + }); + + var header = lines[0] || ''; + var fieldNames = splitLine(header); + var pidIdx = fieldNames.indexOf('PID'); + var ppidIdx = fieldNames.indexOf('PPID'); + var cmdIdx = fieldNames.indexOf('COMMAND'); + + return lines + .slice(1) + .map(function (line) { + var fields = splitLine(line); + var pid = fields[pidIdx]; + var ppid = fields[ppidIdx]; + var cmd = fields[cmdIdx]; + var args = cmdIdx !== -1 ? fields.slice(cmdIdx + 1) : []; + return { + pid: pid, + command: cmd, + arguments: args, + ppid: ppid + }; + }) + .filter(function (proc) { + return proc.pid && proc.command; + }); +} + +function splitLine(line) { + return split(line, {sep: ' ', brackets: false, keepDoubleQuotes: true, keepSingleQuotes: true}).filter(function (word) { + return word !== ''; + }); +} diff --git a/package.json b/package.json index d0ec26a..d74bfb4 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "pid" ], "dependencies": { + "split-string": "^3.0.2", "table-parser": "^0.1.3" }, "license": "MIT", diff --git a/test/test.js b/test/test.js index a682004..a859762 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,5 @@ var PS = require('../index'); +var parsePSOutput = require('../lib/parse-output'); var CP = require('child_process'); var assert = require('assert'); var Path = require('path'); @@ -196,4 +197,119 @@ describe('test', function () { }); }); }); + + describe('OSX formatted ps output', function () { + it('should be parsed correctly', function () { + var output = + 'F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND\n' + + '4 1000 3002 1 20 0 45248 0 ep_pol Ss ? 0:00 /lib/systemd/systemd --user\n' + + '5 1000 3006 3002 20 0 163688 4 - S ? 0:00 (sd-pam)\n' + + '0 1000 3101 1 20 0 9657524 6355656 poll_s Sl ? 1440:57 mysqld --datadir /aa/aa/aa\n' + + '0 1000 3178 1 20 0 808752 247892 hrtime Sl ? 11234:03 ./aa -aa /aa/aa/aa/aa.aa -aa /aa/aa/aa/aa/aa/aa/aa -aa /aa/aa/aa/aa/aa/bb/bb.bb.bb.bb -t 4 -bb /cc/cc/cc/cc/cc/cc/cc.cc -cc /a/a/a/a/a/a/a.a\n' + + '0 1000 3198 1 20 0 639312 0 hrtime Sl ? 3:54 ./h -d /a/a/a -p 3\n' + + '0 1000 9215 1 20 0 1879292 95352 ep_pol Sl ? 47:06 node ./d/d\n' + + '0 1000 12547 1 20 0 1931424 21016 ep_pol Sl ? 0:14 node app.js --prod\n' + + '0 1000 17244 1 20 0 1450676 190724 ep_pol Sl ? 226:10 node app.js --prod --name="My app"\n' + + '1 1000 17789 1 20 0 79940 44376 - S ? 17:43 tor --runasdaemon 1\n' + + '5 1000 21352 21325 20 0 113860 1236 - S ? 0:01 sshd: user@pts/8\n' + + '0 1000 21353 21352 20 0 22676 3804 wait_w Ss+ pts/8 0:00 -bash\n' + + '5 1000 21675 21647 20 0 113868 1232 - S ? 0:00 sshd: user@pts/9\n' + + '0 1000 21676 21675 20 0 22788 4748 wait Ss pts/9 0:00 -bash\n' + + '0 1000 21973 21676 20 0 920496 28816 ep_pol Sl+ pts/9 0:00 node\n' + + '0 1000 21987 21973 20 0 28916 1500 - R+ pts/9 0:00 ps lx\n'; + var parsedProcesses = parsePSOutput(output); + assert.deepEqual(parsedProcesses, [ + { + 'pid': '3002', + 'command': '/lib/systemd/systemd', + 'arguments': ['--user'], + 'ppid': '1' + }, + { + 'pid': '3006', + 'command': '(sd-pam)', + 'arguments': [], + 'ppid': '3002' + }, + { + 'pid': '3101', + 'command': 'mysqld', + 'arguments': ['--datadir', '/aa/aa/aa'], + 'ppid': '1' + }, + { + 'pid': '3178', + 'command': './aa', + 'arguments': ['-aa', '/aa/aa/aa/aa.aa', '-aa', '/aa/aa/aa/aa/aa/aa/aa', '-aa', '/aa/aa/aa/aa/aa/bb/bb.bb.bb.bb', '-t', '4', '-bb', '/cc/cc/cc/cc/cc/cc/cc.cc', '-cc', '/a/a/a/a/a/a/a.a'], + 'ppid': '1' + }, + { + 'pid': '3198', + 'command': './h', + 'arguments': [ '-d', '/a/a/a', '-p', '3'], + 'ppid': '1' + }, + { + 'pid': '9215', + 'command': 'node', + 'arguments': ['./d/d'], + 'ppid': '1' + }, + { + 'pid': '12547', + 'command': 'node', + 'arguments': ['app.js', '--prod'], + 'ppid': '1' + }, + { + 'pid': '17244', + 'command': 'node', + 'arguments': ['app.js', '--prod', '--name="My app"'], + 'ppid': '1' + }, + { + 'pid': '17789', + 'command': 'tor', + 'arguments': ['--runasdaemon', '1'], + 'ppid': '1' + }, + { + 'pid': '21352', + 'command': 'sshd:', + 'arguments': ['user@pts/8'], + 'ppid': '21325' + }, + { + 'pid': '21353', + 'command': '-bash', + 'arguments': [], + 'ppid': '21352' + }, + { + 'pid': '21675', + 'command': 'sshd:', + 'arguments': ['user@pts/9'], + 'ppid': '21647' + }, + { + 'pid': '21676', + 'command': '-bash', + 'arguments': [], + 'ppid': '21675' + }, + { + 'pid': '21973', + 'command': 'node', + 'arguments': [], + 'ppid': '21676' + }, + { + 'pid': '21987', + 'command': 'ps', + 'arguments': ['lx'], + 'ppid': '21973' + } + ]); + }); + }); });