a.mjs
:
// ", ', \ and (space) can be escaped with \
// \ before other symbols is removed
// inside double quotes " and \ can be escaped with \
// \ before other symbols remains
export default function splitCommandLine(l) {
const r = [];
loop: while (l.length) {
l = l.replace(/^ +/, '');
let i = 0;
loop2: while (true) {
i = i + l.slice(i).search(/"|'| |\\|$/);
const c = l.charAt(i);
switch (c) {
case '"':
case "'":
let i2 = 0;
if (c == "'") {
i2 = l.slice(i + 1).indexOf(c);
if (i2 == -1) throw new Error("unterminated '");
} else {
loop3: while (true) {
const r = l.slice(i + 1 + i2).search(/"|\\/);
if (r == -1) throw new Error('unterminated "');
i2 = i2 + r;
const c2 = l.charAt(i + 1 + i2);
switch (c2) {
case '"': break loop3;
case '\\':
if (['"', '\\'].includes(l.charAt(i + 1 + i2 + 1)))
l = l.slice(0, i + 1 + i2)
+ l.slice(i + 1 + i2 + 1);
i2++;
break;
}
}
}
l = l.slice(0, i)
+ l.slice(i + 1, i + 1 + i2)
+ l.slice(i + 1 + i2 + 1);
i = i + i2;
break;
case '\\':
l = l.slice(0, i) + l.slice(i + 1);
i++;
break;
default:
break loop2;
}
}
if (i > 0)
r.push(l.slice(0, i));
l = l.slice(i);
}
return r;
}
a.test.mjs
:
import t from 'tap'
import splitCommandLine from './a.mjs'
t.matchOnly(splitCommandLine(''), []);
t.matchOnly(splitCommandLine('a'), ['a']);
t.matchOnly(splitCommandLine('a b'), ['a', 'b']);
t.matchOnly(splitCommandLine('\\ '), [' ']);
t.matchOnly(splitCommandLine("\\'"), ["'"]);
t.matchOnly(splitCommandLine('\\"'), ['"']);
t.matchOnly(splitCommandLine('\\.'), ['.']);
t.matchOnly(splitCommandLine('"a"'), ['a']);
t.matchOnly(splitCommandLine("'a'"), ['a']);
t.matchOnly(splitCommandLine("'\\ '"), ['\\ ']);
t.matchOnly(splitCommandLine('"\\ "'), ['\\ ']);
t.matchOnly(splitCommandLine('"\\\\"'), ['\\']);
t.matchOnly(splitCommandLine('"\\""'), ['"']);
t.throws(() => { splitCommandLine('"') });
t.throws(() => { splitCommandLine("'") });
$ docker run --rm -itv "$PWD:/app" -w /app alpine:3.20
/app # apk add nodejs npm
/app # npm i tap
/app # npx tap