const TR_CLASS = { '[:alnum:]': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', '[:alpha:]': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '[:blank:]': ' \t', '[:cntrl:]': [...Array(32).keys()].map((i) => String.fromCharCode(i)).join('') + '\x7f', '[:digit:]': '0123456789', '[:lower:]': 'abcdefghijklmnopqrstuvwxyz', '[:print:]': [...Array(95).keys()].map((i) => String.fromCharCode(i + 32)).join(''), '[:punct:]': '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~', '[:space:]': ' \t\n\r\v\f', '[:upper:]': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '[:xdigit:]': '0123456789abcdefABCDEF' } /** @param {string} s */ function trExpandClasses(s) { let out = s for (const [name, chars] of Object.entries(TR_CLASS)) { out = out.split(name).join(chars) } return out } async function run(ctx, argv) { let del = false const sets = [] for (let i = 1; i < argv.length; i++) { if (argv[i] === '-d' || argv[i] === '--delete') del = true else if (argv[i] !== '--') sets.push(argv[i]) } if (!sets.length || (!del && sets.length < 2)) { ctx.console.error('usage: tr [-d] SET1 [SET2]') ctx.exitCode = 1 return } const s = bareStdin(ctx) if (del) { const kill = new Set(trExpandClasses(sets[0]).split('')) let o = '' for (const ch of s) if (!kill.has(ch)) o += ch ctx.console.log(o) return } const from = trExpandClasses(sets[0]) const to = trExpandClasses(sets[1]) const map = Object.create(null) const n = Math.max(from.length, to.length) for (let i = 0; i < n; i++) { const fc = from[i] || from[from.length - 1] const tc = to[i] != null ? to[i] : to[to.length - 1] || '' map[fc] = tc } let o = '' for (const ch of s) o += map[ch] != null ? map[ch] : ch ctx.console.log(o) }