New utils
This commit is contained in:
@@ -76,4 +76,4 @@ npm test -w bare-os-booter
|
||||
|
||||
- [Developer guide — Bare modules](../../developer-guide/12-bare-modules-and-pear-ecosystem.md)
|
||||
- [Handbook — Booter runtime](../../handbook/04-the-booter-runtime.md)
|
||||
- [DOCUMENTATION.md](../../DOCUMENTATION.md) §12 (detailed; some lists may lag code)
|
||||
- [DOCUMENTATION.md](../../DOCUMENTATION.md) §12 — **`packages/bare-os-coreutils/lib/commands.mjs`** is the authoritative **`/bin`** name list
|
||||
|
||||
@@ -293,7 +293,12 @@ async function executeKernel(disk, store, swarm, initSource) {
|
||||
'BARE_OS_HTTP_DENYLIST',
|
||||
'BARE_OS_TLS_PIN_SHA256',
|
||||
'BARE_OS_BARE_MODULES',
|
||||
'BARE_OS_BARE_DRIVE_BUNDLES'
|
||||
'BARE_OS_BARE_DRIVE_BUNDLES',
|
||||
'BARE_OS_FIND_EXEC_MAX',
|
||||
'BARE_OS_YES_MAX_LINES',
|
||||
'BARE_OS_SHUF_MAX_LINES',
|
||||
'BARE_OS_SPLIT_MAX_FILES',
|
||||
'BARE_OS_NPROC'
|
||||
]) {
|
||||
const v = hostEnv[k]
|
||||
if (v != null && v !== '') shellEnv[k] = v
|
||||
|
||||
+333
-11
@@ -642,7 +642,9 @@ test('ls --color=always colors directory blue and executable green', async (t) =
|
||||
await ctx.vfs.writeFile('xfile', b4a.from(''))
|
||||
await ctx.vfs.chmod('xfile', 0o755)
|
||||
await runBinCommand(ctx, ['ls', '--color=always'])
|
||||
const shortLine = lines.find((l) => l.includes('subdir') && l.includes('xfile'))
|
||||
const shortLine = lines.find(
|
||||
(l) => l.includes('subdir') && l.includes('xfile')
|
||||
)
|
||||
t.ok(shortLine)
|
||||
t.ok(
|
||||
/\x1b\[[0-9;]*msubdir\x1b\[0m/.test(shortLine),
|
||||
@@ -1233,7 +1235,11 @@ test('buildBareCtxObjectFromHost loads core keys on Node', async (t) => {
|
||||
})
|
||||
|
||||
test('maybeMergeBareFromDrive fills missing keys from bundle (mock vfs)', async (t) => {
|
||||
const repoRoot = path.join(fileURLToPath(new URL('.', import.meta.url)), '..', '..')
|
||||
const repoRoot = path.join(
|
||||
fileURLToPath(new URL('.', import.meta.url)),
|
||||
'..',
|
||||
'..'
|
||||
)
|
||||
const bundleAbs = path.join(repoRoot, 'kernel/lib/bare/bundles/b4a.js')
|
||||
const bundleSrc = await readFile(bundleAbs)
|
||||
const manifest = {
|
||||
@@ -1243,7 +1249,8 @@ test('maybeMergeBareFromDrive fills missing keys from bundle (mock vfs)', async
|
||||
const target = {}
|
||||
const vfs = {
|
||||
async readFile(p) {
|
||||
if (p === '/lib/bare/manifest.json') return b4a.from(JSON.stringify(manifest))
|
||||
if (p === '/lib/bare/manifest.json')
|
||||
return b4a.from(JSON.stringify(manifest))
|
||||
if (p === '/lib/bare/bundles/b4a.js') return new Uint8Array(bundleSrc)
|
||||
return null
|
||||
}
|
||||
@@ -1273,7 +1280,10 @@ test('defaultShellAliases does not remap sed', async (t) => {
|
||||
|
||||
test('defaultShellAliases nano maps to edit', async (t) => {
|
||||
t.is(defaultShellAliases().nano, 'edit')
|
||||
t.alike(expandArgvAliases(['nano', 'x'], defaultShellAliases()), ['edit', 'x'])
|
||||
t.alike(expandArgvAliases(['nano', 'x'], defaultShellAliases()), [
|
||||
'edit',
|
||||
'x'
|
||||
])
|
||||
})
|
||||
|
||||
test('expandArgvAliases throws on cyclic alias chain', async (t) => {
|
||||
@@ -1422,7 +1432,9 @@ test('bareLsColorOpenSgrFromMap ca when stat has capabilities', async (t) => {
|
||||
})
|
||||
|
||||
test('applyBareOsThemeFromEnv BARE_OS_COLOR_DEPTH=256 drops truecolor', async (t) => {
|
||||
const ctx = { vfs: { env: { BARE_OS_THEME: 'nord', BARE_OS_COLOR_DEPTH: '256' } } }
|
||||
const ctx = {
|
||||
vfs: { env: { BARE_OS_THEME: 'nord', BARE_OS_COLOR_DEPTH: '256' } }
|
||||
}
|
||||
await applyBareOsThemeFromEnv(ctx)
|
||||
const p = String(ctx.vfs.env.BARE_OS_COLOR_PROMPT || '')
|
||||
t.ok(p.includes('38;5;'), '256-color palette index SGR')
|
||||
@@ -1436,8 +1448,7 @@ test('dircolors -p includes TERM and di', async (t) => {
|
||||
})
|
||||
|
||||
test('bareParseDircolorsDatabase TERM block', async (t) => {
|
||||
const text =
|
||||
'TERM xterm\ndi 01;34\nTERM none\nfi 00\nTERM *\nln 01;36\n'
|
||||
const text = 'TERM xterm\ndi 01;34\nTERM none\nfi 00\nTERM *\nln 01;36\n'
|
||||
const m = bareParseDircolorsDatabase(text, 'xterm')
|
||||
t.is(m.di, '01;34')
|
||||
t.is(m.ln, '01;36')
|
||||
@@ -1676,10 +1687,9 @@ test('ls bareOsStdoutCaptured lists one name per line', async (t) => {
|
||||
await ctx.vfs.writeFile('aaa', b4a.from(''))
|
||||
await ctx.vfs.writeFile('lib', b4a.from(''))
|
||||
await ctx.vfs.writeFile('zzz', b4a.from(''))
|
||||
await runBinCommand(
|
||||
Object.assign({}, ctx, { bareOsStdoutCaptured: true }),
|
||||
['ls']
|
||||
)
|
||||
await runBinCommand(Object.assign({}, ctx, { bareOsStdoutCaptured: true }), [
|
||||
'ls'
|
||||
])
|
||||
t.is(lines.length, 3)
|
||||
t.ok(lines.includes('aaa') && lines.includes('lib') && lines.includes('zzz'))
|
||||
await store.close()
|
||||
@@ -2636,6 +2646,12 @@ async function run(ctx, argv) {
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(lines.pop(), '4096')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['getconf', 'BARE_OS_YES_MAX_LINES'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(lines.pop(), '100000')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['getconf', 'NOT_A_REAL_CONF_NAME'])
|
||||
@@ -3580,6 +3596,312 @@ test('runBinCommand man ls prints manual text', async (t) => {
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test('coreutils sed and awk fixture corpus', async (t) => {
|
||||
const corpusPath = path.join(
|
||||
__dirname,
|
||||
'../bare-os-coreutils/fixtures/sed-awk-corpus.json'
|
||||
)
|
||||
const corpus = JSON.parse(await readFile(corpusPath, 'utf8'))
|
||||
const dir = testCorestoreDir('sedawkcorpus')
|
||||
const store = new Corestore(dir)
|
||||
const drive = new Hyperdrive(store)
|
||||
const personal = new Hyperdrive(store.namespace('psac'))
|
||||
await drive.ready()
|
||||
await personal.ready()
|
||||
await drive.put('/bin/sed', b4a.from(await readBuiltBin('sed')))
|
||||
await drive.put('/bin/awk', b4a.from(await readBuiltBin('awk')))
|
||||
const lines = []
|
||||
const ctx = testCtx(drive, personal)
|
||||
ctx.exitCode = 0
|
||||
ctx.console = {
|
||||
log(s) {
|
||||
lines.push(String(s))
|
||||
},
|
||||
error(...a) {
|
||||
lines.push(a.join(' '))
|
||||
}
|
||||
}
|
||||
function corpusNormLines(logs) {
|
||||
return logs
|
||||
.flatMap((l) => String(l).split('\n'))
|
||||
.filter((x) => x.length > 0)
|
||||
}
|
||||
for (const c of corpus.sed) {
|
||||
await ctx.vfs.writeFile(c.file, b4a.from(c.content))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, c.argv)
|
||||
t.is(ctx.exitCode, 0, 'sed ' + c.name)
|
||||
t.alike(corpusNormLines(lines), c.lines, 'sed ' + c.name)
|
||||
}
|
||||
for (const c of corpus.awk) {
|
||||
if (c.progFile) {
|
||||
await ctx.vfs.writeFile(c.progFile, b4a.from(c.progContent))
|
||||
}
|
||||
if (c.file) await ctx.vfs.writeFile(c.file, b4a.from(c.content))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, c.argv)
|
||||
t.is(ctx.exitCode, 0, 'awk ' + c.name)
|
||||
t.alike(corpusNormLines(lines), c.lines, 'awk ' + c.name)
|
||||
}
|
||||
await store.close()
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test('coreutils matrix: cp find sort printf uniq realpath sha256sum base64 rm -d', async (t) => {
|
||||
const dir = testCorestoreDir('corematrix')
|
||||
const store = new Corestore(dir)
|
||||
const drive = new Hyperdrive(store)
|
||||
const personal = new Hyperdrive(store.namespace('pcm'))
|
||||
await drive.ready()
|
||||
await personal.ready()
|
||||
const bins = [
|
||||
'cp',
|
||||
'find',
|
||||
'sort',
|
||||
'printf',
|
||||
'uniq',
|
||||
'realpath',
|
||||
'sha256sum',
|
||||
'base64',
|
||||
'rm',
|
||||
'mkdir',
|
||||
'touch'
|
||||
]
|
||||
for (const b of bins) {
|
||||
await drive.put('/bin/' + b, b4a.from(await readBuiltBin(b)))
|
||||
}
|
||||
await drive.put(
|
||||
'/bin/_corpus_echo',
|
||||
b4a.from(`async function run(ctx, argv) {
|
||||
ctx.console.log(argv.slice(1).join(' '))
|
||||
}
|
||||
`)
|
||||
)
|
||||
const lines = []
|
||||
const ctx = testCtx(drive, personal)
|
||||
ctx.runBinCommand = (argv) => runBinCommand(ctx, argv)
|
||||
ctx.exitCode = 0
|
||||
ctx.console = {
|
||||
log(s) {
|
||||
lines.push(String(s))
|
||||
},
|
||||
error(...a) {
|
||||
lines.push(a.join(' '))
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.vfs.mkdir('tree', { recursive: true })
|
||||
await ctx.vfs.writeFile('tree/a.txt', b4a.from('newdata'))
|
||||
await ctx.vfs.writeFile('dest.txt', b4a.from('stale'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['touch', '-d', '@1', 'dest.txt'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['touch', '-d', '@100000', 'tree/a.txt'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['cp', '-u', 'tree/a.txt', 'dest.txt'])
|
||||
t.is(ctx.exitCode, 0, 'cp -u')
|
||||
t.is(ctx.b4a.toString(await ctx.vfs.readFile('dest.txt')), 'newdata')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['find', '.', '-type', 'f', '-regex', '.*\\.txt$'])
|
||||
t.is(ctx.exitCode, 0, 'find -regex')
|
||||
t.ok(lines.some((l) => String(l).includes('a.txt')))
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['find', 'tree', '-exec', '_corpus_echo', '{}', ';'])
|
||||
t.is(ctx.exitCode, 0, 'find -exec')
|
||||
t.ok(lines.some((l) => /a\.txt/.test(String(l))))
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
ctx.shellStdin = '3\n1\n2\n'
|
||||
await runBinCommand(ctx, ['sort', '-n'])
|
||||
t.is(lines.join('\n'), '1\n2\n3')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['printf', '%s-%d', 'n', '7'])
|
||||
t.is(lines[0], 'n-7')
|
||||
|
||||
await ctx.vfs.writeFile('u.txt', b4a.from('a\na\nb\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['uniq', '-c', 'u.txt'])
|
||||
t.ok(lines.some((l) => /^\s*2\s+a$/.test(String(l))))
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['realpath', 'tree/a.txt'])
|
||||
t.ok(/a\.txt$/.test(lines[0]))
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['sha256sum', 'u.txt'])
|
||||
t.is(lines.length, 1)
|
||||
t.ok(/^[a-f0-9]{64}\s+/.test(lines[0]))
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['base64', '-w', '0', 'u.txt'])
|
||||
const dec = Buffer.from(
|
||||
lines.join('').replace(/\s+/g, ''),
|
||||
'base64'
|
||||
).toString()
|
||||
t.is(dec, 'a\na\nb\n')
|
||||
|
||||
await ctx.vfs.mkdir('emptydir', { recursive: true })
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['rm', '-d', 'emptydir'])
|
||||
t.is(ctx.exitCode, 0, 'rm -d')
|
||||
|
||||
await store.close()
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test('coreutils gnu-gap batch: paste tac rev md5sum expr tsort numfmt truncate install comm join', async (t) => {
|
||||
const dir = testCorestoreDir('gnugap')
|
||||
const store = new Corestore(dir)
|
||||
const drive = new Hyperdrive(store)
|
||||
const personal = new Hyperdrive(store.namespace('ngg'))
|
||||
await drive.ready()
|
||||
await personal.ready()
|
||||
const bins = [
|
||||
'paste',
|
||||
'tac',
|
||||
'rev',
|
||||
'md5sum',
|
||||
'expr',
|
||||
'tsort',
|
||||
'numfmt',
|
||||
'sync',
|
||||
'truncate',
|
||||
'install',
|
||||
'unlink',
|
||||
'comm',
|
||||
'join',
|
||||
'yes',
|
||||
'cp',
|
||||
'touch',
|
||||
'mkdir',
|
||||
'ls'
|
||||
]
|
||||
for (const b of bins) {
|
||||
await drive.put('/bin/' + b, b4a.from(await readBuiltBin(b)))
|
||||
}
|
||||
const lines = []
|
||||
const ctx = testCtx(drive, personal)
|
||||
ctx.runBinCommand = (argv) => runBinCommand(ctx, argv)
|
||||
ctx.exitCode = 0
|
||||
ctx.console = {
|
||||
log(s) {
|
||||
lines.push(String(s))
|
||||
},
|
||||
error(...a) {
|
||||
lines.push(a.join(' '))
|
||||
}
|
||||
}
|
||||
ctx.vfs.env.BARE_OS_YES_MAX_LINES = '2'
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['yes', 'ok'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(lines.filter((l) => l === 'ok').length, 2)
|
||||
|
||||
await ctx.vfs.writeFile('nums.txt', b4a.from('1\n2\n3\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['tac', 'nums.txt'])
|
||||
t.is(lines.join('\n'), '3\n2\n1')
|
||||
|
||||
await ctx.vfs.writeFile('rv.txt', b4a.from('ab\ncd\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['rev', 'rv.txt'])
|
||||
t.is(lines.join('\n'), 'ba\ndc')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['md5sum', 'nums.txt'])
|
||||
t.ok(/^[a-f0-9]{32}\s/.test(lines[0]))
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['expr', '1', '+', '2', '*', '3'])
|
||||
t.is(lines[0], '7')
|
||||
|
||||
await ctx.vfs.writeFile('ts.txt', b4a.from('a b\nb c\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['tsort', 'ts.txt'])
|
||||
t.is(lines.join('\n'), 'a\nb\nc')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['numfmt', '--to=iec', '1024'])
|
||||
t.is(lines[0], '1K')
|
||||
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['sync'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
|
||||
await ctx.vfs.writeFile('tr.txt', b4a.from('hello'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['truncate', '-s', '2', 'tr.txt'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(ctx.b4a.toString(await ctx.vfs.readFile('tr.txt')), 'he')
|
||||
|
||||
await ctx.vfs.writeFile('src.txt', b4a.from('data'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['install', '-m', '600', 'src.txt', 'dest.txt'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(ctx.b4a.toString(await ctx.vfs.readFile('dest.txt')), 'data')
|
||||
|
||||
await ctx.vfs.writeFile('s1.txt', b4a.from('a\nb\n'))
|
||||
await ctx.vfs.writeFile('s2.txt', b4a.from('a\nc\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['comm', 's1.txt', 's2.txt'])
|
||||
t.ok(lines.some((l) => l.includes('\t\ta')))
|
||||
|
||||
await ctx.vfs.writeFile('j1.txt', b4a.from('1 x\n2 y\n'))
|
||||
await ctx.vfs.writeFile('j2.txt', b4a.from('1 p\n2 q\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['join', 'j1.txt', 'j2.txt'])
|
||||
t.ok(lines.some((l) => /1\s+x\s+1\s+p/.test(String(l))))
|
||||
|
||||
await ctx.vfs.writeFile('u.txt', b4a.from('x'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['unlink', 'u.txt'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
t.is(await ctx.vfs.readFile('u.txt'), null)
|
||||
|
||||
await ctx.vfs.writeFile('p1.txt', b4a.from('a\nb\n'))
|
||||
await ctx.vfs.writeFile('p2.txt', b4a.from('1\n2\n'))
|
||||
lines.length = 0
|
||||
ctx.exitCode = 0
|
||||
await runBinCommand(ctx, ['paste', 'p1.txt', 'p2.txt'])
|
||||
t.is(lines[0], 'a\t1')
|
||||
t.is(lines[1], 'b\t2')
|
||||
|
||||
await store.close()
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
async function readBuiltBin(name) {
|
||||
const fs = await import('node:fs/promises')
|
||||
const p = path.join(__dirname, '../../kernel/bin', name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# bare-os-coreutils
|
||||
|
||||
**Build step**, not a runtime library: validates and merges **`man/pages/*.json`** into **`kernel/share/man/man.json`** (see **`scripts/build-man-db.mjs`**), then concatenates **`lib/runtime.js`**, optional **`lib/*-engine.js`** or **`lib/man-render.js`** chunks (see **`preamble`** in **`build.mjs`** — **`sed`**, **`awk`**, **`man`**), then each **`src/<command>.js`**, and writes standalone scripts to:
|
||||
**Build step**, not a runtime library: validates and merges **`man/pages/*.json`** into **`kernel/share/man/man.json`** (see **`scripts/build-man-db.mjs`**), then concatenates **`lib/runtime.js`**, optional preamble chunks (see **`preamble`** in **`build.mjs`** — e.g. **`md5sum`** → **`lib/md5.js`**, **`sed`**, **`awk`**, **`jq`**, **`man`**, lscolors, **`edit`**/**`nano`** TUI), then each **`src/<command>.js`**, and writes standalone scripts to:
|
||||
|
||||
- `kernel/bin/<command>` — staged into the **system** Hyperdrive as `/bin/*`
|
||||
- `packages/bare-os-seeder/kernel/bin/<command>` — **vendored** copy for Pear bundles (seeder has no sibling `bare-os-coreutils` at runtime)
|
||||
@@ -33,7 +33,7 @@ Or `node packages/bare-os-coreutils/build.mjs`.
|
||||
|
||||
**Source of truth:** **`lib/commands.mjs`** — **`COREUTILS_COMMANDS`** (imported by **`build.mjs`** and **`scripts/build-man-db.mjs`**). Each name must have **`man/pages/<name>.json`**.
|
||||
|
||||
`awk`, `basename`, `cat`, `chgrp`, `chmod`, `chown`, `cksum`, `clear`, `cp`, `crontab`, `cut`, `date`, `dirname`, `dircolors`, `du`, `edit`, `echo`, `env`, `exit`, `false`, `find`, `getconf`, `git-pear`, `grep`, `head`, `hdms`, `help`, `hostname`, `id`, `jq`, `ln`, `login`, `logout`, `logname`, `ls`, `man`, `mkdir`, `mkfifo`, `mktemp`, `mv`, `nano`, `nl`, `od`, `pathchk`, `printenv`, `printf`, `pwd`, `readlink`, `rm`, `rmdir`, `savevault`, `sed`, `seq`, `sleep`, `sort`, `stat`, `tail`, `tee`, `test`, `theme`, `time`, `touch`, `tr`, `true`, `tty`, `uname`, `wc`, `which`, `whoami`, `xargs`
|
||||
`arch`, `awk`, `base32`, `base64`, `basename`, `basenc`, `cat`, `chgrp`, `chmod`, `chown`, `cksum`, `clear`, `comm`, `cp`, `crontab`, `cut`, `date`, `df`, `dir`, `dirname`, `dircolors`, `du`, `edit`, `echo`, `env`, `exit`, `expand`, `expr`, `factor`, `false`, `find`, `fmt`, `fold`, `getconf`, `git-pear`, `grep`, `groups`, `head`, `hdms`, `help`, `hostid`, `hostname`, `id`, `install`, `join`, `jq`, `ln`, `login`, `logout`, `logname`, `ls`, `man`, `md5sum`, `mkdir`, `mkfifo`, `mktemp`, `mv`, `nano`, `nl`, `nproc`, `numfmt`, `od`, `paste`, `pathchk`, `pr`, `printenv`, `printf`, `pwd`, `readlink`, `realpath`, `rev`, `rm`, `rmdir`, `savevault`, `sed`, `seq`, `sha1sum`, `sha256sum`, `sha512sum`, `shuf`, `sleep`, `sort`, `split`, `stat`, `sum`, `sync`, `tac`, `tail`, `tee`, `test`, `theme`, `time`, `touch`, `tr`, `truncate`, `true`, `tsort`, `tty`, `uname`, `uniq`, `unlink`, `unexpand`, `uptime`, `users`, `vdir`, `wc`, `which`, `who`, `whoami`, `xargs`, `yes`
|
||||
|
||||
**`edit`** is a full-screen TTY buffer editor (syntax highlighting, search, save). **`nano`** is built from the same **`src/edit.js`** with the same **`lib/edit-*.js`** preamble; **`/bin/nano`** exists for familiarity, and the stock shell alias **`nano` → `edit`** routes **`nano`** to that utility (see **`packages/bare-os-booter/lib/shell.js`**). Both require a real TTY (**`stdout.isTTY`**).
|
||||
|
||||
@@ -47,7 +47,9 @@ Or `node packages/bare-os-coreutils/build.mjs`.
|
||||
|
||||
**Pipelines:** the shell sets **`ctx.bareOsStdoutCaptured`** when a command’s stdout is captured (pipe or **`>`** / **`>>`**). **`ls`** prints **one name per line** in short mode in that case (GNU-like), so **`ls | grep`** / **`sort`** / **`wc`** see one record per line.
|
||||
|
||||
**Recent parity / UX:** **`tail -f`** (watch or poll; **`BARE_OS_TAIL_F_*`** env), **`head`/`tail -c`** and **`+` line/byte offsets**, **`sort -n/-r/-u/-f/-k/-t`**, **`sort -c/-C`** (check), **`-s`** (stable), **`-o`**, **`wc -l/-w/-c`**, **`grep -A/-B/-C`** and **`--color`**, **`date +FORMAT`** (strftime-like subset), **`test`** integer compares and **`-h`/`-L`**, **`xargs -I`**, **`find -iname`/`-print0`**, **`du -h`**, **`basename -a`/`-s`**, **`dirname -z`**. Phase 2 adds **`cat -n/-A`**, **`env -i`**, **`touch -a/-m/-d/-r`**, **`mkdir -m`**, **`grep -r`**, **`cp`/`mv` `-L`/`-P`**, **`readlink -f`**, **`rmdir -p`**, **`du -a/-L`**, **`find -mtime`/`-newer`/`-prune`/`-empty`/`-delete`** (gated), **`stat --format`**, **`cut -s`**, **`tr [:class:]`**, richer **`od`/`nl`/`seq`/`pathchk`/`printf`/`time`**, and **`bareOsBinWrite`** for captured NUL output in tests.
|
||||
**GNU-style text / data utilities (bounded where needed):** **`paste`**, **`split`** (**`BARE_OS_SPLIT_MAX_FILES`**), **`tac`**, **`rev`**, **`expand`**, **`unexpand`**, **`fold`**, **`fmt`**, **`comm`**, **`join`**, **`pr`**, **`yes`** (**`BARE_OS_YES_MAX_LINES`**), **`shuf`** (**`BARE_OS_SHUF_MAX_LINES`**), **`tsort`**, **`factor`**, **`expr`**, **`numfmt`**. **Checksums / encodings:** **`md5sum`**, **`sha1sum`**, **`sha256sum`**, **`sha512sum`**, **`sum`**, **`base32`**, **`basenc --base16`**. **Files / stubs:** **`truncate`**, **`unlink`**, **`install`**, **`df`**, **`sync`**, **`dir`**/**`vdir`** (delegate to **`ls`**), **`arch`**, **`groups`**, **`hostid`**, **`nproc`**, **`uptime`**, **`users`**, **`who`**.
|
||||
|
||||
**Earlier parity / UX:** **`tail -f`** (watch or poll; **`BARE_OS_TAIL_F_*`** env), **`head`/`tail -c`** and **`+` line/byte offsets**, **`sort -n/-r/-u/-f/-k/-t`**, **`sort -c/-C`** (check), **`-s`** (stable), **`-o`**, **`wc -l/-w/-c`**, **`grep -A/-B/-C`** and **`--color`**, **`date +FORMAT`** (strftime-like subset), **`test`** integer compares and **`-h`/`-L`**, **`xargs -I`**, **`find -iname`/`-print0`**, **`du -h`**, **`basename -a`/`-s`**, **`dirname -z`**, **`cat -n/-A`**, **`env -i`**, **`touch -a/-m/-d/-r`**, **`mkdir -m`**, **`grep -r`**, **`cp`/`mv` `-L`/`-P`**, **`readlink -f`**, **`rmdir -p`**, **`du -a/-L`**, **`find -mtime`/`-newer`/`-prune`/`-empty`/`-delete`** (gated), **`stat --format`**, **`uniq`**, **`realpath`**, **`base64`**, **`rm -d`**, **`cut -s`**, **`tr [:class:]`**, richer **`od`/`nl`/`seq`/`pathchk`/`printf`/`time`**, and **`bareOsBinWrite`** for captured NUL output in tests. Cap names for **`getconf`** include **`BARE_OS_FIND_EXEC_MAX`**, **`BARE_OS_YES_MAX_LINES`**, **`BARE_OS_SHUF_MAX_LINES`**, **`BARE_OS_SPLIT_MAX_FILES`**.
|
||||
|
||||
See [handbook §6 — Kernel and `/bin`](../../handbook/06-kernel-and-binaries.md), [handbook §9 — POSIX alignment](../../handbook/09-posix-utilities-shell-and-vfs.md), and [handbook §10 — `man` and online help](../../handbook/10-manpages-and-online-help.md).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { readFile, writeFile, mkdir } from 'fs/promises'
|
||||
import { dirname, join } from 'path'
|
||||
import { fileURLToPath, pathToFileURL } from 'url'
|
||||
|
||||
import { COREUTILS_COMMANDS } from './lib/commands.mjs'
|
||||
import { COREUTILS_COMMANDS, HELP_DELEGATED_COMMANDS } from './lib/commands.mjs'
|
||||
import { buildManDb } from './scripts/build-man-db.mjs'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
@@ -12,6 +12,7 @@ const seederKernelBin = join(repoRoot, 'packages/bare-os-seeder/kernel/bin')
|
||||
|
||||
/** Commands whose /bin script is preceded by extra library sources (no import in src). */
|
||||
const preamble = {
|
||||
md5sum: ['md5.js'],
|
||||
sed: ['sed-engine.js'],
|
||||
awk: ['awk-engine.js'],
|
||||
jq: ['jq-engine.js'],
|
||||
@@ -64,12 +65,21 @@ export async function build() {
|
||||
? join(repoRoot, 'packages/bare-os-lscolors/bare-os-lscolors.js')
|
||||
: join(__dirname, 'lib', f)
|
||||
let chunk = await readFile(chunkPath, 'utf8')
|
||||
if (f === 'bare-os-lscolors.js') chunk = stripLscolorsBundleExport(chunk)
|
||||
if (f === 'bare-os-lscolors.js')
|
||||
chunk = stripLscolorsBundleExport(chunk)
|
||||
pre += chunk + '\n'
|
||||
}
|
||||
}
|
||||
const srcName = commandSourceFile(name)
|
||||
const body = await readFile(join(__dirname, 'src', `${srcName}.js`), 'utf8')
|
||||
let body = await readFile(join(__dirname, 'src', `${srcName}.js`), 'utf8')
|
||||
if (name === 'help') {
|
||||
const helpBins = [...commands, ...HELP_DELEGATED_COMMANDS].sort()
|
||||
body =
|
||||
'var BARE_OS_HELP_BIN_SPACED = ' +
|
||||
JSON.stringify(helpBins.join(' ')) +
|
||||
'\n' +
|
||||
body
|
||||
}
|
||||
const out = runtime + '\n' + pre + body
|
||||
await writeFile(join(kernelBin, name), out)
|
||||
await writeFile(join(seederKernelBin, name), out)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"description": "Regression snippets for Bare OS sed/awk engines; not a full POSIX conformance suite.",
|
||||
"sed": [
|
||||
{
|
||||
"name": "substitute_first_per_line",
|
||||
"file": "_corpus_sed1.txt",
|
||||
"content": "foo bar\nbaz foo\n",
|
||||
"argv": ["sed", "-e", "s/foo/FOO/", "_corpus_sed1.txt"],
|
||||
"lines": ["FOO bar", "baz FOO"]
|
||||
},
|
||||
{
|
||||
"name": "delete_matching_lines",
|
||||
"file": "_corpus_sed2.txt",
|
||||
"content": "keep\ndrop\nkeep2\n",
|
||||
"argv": ["sed", "-e", "/drop/d", "_corpus_sed2.txt"],
|
||||
"lines": ["keep", "keep2"]
|
||||
}
|
||||
],
|
||||
"awk": [
|
||||
{
|
||||
"name": "begin_print_literal",
|
||||
"argv": ["awk", "BEGIN{print 7}"],
|
||||
"lines": ["7"]
|
||||
},
|
||||
{
|
||||
"name": "begin_end_inline",
|
||||
"file": "_corpus_awk2.txt",
|
||||
"content": "x\n",
|
||||
"argv": [
|
||||
"awk",
|
||||
"BEGIN{print \"start\"}END{print \"end\"}",
|
||||
"_corpus_awk2.txt"
|
||||
],
|
||||
"lines": ["start", "end"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,18 +3,25 @@
|
||||
* Keep sorted alphabetically.
|
||||
*/
|
||||
export const COREUTILS_COMMANDS = [
|
||||
'arch',
|
||||
'awk',
|
||||
'base32',
|
||||
'base64',
|
||||
'basename',
|
||||
'basenc',
|
||||
'cat',
|
||||
'chgrp',
|
||||
'chmod',
|
||||
'chown',
|
||||
'cksum',
|
||||
'clear',
|
||||
'comm',
|
||||
'cp',
|
||||
'crontab',
|
||||
'cut',
|
||||
'date',
|
||||
'df',
|
||||
'dir',
|
||||
'dirname',
|
||||
'dircolors',
|
||||
'du',
|
||||
@@ -22,16 +29,25 @@ export const COREUTILS_COMMANDS = [
|
||||
'echo',
|
||||
'env',
|
||||
'exit',
|
||||
'expand',
|
||||
'expr',
|
||||
'factor',
|
||||
'false',
|
||||
'find',
|
||||
'fmt',
|
||||
'fold',
|
||||
'getconf',
|
||||
'git-pear',
|
||||
'grep',
|
||||
'groups',
|
||||
'head',
|
||||
'hdms',
|
||||
'help',
|
||||
'hostid',
|
||||
'hostname',
|
||||
'id',
|
||||
'install',
|
||||
'join',
|
||||
'jq',
|
||||
'ln',
|
||||
'login',
|
||||
@@ -39,26 +55,41 @@ export const COREUTILS_COMMANDS = [
|
||||
'logname',
|
||||
'ls',
|
||||
'man',
|
||||
'md5sum',
|
||||
'mkdir',
|
||||
'mkfifo',
|
||||
'mktemp',
|
||||
'mv',
|
||||
'nano',
|
||||
'nl',
|
||||
'nproc',
|
||||
'numfmt',
|
||||
'od',
|
||||
'paste',
|
||||
'pathchk',
|
||||
'pr',
|
||||
'printenv',
|
||||
'printf',
|
||||
'pwd',
|
||||
'readlink',
|
||||
'realpath',
|
||||
'rev',
|
||||
'rm',
|
||||
'rmdir',
|
||||
'savevault',
|
||||
'sed',
|
||||
'seq',
|
||||
'sha1sum',
|
||||
'sha256sum',
|
||||
'sha512sum',
|
||||
'shuf',
|
||||
'sleep',
|
||||
'sort',
|
||||
'split',
|
||||
'stat',
|
||||
'sum',
|
||||
'sync',
|
||||
'tac',
|
||||
'tail',
|
||||
'tee',
|
||||
'test',
|
||||
@@ -66,13 +97,23 @@ export const COREUTILS_COMMANDS = [
|
||||
'time',
|
||||
'touch',
|
||||
'tr',
|
||||
'truncate',
|
||||
'true',
|
||||
'tsort',
|
||||
'tty',
|
||||
'uname',
|
||||
'uniq',
|
||||
'unlink',
|
||||
'unexpand',
|
||||
'uptime',
|
||||
'users',
|
||||
'vdir',
|
||||
'wc',
|
||||
'which',
|
||||
'who',
|
||||
'whoami',
|
||||
'xargs'
|
||||
'xargs',
|
||||
'yes'
|
||||
]
|
||||
|
||||
/** Extra manual pages not built as /bin scripts on the system drive. */
|
||||
@@ -84,3 +125,15 @@ export const MAN_EXTRA_PAGES = [
|
||||
'git',
|
||||
'wget'
|
||||
]
|
||||
|
||||
/**
|
||||
* Booter-delegated or stub /bin names not in COREUTILS_COMMANDS; merged into `help` output (sorted).
|
||||
* Keep sorted; see build.mjs.
|
||||
*/
|
||||
export const HELP_DELEGATED_COMMANDS = [
|
||||
'curl',
|
||||
'git',
|
||||
'journalctl',
|
||||
'systemctl',
|
||||
'wget'
|
||||
]
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/** RFC 1321 MD5 — no Web Crypto; used by md5sum. */
|
||||
function bareMd5DigestBytes(u8) {
|
||||
const n = u8.length
|
||||
const bitLen = (BigInt(n) * 8n) & 0xffffffffffffffffn
|
||||
const padLen = (56 - ((n + 1) % 64) + 64) % 64
|
||||
const total = n + 1 + padLen + 8
|
||||
const buf = new Uint8Array(total)
|
||||
buf.set(u8)
|
||||
buf[n] = 0x80
|
||||
const view = new DataView(buf.buffer)
|
||||
view.setUint32(total - 8, Number(bitLen & 0xffffffffn), true)
|
||||
view.setUint32(total - 4, Number((bitLen >> 32n) & 0xffffffffn), true)
|
||||
|
||||
let a0 = 0x67452301
|
||||
let b0 = 0xefcdab89
|
||||
let c0 = 0x98badcfe
|
||||
let d0 = 0x10325476
|
||||
const s = [
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9,
|
||||
14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
|
||||
]
|
||||
const K = new Uint32Array(64)
|
||||
for (let i = 0; i < 64; i++) K[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000) >>> 0
|
||||
|
||||
const leftRotate = (x, c) => ((x << c) | (x >>> (32 - c))) >>> 0
|
||||
for (let off = 0; off < total; off += 64) {
|
||||
const M = new Uint32Array(16)
|
||||
for (let i = 0; i < 16; i++) {
|
||||
M[i] = view.getUint32(off + i * 4, true)
|
||||
}
|
||||
let A = a0
|
||||
let B = b0
|
||||
let C = c0
|
||||
let D = d0
|
||||
for (let i = 0; i < 64; i++) {
|
||||
let F, g
|
||||
if (i < 16) {
|
||||
F = (B & C) | (~B & D)
|
||||
g = i
|
||||
} else if (i < 32) {
|
||||
F = (D & B) | (~D & C)
|
||||
g = (5 * i + 1) % 16
|
||||
} else if (i < 48) {
|
||||
F = B ^ C ^ D
|
||||
g = (3 * i + 5) % 16
|
||||
} else {
|
||||
F = C ^ (B | ~D)
|
||||
g = (7 * i) % 16
|
||||
}
|
||||
F = (F + A + K[i] + M[g]) >>> 0
|
||||
A = D
|
||||
D = C
|
||||
C = B
|
||||
B = (B + leftRotate(F, s[i])) >>> 0
|
||||
}
|
||||
a0 = (a0 + A) >>> 0
|
||||
b0 = (b0 + B) >>> 0
|
||||
c0 = (c0 + C) >>> 0
|
||||
d0 = (d0 + D) >>> 0
|
||||
}
|
||||
const out = new Uint8Array(16)
|
||||
const dv = new DataView(out.buffer)
|
||||
dv.setUint32(0, a0, true)
|
||||
dv.setUint32(4, b0, true)
|
||||
dv.setUint32(8, c0, true)
|
||||
dv.setUint32(12, d0, true)
|
||||
return [...out].map((b) => b.toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "arch",
|
||||
"section": 1,
|
||||
"title": "print machine hardware name",
|
||||
"synopsis": [
|
||||
"arch"
|
||||
],
|
||||
"description": "Prints PROCESSOR_ARCHITECTURE or BARE_OS_ARCH (session stub).",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"arch",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "arch"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "base32",
|
||||
"section": 1,
|
||||
"title": "encode or decode base32",
|
||||
"synopsis": [
|
||||
"base32 [-d] [FILE]"
|
||||
],
|
||||
"description": "RFC 4648 Base32; decode emits raw bytes when stdout supports binary.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"base32",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "base32"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "base64",
|
||||
"section": 1,
|
||||
"title": "encode or decode base64",
|
||||
"synopsis": ["base64 [OPTION]... [FILE]", "base64 -d [OPTION]... [FILE]"],
|
||||
"description": "Encodes binary input to Base64, or decodes Base64 to raw bytes. Default reads stdin or FILE; decode output uses raw writes when available (process.stdout.write or ctx.bareOsBinWrite).",
|
||||
"options": [
|
||||
{ "flag": "-d, --decode", "meaning": "Decode incoming Base64" },
|
||||
{
|
||||
"flag": "-w COLS, --wrap",
|
||||
"meaning": "Wrap encoded lines at COLS (0 = no wrap)"
|
||||
},
|
||||
{ "flag": "-h, --help", "meaning": "Print usage" }
|
||||
],
|
||||
"keywords": ["base64", "bare-os", "coreutils"],
|
||||
"examples": [
|
||||
{ "caption": "encode a file", "code": "base64 < binary.bin > text.b64" },
|
||||
{ "caption": "decode", "code": "base64 -d text.b64 > out.bin" }
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "basenc",
|
||||
"section": 1,
|
||||
"title": "encode or decode with alphabet",
|
||||
"synopsis": [
|
||||
"basenc --base16 [-d] [FILE]"
|
||||
],
|
||||
"description": "Hex (base16) encode/decode only; other alphabets not implemented.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"basenc",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "basenc"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -2,17 +2,30 @@
|
||||
"name": "chgrp",
|
||||
"section": 1,
|
||||
"title": "change file group ownership",
|
||||
"synopsis": ["chgrp [OPTION]... [OPERAND]..."],
|
||||
"description": "Changing group ownership is not supported on Bare OS: Hyperdrive metadata is single-session oriented.",
|
||||
"options": [],
|
||||
"keywords": ["chgrp", "bare-os", "coreutils", "stub"],
|
||||
"stub": true,
|
||||
"diagnostics": ["chgrp: changing group is not supported on Bare OS"],
|
||||
"bareOsNotes": "Single-user identity; gid fields exist for display only.",
|
||||
"synopsis": ["chgrp [-h] GROUP FILE..."],
|
||||
"description": "Sets the group id (and optional group name) in Hyperdrive metadata on writable paths. GROUP may be a numeric gid or the name root, guest, nobody, or the current session GROUP.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage and exit"
|
||||
}
|
||||
],
|
||||
"keywords": ["chgrp", "bare-os", "coreutils", "metadata"],
|
||||
"diagnostics": [
|
||||
"chgrp: invalid group",
|
||||
"chgrp: not supported by this VFS",
|
||||
"chgrp: unsupported option"
|
||||
],
|
||||
"bareOsNotes": "Same writable scope as chmod; gid is stored for display and permission checks, not a real multi-user group database.",
|
||||
"examples": [
|
||||
{
|
||||
"caption": "not supported — use identity model",
|
||||
"code": "# chgrp is a stub; group is display metadata only"
|
||||
"caption": "set group by numeric gid",
|
||||
"code": "chgrp 1000 ~/data/file"
|
||||
},
|
||||
{
|
||||
"caption": "set group to current session group name",
|
||||
"code": "chgrp guest ~/tmp/file"
|
||||
}
|
||||
]
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
|
||||
@@ -2,17 +2,30 @@
|
||||
"name": "chown",
|
||||
"section": 1,
|
||||
"title": "change file owner and group",
|
||||
"synopsis": ["chown [OPTION]... [OPERAND]..."],
|
||||
"description": "Changing file owner is not supported on Bare OS (single-user Hyperdrive metadata).",
|
||||
"options": [],
|
||||
"keywords": ["chown", "bare-os", "coreutils", "stub"],
|
||||
"stub": true,
|
||||
"diagnostics": ["chown: changing owner is not supported on Bare OS"],
|
||||
"bareOsNotes": "Use identity login/logout instead of POSIX ownership changes.",
|
||||
"synopsis": ["chown [-h] OWNER[:GROUP] FILE...", "chown [-h] :GROUP FILE..."],
|
||||
"description": "Updates uid/gid and optional user/group names in Hyperdrive metadata on writable paths (personal drive, /tmp, etc.). Not a multi-user host kernel: OWNER and GROUP are limited to numeric ids and the names root, guest, nobody, or the current session USER/GROUP.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage and exit"
|
||||
}
|
||||
],
|
||||
"keywords": ["chown", "bare-os", "coreutils", "metadata"],
|
||||
"diagnostics": [
|
||||
"chown: invalid owner or group",
|
||||
"chown: not supported by this VFS",
|
||||
"chown: unsupported option"
|
||||
],
|
||||
"bareOsNotes": "Requires vfs.chown. Identity is still single-session; use login/logout for Ed25519-backed identity, not arbitrary POSIX users.",
|
||||
"examples": [
|
||||
{
|
||||
"caption": "not supported",
|
||||
"code": "# chown stub — see man identity / login"
|
||||
"caption": "set numeric owner and group on a file under $HOME",
|
||||
"code": "chown 1000:1000 ~/notes.txt"
|
||||
},
|
||||
{
|
||||
"caption": "change group only (leading colon), keep owner",
|
||||
"code": "chown :guest ~/shared.txt"
|
||||
}
|
||||
]
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "comm",
|
||||
"section": 1,
|
||||
"title": "compare two sorted files line by line",
|
||||
"synopsis": [
|
||||
"comm [-123] FILE1 FILE2"
|
||||
],
|
||||
"description": "Three columns: lines only in FILE1, only in FILE2, both. Suppress with -1, -2, -3.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"comm",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "comm"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "df",
|
||||
"section": 1,
|
||||
"title": "report file system disk space usage",
|
||||
"synopsis": [
|
||||
"df [-h] [FILE]"
|
||||
],
|
||||
"description": "Synthetic Hyperdrive free space; not real block devices.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --human-readable",
|
||||
"meaning": "Print sizes in powers of 1024 (K, M, …)"
|
||||
},
|
||||
{ "flag": "--help", "meaning": "Print usage" }
|
||||
],
|
||||
"keywords": [
|
||||
"df",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "df"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "dir",
|
||||
"section": 1,
|
||||
"title": "list directory contents",
|
||||
"synopsis": [
|
||||
"dir [OPTION]... [FILE]..."
|
||||
],
|
||||
"description": "Delegates to ls -C.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"dir",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "dir"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "expand",
|
||||
"section": 1,
|
||||
"title": "convert tabs to spaces",
|
||||
"synopsis": [
|
||||
"expand [-t N] [FILE]..."
|
||||
],
|
||||
"description": "Uniform tab width (default 8).",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"expand",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "expand"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "expr",
|
||||
"section": 1,
|
||||
"title": "evaluate expressions",
|
||||
"synopsis": [
|
||||
"expr EXPRESSION"
|
||||
],
|
||||
"description": "Integer + - * / %, comparisons, string = and !=.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"expr",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "expr"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "factor",
|
||||
"section": 1,
|
||||
"title": "factor numbers",
|
||||
"synopsis": [
|
||||
"factor [NUMBER]..."
|
||||
],
|
||||
"description": "Prime factors by trial division; safe integers only.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"factor",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "factor"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -3,11 +3,28 @@
|
||||
"section": 1,
|
||||
"title": "find files",
|
||||
"synopsis": ["find [PATH...] [EXPRESSION]"],
|
||||
"description": "Walks a directory tree with a small predicate set: -maxdepth, -mindepth, -name, -iname, -path, -type, -print0. Does not implement full POSIX find expression grammar.",
|
||||
"description": "Walks a directory tree with predicates including -maxdepth, -mindepth, -name, -iname, -path, -regex (full path, JS RegExp), -type, -empty, -mtime, -newer, -prune, -delete (gated by BARE_OS_FIND_DELETE), -exec/-ok … \\; (requires ctx.runBinCommand; BARE_OS_FIND_EXEC_MAX, default 64; -ok needs BARE_OS_FIND_OK=1), and -print0. Not full POSIX find grammar.",
|
||||
"options": [
|
||||
{ "flag": "-name / -iname", "meaning": "Base name glob match (case-sensitive / case-insensitive)" },
|
||||
{ "flag": "-print0", "meaning": "Separate paths with NUL (requires host stdout)" },
|
||||
{ "flag": "-type f|d|l", "meaning": "Restrict to file, directory, or symlink" }
|
||||
{
|
||||
"flag": "-name / -iname",
|
||||
"meaning": "Base name glob match (case-sensitive / case-insensitive)"
|
||||
},
|
||||
{
|
||||
"flag": "-regex PAT",
|
||||
"meaning": "Full path must match JavaScript RegExp PAT"
|
||||
},
|
||||
{
|
||||
"flag": "-exec / -ok",
|
||||
"meaning": "Run a utility with {} replaced by path; terminate with ;"
|
||||
},
|
||||
{
|
||||
"flag": "-print0",
|
||||
"meaning": "Separate paths with NUL (requires host stdout)"
|
||||
},
|
||||
{
|
||||
"flag": "-type f|d|l",
|
||||
"meaning": "Restrict to file, directory, or symlink"
|
||||
}
|
||||
],
|
||||
"keywords": ["find", "directory", "walk", "search"],
|
||||
"bareOsNotes": "Expression syntax is a simplified subset.",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fmt",
|
||||
"section": 1,
|
||||
"title": "simple text formatter",
|
||||
"synopsis": [
|
||||
"fmt [-w WIDTH] [FILE]..."
|
||||
],
|
||||
"description": "Reflow paragraphs (blank-line separated).",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"fmt",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "fmt"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fold",
|
||||
"section": 1,
|
||||
"title": "wrap each input line",
|
||||
"synopsis": [
|
||||
"fold [-w WIDTH] [FILE]..."
|
||||
],
|
||||
"description": "Fixed-width wrap without word break.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"fold",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "fold"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -18,7 +18,13 @@
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"bareOsNotes": "Subset only; unknown names fail with exit status 1. See src/getconf.js for the name table.",
|
||||
"environment": [
|
||||
"BARE_OS_YES_MAX_LINES — max lines yes prints (host passthrough overrides default)",
|
||||
"BARE_OS_SHUF_MAX_LINES — max lines shuf holds in memory",
|
||||
"BARE_OS_SPLIT_MAX_FILES — max output files split may create",
|
||||
"BARE_OS_FIND_EXEC_MAX — max find -exec/-ok invocations per run (also in table)"
|
||||
],
|
||||
"bareOsNotes": "Subset only; unknown names fail with exit status 1. See src/getconf.js for the name table. BARE_OS_NPROC overrides /bin/nproc when passed from the host (see DOCUMENTATION.md §14).",
|
||||
"examples": [
|
||||
{
|
||||
"caption": "path length limit",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "groups",
|
||||
"section": 1,
|
||||
"title": "print group names",
|
||||
"synopsis": [
|
||||
"groups [USER]"
|
||||
],
|
||||
"description": "Prints supplemental groups from env or primary GROUP.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"groups",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "groups"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "hostid",
|
||||
"section": 1,
|
||||
"title": "print numeric host identifier",
|
||||
"synopsis": [
|
||||
"hostid"
|
||||
],
|
||||
"description": "Eight hex digits from HOSTID env or session hash.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"hostid",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "hostid"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "install",
|
||||
"section": 1,
|
||||
"title": "copy files and set attributes",
|
||||
"synopsis": [
|
||||
"install [-m MODE] SOURCE DEST"
|
||||
],
|
||||
"description": "Copy one file; optional chmod.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"install",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "install"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "join",
|
||||
"section": 1,
|
||||
"title": "join lines of two files on a common field",
|
||||
"synopsis": [
|
||||
"join [-t CHAR] [-1 N] [-2 N] FILE1 FILE2"
|
||||
],
|
||||
"description": "Relational join on sorted files.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"join",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "join"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "md5sum",
|
||||
"section": 1,
|
||||
"title": "compute MD5 checksums",
|
||||
"synopsis": [
|
||||
"md5sum [FILE]..."
|
||||
],
|
||||
"description": "Bundled MD5 (not Web Crypto); GNU-style output lines.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"md5sum",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "md5sum"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "nproc",
|
||||
"section": 1,
|
||||
"title": "print number of processing units",
|
||||
"synopsis": [
|
||||
"nproc [--all]"
|
||||
],
|
||||
"description": "Counts processor lines in /proc/cpuinfo or BARE_OS_NPROC.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"nproc",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "nproc"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "numfmt",
|
||||
"section": 1,
|
||||
"title": "convert numbers",
|
||||
"synopsis": [
|
||||
"numfmt [--to=iec|--to=si] [NUMBER]..."
|
||||
],
|
||||
"description": "Human-readable IEC (1024) or SI (1000) scales.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"numfmt",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "numfmt"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "paste",
|
||||
"section": 1,
|
||||
"title": "merge lines of files",
|
||||
"synopsis": [
|
||||
"paste [-d LIST] [-s] [FILE]..."
|
||||
],
|
||||
"description": "Parallel or serial (-s) column merge.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"paste",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "paste"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "pr",
|
||||
"section": 1,
|
||||
"title": "paginate or columnate",
|
||||
"synopsis": [
|
||||
"pr [-w WIDTH] [-n] [FILE]..."
|
||||
],
|
||||
"description": "Minimal column print and optional line numbers.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"pr",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "pr"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "realpath",
|
||||
"section": 1,
|
||||
"title": "print resolved logical path",
|
||||
"synopsis": ["realpath [-m] FILE..."],
|
||||
"description": "Prints the VFS-resolved absolute path (same as the shell’s logical resolution). Does not traverse the host filesystem.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-m, --canonicalize-missing",
|
||||
"meaning": "Do not require the path to exist"
|
||||
},
|
||||
{ "flag": "-h, --help", "meaning": "Print usage" }
|
||||
],
|
||||
"keywords": ["realpath", "bare-os", "coreutils"],
|
||||
"examples": [
|
||||
{ "caption": "resolve under $HOME", "code": "realpath ./notes.txt" }
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "rev",
|
||||
"section": 1,
|
||||
"title": "reverse lines characterwise",
|
||||
"synopsis": [
|
||||
"rev [FILE]..."
|
||||
],
|
||||
"description": "Reverses each line's characters.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"rev",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "rev"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "sha1sum",
|
||||
"section": 1,
|
||||
"title": "compute SHA-1 checksums",
|
||||
"synopsis": [
|
||||
"sha1sum [FILE]..."
|
||||
],
|
||||
"description": "Uses Web Crypto SHA-1 when available.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"sha1sum",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "sha1sum"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "sha256sum",
|
||||
"section": 1,
|
||||
"title": "compute SHA-256 checksums",
|
||||
"synopsis": ["sha256sum [FILE]..."],
|
||||
"description": "Prints SHA-256 hex digests in GNU-style lines (hash, two spaces, name). Uses Web Crypto globalThis.crypto.subtle when available. Reads stdin when no operands or when FILE is -.",
|
||||
"options": [{ "flag": "-h, --help", "meaning": "Print usage" }],
|
||||
"keywords": ["sha256sum", "checksum", "bare-os", "coreutils"],
|
||||
"examples": [
|
||||
{ "caption": "checksum files", "code": "sha256sum *.js" },
|
||||
{ "caption": "stdin", "code": "cat f | sha256sum" }
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "sha512sum",
|
||||
"section": 1,
|
||||
"title": "compute SHA-512 checksums",
|
||||
"synopsis": [
|
||||
"sha512sum [FILE]..."
|
||||
],
|
||||
"description": "Uses Web Crypto SHA-512 when available.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"sha512sum",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "sha512sum"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "shuf",
|
||||
"section": 1,
|
||||
"title": "shuffle lines",
|
||||
"synopsis": [
|
||||
"shuf [FILE]..."
|
||||
],
|
||||
"description": "Shuffles in memory; capped by BARE_OS_SHUF_MAX_LINES.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"shuf",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "shuf"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "split",
|
||||
"section": 1,
|
||||
"title": "split a file into pieces",
|
||||
"synopsis": [
|
||||
"split [-l N] [-b N] [INPUT [PREFIX]]"
|
||||
],
|
||||
"description": "Line or byte chunks; output count capped.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"split",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "split"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "sum",
|
||||
"section": 1,
|
||||
"title": "checksum and count blocks",
|
||||
"synopsis": [
|
||||
"sum [-r] [FILE]..."
|
||||
],
|
||||
"description": "SysV default or BSD (-r) 16-bit checksum.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"sum",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "sum"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "sync",
|
||||
"section": 1,
|
||||
"title": "flush file system buffers",
|
||||
"synopsis": [
|
||||
"sync"
|
||||
],
|
||||
"description": "No-op success on Bare OS.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"sync",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "sync"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "tac",
|
||||
"section": 1,
|
||||
"title": "concatenate and print lines in reverse",
|
||||
"synopsis": [
|
||||
"tac [FILE]..."
|
||||
],
|
||||
"description": "Last line first; per-file order.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"tac",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "tac"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "truncate",
|
||||
"section": 1,
|
||||
"title": "shrink or extend file size",
|
||||
"synopsis": [
|
||||
"truncate -s SIZE FILE"
|
||||
],
|
||||
"description": "Absolute size only; pads with zeros.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"truncate",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "truncate"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "tsort",
|
||||
"section": 1,
|
||||
"title": "topological sort",
|
||||
"synopsis": [
|
||||
"tsort [FILE]"
|
||||
],
|
||||
"description": "Directed edges as pairs per line (A B).",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"tsort",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "tsort"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "unexpand",
|
||||
"section": 1,
|
||||
"title": "convert spaces to tabs",
|
||||
"synopsis": [
|
||||
"unexpand [-t N] [FILE]..."
|
||||
],
|
||||
"description": "Uniform tab width spacing to tabs.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"unexpand",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "unexpand"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "uniq",
|
||||
"section": 1,
|
||||
"title": "report or filter adjacent duplicate lines",
|
||||
"synopsis": ["uniq [-c] [-d] [-u] [INPUT]"],
|
||||
"description": "Filters adjacent duplicate lines (input should be sorted for POSIX semantics). Optional OUTPUT operand is accepted for familiarity but ignored; use shell redirects.",
|
||||
"options": [
|
||||
{ "flag": "-c, --count", "meaning": "Prefix lines with occurrence counts" },
|
||||
{
|
||||
"flag": "-d, --repeated",
|
||||
"meaning": "Only print duplicate lines (one per group)"
|
||||
},
|
||||
{ "flag": "-u, --unique", "meaning": "Only print lines that appear once" },
|
||||
{ "flag": "-h, --help", "meaning": "Print usage" }
|
||||
],
|
||||
"keywords": ["uniq", "bare-os", "coreutils"],
|
||||
"examples": [
|
||||
{ "caption": "unique sorted lines", "code": "sort names.txt | uniq" },
|
||||
{ "caption": "counts", "code": "sort log | uniq -c" }
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "unlink",
|
||||
"section": 1,
|
||||
"title": "remove a file",
|
||||
"synopsis": [
|
||||
"unlink FILE"
|
||||
],
|
||||
"description": "Single-file unlink.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"unlink",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "unlink"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "uptime",
|
||||
"section": 1,
|
||||
"title": "show uptime",
|
||||
"synopsis": [
|
||||
"uptime"
|
||||
],
|
||||
"description": "Uses /proc/uptime and /proc/loadavg when present.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"uptime",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "uptime"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "users",
|
||||
"section": 1,
|
||||
"title": "print login names",
|
||||
"synopsis": [
|
||||
"users"
|
||||
],
|
||||
"description": "Single-session user name.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"users",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "users"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "vdir",
|
||||
"section": 1,
|
||||
"title": "verbose directory listing",
|
||||
"synopsis": [
|
||||
"vdir [OPTION]... [FILE]..."
|
||||
],
|
||||
"description": "Delegates to ls -l.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"vdir",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "vdir"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "who",
|
||||
"section": 1,
|
||||
"title": "show who is logged on",
|
||||
"synopsis": [
|
||||
"who [OPTION]..."
|
||||
],
|
||||
"description": "Minimal session table from environment.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"who",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "who"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "yes",
|
||||
"section": 1,
|
||||
"title": "output a string repeatedly",
|
||||
"synopsis": [
|
||||
"yes [STRING]"
|
||||
],
|
||||
"description": "Prints until BARE_OS_YES_MAX_LINES cap.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-h, --help",
|
||||
"meaning": "Print usage"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"yes",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"caption": "basic",
|
||||
"code": "yes"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
}
|
||||
@@ -6,6 +6,6 @@
|
||||
"description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)",
|
||||
"scripts": {
|
||||
"build": "node ./build.mjs",
|
||||
"test": "node ./test/edit-key-parse.test.mjs"
|
||||
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
ctx.console.log('usage: arch\nPrint machine hardware name (same idea as uname -m).')
|
||||
return
|
||||
}
|
||||
if (argv[i].startsWith('-')) {
|
||||
ctx.console.error('arch: unknown option ' + argv[i])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
const e = ctx.vfs.env || {}
|
||||
ctx.console.log(
|
||||
e.PROCESSOR_ARCHITECTURE || e.MACHINE || e.BARE_OS_ARCH || 'unknown'
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
|
||||
function bareB32EncodeBytes(u8) {
|
||||
let out = ''
|
||||
let i = 0
|
||||
let buf = 0
|
||||
let bits = 0
|
||||
for (; i < u8.length; i++) {
|
||||
buf = (buf << 8) | u8[i]
|
||||
bits += 8
|
||||
while (bits >= 5) {
|
||||
bits -= 5
|
||||
out += B32[(buf >> bits) & 31]
|
||||
}
|
||||
}
|
||||
if (bits > 0) out += B32[(buf << (5 - bits)) & 31]
|
||||
while (out.length % 8 !== 0) out += '='
|
||||
return out
|
||||
}
|
||||
|
||||
function bareB32DecodeToU8(s) {
|
||||
const t = String(s).replace(/\s+/g, '').replace(/=+$/, '')
|
||||
let buf = 0
|
||||
let bits = 0
|
||||
const bytes = []
|
||||
for (let i = 0; i < t.length; i++) {
|
||||
const c = t[i]
|
||||
const v = B32.indexOf(c)
|
||||
if (v < 0) throw new Error('invalid base32 character')
|
||||
buf = (buf << 5) | v
|
||||
bits += 5
|
||||
if (bits >= 8) {
|
||||
bits -= 8
|
||||
bytes.push((buf >> bits) & 255)
|
||||
}
|
||||
}
|
||||
return new Uint8Array(bytes)
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let decode = false
|
||||
let wrap = 0
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: base32 [-d] [-w COLS] [FILE]\n' +
|
||||
'RFC 4648 Base32; -d decodes to raw bytes.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a === '-d' || a === '--decode') {
|
||||
decode = true
|
||||
continue
|
||||
}
|
||||
if ((a === '-w' || a === '--wrap') && argv[i + 1]) {
|
||||
wrap = Number.parseInt(argv[++i], 10)
|
||||
if (!Number.isFinite(wrap) || wrap < 0) wrap = 0
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('base32: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
let buf
|
||||
if (!paths.length || paths[0] === '-') {
|
||||
buf = b4.from(bareStdin(ctx))
|
||||
} else {
|
||||
const b = await ctx.vfs.readFile(paths[0])
|
||||
if (!b) {
|
||||
ctx.console.error('base32: cannot read ' + paths[0])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
buf = b instanceof Uint8Array ? b : new Uint8Array(b)
|
||||
}
|
||||
if (decode) {
|
||||
const text = ctx.b4a.toString(buf)
|
||||
let raw
|
||||
try {
|
||||
raw = bareB32DecodeToU8(text)
|
||||
} catch (e) {
|
||||
ctx.console.error('base32: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (!bareOsEmitRaw(ctx, raw)) {
|
||||
ctx.console.error(
|
||||
'base32: decode output requires process.stdout.write or ctx.bareOsBinWrite'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
let enc = bareB32EncodeBytes(buf)
|
||||
if (wrap > 0) {
|
||||
const lines = []
|
||||
for (let i = 0; i < enc.length; i += wrap) {
|
||||
lines.push(enc.slice(i, i + wrap))
|
||||
}
|
||||
ctx.console.log(lines.join('\n'))
|
||||
} else {
|
||||
ctx.console.log(enc)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* base64 — encode or decode Base64 (RFC 4648).
|
||||
* Uses btoa/atob or Buffer when available; otherwise a small inline encoder.
|
||||
*/
|
||||
function bareB64EncodeBytes(u8) {
|
||||
const B = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
let out = ''
|
||||
let i = 0
|
||||
for (; i + 2 < u8.length; i += 3) {
|
||||
const n = (u8[i] << 16) | (u8[i + 1] << 8) | u8[i + 2]
|
||||
out += B[(n >> 18) & 63] + B[(n >> 12) & 63] + B[(n >> 6) & 63] + B[n & 63]
|
||||
}
|
||||
const rest = u8.length - i
|
||||
if (rest === 1) {
|
||||
const n = u8[i] << 16
|
||||
out += B[(n >> 18) & 63] + B[(n >> 12) & 63] + '=='
|
||||
} else if (rest === 2) {
|
||||
const n = (u8[i] << 16) | (u8[i + 1] << 8)
|
||||
out += B[(n >> 18) & 63] + B[(n >> 12) & 63] + B[(n >> 6) & 63] + '='
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function bareB64DecodeToU8(s) {
|
||||
const t = String(s).replace(/\s+/g, '')
|
||||
if (typeof globalThis.Buffer !== 'undefined') {
|
||||
return new Uint8Array(globalThis.Buffer.from(t, 'base64'))
|
||||
}
|
||||
if (typeof globalThis.atob === 'function') {
|
||||
const bin = globalThis.atob(t)
|
||||
const out = new Uint8Array(bin.length)
|
||||
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i) & 255
|
||||
return out
|
||||
}
|
||||
throw new Error('base64 decode requires Buffer or atob')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let decode = false
|
||||
let wrap = 76
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: base64 [-d] [-w COLS] [FILE]\n' +
|
||||
' base64 --decode [-w COLS] [FILE]\n' +
|
||||
'Default: encode stdin or FILE; -d/--decode decodes to raw bytes on stdout.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a === '-d' || a === '--decode') {
|
||||
decode = true
|
||||
continue
|
||||
}
|
||||
if ((a === '-w' || a === '--wrap') && argv[i + 1]) {
|
||||
wrap = Number.parseInt(argv[++i], 10)
|
||||
if (!Number.isFinite(wrap) || wrap < 0) wrap = 0
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('base64: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
let buf
|
||||
if (!paths.length || paths[0] === '-') {
|
||||
buf = b4.from(bareStdin(ctx))
|
||||
} else {
|
||||
const b = await ctx.vfs.readFile(paths[0])
|
||||
if (!b) {
|
||||
ctx.console.error('base64: cannot read ' + paths[0])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
buf = b instanceof Uint8Array ? b : new Uint8Array(b)
|
||||
}
|
||||
if (decode) {
|
||||
const text = new TextDecoder().decode(buf)
|
||||
let raw
|
||||
try {
|
||||
raw = bareB64DecodeToU8(text)
|
||||
} catch (e) {
|
||||
ctx.console.error('base64: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (!bareOsEmitRaw(ctx, raw)) {
|
||||
ctx.console.error(
|
||||
'base64: decode output requires process.stdout.write or ctx.bareOsBinWrite'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
let enc
|
||||
if (typeof globalThis.btoa === 'function') {
|
||||
let s = ''
|
||||
const step = 0x8000
|
||||
for (let i = 0; i < buf.length; i += step) {
|
||||
const chunk = buf.subarray(i, i + step)
|
||||
s += String.fromCharCode.apply(null, chunk)
|
||||
}
|
||||
enc = globalThis.btoa(s)
|
||||
} else {
|
||||
enc = bareB64EncodeBytes(buf)
|
||||
}
|
||||
if (wrap > 0) {
|
||||
const lines = []
|
||||
for (let i = 0; i < enc.length; i += wrap) {
|
||||
lines.push(enc.slice(i, i + wrap))
|
||||
}
|
||||
ctx.console.log(lines.join('\n'))
|
||||
} else {
|
||||
ctx.console.log(enc)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
function bareHexEncode(u8) {
|
||||
let s = ''
|
||||
for (let i = 0; i < u8.length; i++) {
|
||||
s += u8[i].toString(16).padStart(2, '0')
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
function bareHexDecode(s) {
|
||||
const t = String(s).replace(/\s+/g, '')
|
||||
if (t.length % 2 !== 0) throw new Error('odd hex length')
|
||||
const out = new Uint8Array(t.length / 2)
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
out[i] = parseInt(t.slice(i * 2, i * 2 + 2), 16)
|
||||
if (!Number.isFinite(out[i])) throw new Error('invalid hex')
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let decode = false
|
||||
let base16 = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: basenc --base16 [-d] [FILE]\n' +
|
||||
'Encode or decode hex (Base16). Other bases are not implemented.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a === '-d' || a === '--decode') {
|
||||
decode = true
|
||||
continue
|
||||
}
|
||||
if (a === '--base16') {
|
||||
base16 = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('basenc: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
if (!base16) {
|
||||
ctx.console.error('basenc: requires --base16')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
let buf
|
||||
if (!paths.length || paths[0] === '-') {
|
||||
buf = b4.from(bareStdin(ctx))
|
||||
} else {
|
||||
const b = await ctx.vfs.readFile(paths[0])
|
||||
if (!b) {
|
||||
ctx.console.error('basenc: cannot read ' + paths[0])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
buf = b instanceof Uint8Array ? b : new Uint8Array(b)
|
||||
}
|
||||
if (decode) {
|
||||
const text = b4.toString(buf)
|
||||
let raw
|
||||
try {
|
||||
raw = bareHexDecode(text)
|
||||
} catch (e) {
|
||||
ctx.console.error('basenc: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (!bareOsEmitRaw(ctx, raw)) {
|
||||
ctx.console.error(
|
||||
'basenc: decode output requires process.stdout.write or ctx.bareOsBinWrite'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.console.log(bareHexEncode(buf))
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
async function readLines(ctx, path) {
|
||||
const b4 = ctx.b4a
|
||||
if (path === '-') return bareStdin(ctx).split('\n')
|
||||
const b = await ctx.vfs.readFile(path)
|
||||
if (!b) return null
|
||||
return b4.toString(b).split('\n')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let c1 = true
|
||||
let c2 = true
|
||||
let c3 = true
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: comm [-123] FILE1 FILE2\nCompare sorted files; columns: only FILE1, only FILE2, both.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-1') c1 = false
|
||||
else if (a === '-2') c2 = false
|
||||
else if (a === '-3') c3 = false
|
||||
else if (a.startsWith('-')) {
|
||||
ctx.console.error('comm: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
} else paths.push(a)
|
||||
}
|
||||
if (paths.length !== 2) {
|
||||
ctx.console.error('usage: comm [-123] FILE1 FILE2')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const A = await readLines(ctx, paths[0])
|
||||
const B = await readLines(ctx, paths[1])
|
||||
if (!A || !B) {
|
||||
ctx.console.error('comm: missing input file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trimNl = (arr) => {
|
||||
if (arr.length && arr[arr.length - 1] === '') arr.pop()
|
||||
return arr
|
||||
}
|
||||
const a = trimNl(A.slice())
|
||||
const b = trimNl(B.slice())
|
||||
let i = 0
|
||||
let j = 0
|
||||
while (i < a.length || j < b.length) {
|
||||
if (i >= a.length) {
|
||||
if (c2) ctx.console.log('\t' + b[j])
|
||||
j++
|
||||
continue
|
||||
}
|
||||
if (j >= b.length) {
|
||||
if (c1) ctx.console.log(a[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
const cmp = a[i].localeCompare(b[j])
|
||||
if (cmp < 0) {
|
||||
if (c1) ctx.console.log(a[i])
|
||||
i++
|
||||
} else if (cmp > 0) {
|
||||
if (c2) ctx.console.log('\t' + b[j])
|
||||
j++
|
||||
} else {
|
||||
if (c3) ctx.console.log('\t\t' + a[i])
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
async function copyPath(ctx, from, to, recursive, followSymlink, opts) {
|
||||
const preserve = opts && opts.preserveTime
|
||||
const update = opts && opts.update
|
||||
const verbose = opts && opts.verbose
|
||||
const st = await ctx.vfs.lstat(from)
|
||||
if (!st) {
|
||||
ctx.console.error('cp: ' + from + ': No such file')
|
||||
@@ -8,6 +11,7 @@ async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
if (!followSymlink) {
|
||||
const t = await ctx.vfs.readlink(from)
|
||||
await ctx.vfs.symlink(t, to)
|
||||
if (verbose) ctx.console.log("'" + from + "' -> '" + to + "'")
|
||||
return true
|
||||
}
|
||||
const fst = await ctx.vfs.stat(from)
|
||||
@@ -17,7 +21,19 @@ async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
ctx.console.error('cp: cannot read ' + from)
|
||||
return false
|
||||
}
|
||||
await ctx.vfs.writeFile(to, buf)
|
||||
if (update) {
|
||||
const dst = await ctx.vfs.lstat(to)
|
||||
if (dst && dst.mtimeMs >= fst.mtimeMs) {
|
||||
if (verbose) ctx.console.log('skipped: ' + to)
|
||||
return true
|
||||
}
|
||||
}
|
||||
const wopts =
|
||||
preserve && typeof fst.mtimeMs === 'number'
|
||||
? { mtimeMs: fst.mtimeMs, ctimeMs: fst.ctimeMs }
|
||||
: {}
|
||||
await ctx.vfs.writeFile(to, buf, wopts)
|
||||
if (verbose) ctx.console.log("'" + from + "' -> '" + to + "'")
|
||||
return true
|
||||
}
|
||||
if (fst.type === 'directory') {
|
||||
@@ -31,8 +47,10 @@ async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
if (n === '.bareos_empty') continue
|
||||
const f = from.replace(/\/+$/, '') + '/' + n
|
||||
const t = to.replace(/\/+$/, '') + '/' + n
|
||||
if (!(await copyPath(ctx, f, t, true, followSymlink))) return false
|
||||
if (!(await copyPath(ctx, f, t, true, followSymlink, opts)))
|
||||
return false
|
||||
}
|
||||
if (verbose) ctx.console.log("'" + from + "' -> '" + to + "'")
|
||||
return true
|
||||
}
|
||||
ctx.console.error('cp: cannot copy special file ' + from)
|
||||
@@ -44,7 +62,19 @@ async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
ctx.console.error('cp: cannot read ' + from)
|
||||
return false
|
||||
}
|
||||
await ctx.vfs.writeFile(to, buf)
|
||||
if (update) {
|
||||
const dst = await ctx.vfs.lstat(to)
|
||||
if (dst && dst.mtimeMs >= st.mtimeMs) {
|
||||
if (verbose) ctx.console.log('skipped: ' + to)
|
||||
return true
|
||||
}
|
||||
}
|
||||
const wopts =
|
||||
preserve && typeof st.mtimeMs === 'number'
|
||||
? { mtimeMs: st.mtimeMs, ctimeMs: st.ctimeMs }
|
||||
: {}
|
||||
await ctx.vfs.writeFile(to, buf, wopts)
|
||||
if (verbose) ctx.console.log("'" + from + "' -> '" + to + "'")
|
||||
return true
|
||||
}
|
||||
if (st.type === 'directory') {
|
||||
@@ -58,8 +88,9 @@ async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
if (n === '.bareos_empty') continue
|
||||
const f = from.replace(/\/+$/, '') + '/' + n
|
||||
const t = to.replace(/\/+$/, '') + '/' + n
|
||||
if (!(await copyPath(ctx, f, t, true, followSymlink))) return false
|
||||
if (!(await copyPath(ctx, f, t, true, followSymlink, opts))) return false
|
||||
}
|
||||
if (verbose) ctx.console.log("'" + from + "' -> '" + to + "'")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -68,6 +99,9 @@ async function copyPath(ctx, from, to, recursive, followSymlink) {
|
||||
async function run(ctx, argv) {
|
||||
let recursive = false
|
||||
let followSymlink = false
|
||||
let update = false
|
||||
let verbose = false
|
||||
let preserveTime = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
@@ -83,6 +117,23 @@ async function run(ctx, argv) {
|
||||
followSymlink = false
|
||||
continue
|
||||
}
|
||||
if (a === '-u' || a === '--update') {
|
||||
update = true
|
||||
continue
|
||||
}
|
||||
if (a === '-v' || a === '--verbose') {
|
||||
verbose = true
|
||||
continue
|
||||
}
|
||||
if (a === '--preserve' || a === '-p') {
|
||||
preserveTime = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('--preserve=')) {
|
||||
const v = a.slice('--preserve='.length)
|
||||
if (v === 'timestamps' || v === 'time' || v === 'all') preserveTime = true
|
||||
continue
|
||||
}
|
||||
if (a === '--') {
|
||||
paths.push(...argv.slice(i + 1))
|
||||
break
|
||||
@@ -95,7 +146,9 @@ async function run(ctx, argv) {
|
||||
paths.push(a)
|
||||
}
|
||||
if (paths.length < 2) {
|
||||
ctx.console.error('usage: cp [-R] [-L|-P] SOURCE... DEST')
|
||||
ctx.console.error(
|
||||
'usage: cp [-R] [-L|-P] [-u] [-v] [-p|--preserve[=timestamps]] SOURCE... DEST'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
@@ -113,13 +166,14 @@ async function run(ctx, argv) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const opts = { preserveTime, update, verbose }
|
||||
for (const src of sources) {
|
||||
const base = src.replace(/\/+$/, '').split('/').pop() || src
|
||||
const target =
|
||||
destIsDir || sources.length > 1
|
||||
? dest.replace(/\/+$/, '') + '/' + base
|
||||
: dest
|
||||
if (!(await copyPath(ctx, src, target, recursive, followSymlink)))
|
||||
if (!(await copyPath(ctx, src, target, recursive, followSymlink, opts)))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
async function run(ctx, argv) {
|
||||
let human = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: df [-h] [FILE]...\nSynthetic disk free for Bare OS (Hyperdrive model; not block devices).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-h' || a === '--human-readable') {
|
||||
human = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('df: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
let totalK = 1024 * 1024
|
||||
let usedK = 4096
|
||||
try {
|
||||
const buf = await ctx.vfs.readFile('/proc/bare_os_quotas')
|
||||
if (buf) {
|
||||
const j = JSON.parse(ctx.b4a.toString(buf))
|
||||
if (j && typeof j.pipelineMaxBytes === 'number')
|
||||
totalK = Math.max(1024, Math.ceil(j.pipelineMaxBytes / 1024) * 1024)
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const freeK = Math.max(0, totalK - usedK)
|
||||
const pct =
|
||||
totalK > 0 ? Math.min(100, Math.round((usedK / totalK) * 100)) : 0
|
||||
function fmt(n) {
|
||||
if (!human) return String(n)
|
||||
if (n < 1024) return n + 'K'
|
||||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + 'M'
|
||||
return (n / 1024 / 1024).toFixed(1) + 'G'
|
||||
}
|
||||
const mount = paths.length ? paths[0] : '/'
|
||||
ctx.console.log(
|
||||
'Filesystem 1K-blocks Used Available Use% Mounted on'
|
||||
)
|
||||
ctx.console.log(
|
||||
'bare-hyperdrive ' +
|
||||
String(totalK).padStart(12) +
|
||||
' ' +
|
||||
String(usedK).padStart(8) +
|
||||
' ' +
|
||||
String(freeK).padStart(10) +
|
||||
' ' +
|
||||
String(pct).padStart(3) +
|
||||
'% ' +
|
||||
mount
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
async function run(ctx, argv) {
|
||||
if (typeof ctx.runBinCommand !== 'function') {
|
||||
ctx.console.error('dir: runBinCommand not available')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
await ctx.runBinCommand(['ls', '-C'].concat(argv.slice(1)))
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
function parseUniformWidth(tabArg) {
|
||||
const n = parseInt(String(tabArg).split(',')[0].trim(), 10)
|
||||
return Number.isFinite(n) && n > 0 ? n : 8
|
||||
}
|
||||
|
||||
function nextTabCol(col, w) {
|
||||
return (Math.floor(col / w) + 1) * w
|
||||
}
|
||||
|
||||
function expandLine(line, w) {
|
||||
let col = 0
|
||||
let out = ''
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const ch = line[i]
|
||||
if (ch === '\t') {
|
||||
const target = nextTabCol(col, w)
|
||||
while (col < target) {
|
||||
out += ' '
|
||||
col++
|
||||
}
|
||||
} else {
|
||||
out += ch
|
||||
if (ch === '\n' || ch === '\r') col = 0
|
||||
else col++
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let tabArg = '8'
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: expand [-t N] [FILE]...\nConvert tabs to spaces (uniform tab width N, default 8).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-t' && argv[i + 1]) {
|
||||
tabArg = argv[++i]
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-t') && a.length > 2) {
|
||||
tabArg = a.slice(2)
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('--tabs=')) {
|
||||
tabArg = a.slice(7)
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('expand: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const w = parseUniformWidth(tabArg)
|
||||
const b4 = ctx.b4a
|
||||
function proc(text) {
|
||||
const lines = text.split('\n')
|
||||
for (let li = 0; li < lines.length; li++) {
|
||||
const isLast = li === lines.length - 1
|
||||
const line = lines[li]
|
||||
if (isLast && line === '' && lines.length > 1) continue
|
||||
ctx.console.log(expandLine(line, w))
|
||||
}
|
||||
}
|
||||
if (!paths.length) {
|
||||
proc(bareStdin(ctx))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
proc(bareStdin(ctx))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('expand: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
proc(b4.toString(b))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
async function run(ctx, argv) {
|
||||
const tokens = argv.slice(1)
|
||||
if (!tokens.length || tokens[0] === '--help' || tokens[0] === '-h') {
|
||||
ctx.console.log(
|
||||
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, and string = / !=.'
|
||||
)
|
||||
return
|
||||
}
|
||||
let i = 0
|
||||
const peek = () => tokens[i]
|
||||
const take = () => tokens[i++]
|
||||
function parsePrimary() {
|
||||
const t = take()
|
||||
if (t === '(') {
|
||||
const v = parseAdd()
|
||||
if (take() !== ')') throw new Error('syntax error')
|
||||
return v
|
||||
}
|
||||
if (/^-?\d+$/.test(t)) return { kind: 'n', v: parseInt(t, 10) }
|
||||
return { kind: 's', v: t }
|
||||
}
|
||||
function parseMul() {
|
||||
let left = parsePrimary()
|
||||
while (peek() === '*' || peek() === '/' || peek() === '%') {
|
||||
const op = take()
|
||||
const right = parsePrimary()
|
||||
if (left.kind !== 'n' || right.kind !== 'n') throw new Error('non-numeric')
|
||||
if (op === '*') left = { kind: 'n', v: left.v * right.v }
|
||||
else if (op === '/') {
|
||||
if (right.v === 0) throw new Error('division by zero')
|
||||
left = { kind: 'n', v: Math.trunc(left.v / right.v) }
|
||||
} else {
|
||||
if (right.v === 0) throw new Error('division by zero')
|
||||
left = { kind: 'n', v: left.v % right.v }
|
||||
}
|
||||
}
|
||||
return left
|
||||
}
|
||||
function parseAdd() {
|
||||
let left = parseMul()
|
||||
while (peek() === '+' || peek() === '-') {
|
||||
const op = take()
|
||||
const right = parseMul()
|
||||
if (left.kind !== 'n' || right.kind !== 'n') throw new Error('non-numeric')
|
||||
left = {
|
||||
kind: 'n',
|
||||
v: op === '+' ? left.v + right.v : left.v - right.v
|
||||
}
|
||||
}
|
||||
return left
|
||||
}
|
||||
function parseCmp() {
|
||||
let left = parseAdd()
|
||||
const op = peek()
|
||||
if (
|
||||
op === '=' ||
|
||||
op === '==' ||
|
||||
op === '!=' ||
|
||||
op === '<' ||
|
||||
op === '<=' ||
|
||||
op === '>' ||
|
||||
op === '>='
|
||||
) {
|
||||
take()
|
||||
const right = parseAdd()
|
||||
if (op === '=' || op === '==') {
|
||||
const eq =
|
||||
left.kind === right.kind &&
|
||||
(left.kind === 'n' ? left.v === right.v : left.v === right.v)
|
||||
return { kind: 'n', v: eq ? 1 : 0 }
|
||||
}
|
||||
if (op === '!=') {
|
||||
const eq =
|
||||
left.kind === right.kind &&
|
||||
(left.kind === 'n' ? left.v === right.v : left.v === right.v)
|
||||
return { kind: 'n', v: eq ? 0 : 1 }
|
||||
}
|
||||
if (left.kind !== 'n' || right.kind !== 'n') throw new Error('non-numeric')
|
||||
let ok = false
|
||||
if (op === '<') ok = left.v < right.v
|
||||
else if (op === '<=') ok = left.v <= right.v
|
||||
else if (op === '>') ok = left.v > right.v
|
||||
else if (op === '>=') ok = left.v >= right.v
|
||||
return { kind: 'n', v: ok ? 1 : 0 }
|
||||
}
|
||||
return left
|
||||
}
|
||||
try {
|
||||
const r = parseCmp()
|
||||
if (i < tokens.length) throw new Error('syntax error')
|
||||
ctx.console.log(String(r.kind === 'n' ? r.v : r.v))
|
||||
} catch (e) {
|
||||
ctx.console.error('expr: ' + (e.message || e))
|
||||
ctx.exitCode = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
function factorOne(n) {
|
||||
const out = []
|
||||
let x = n
|
||||
let d = 2
|
||||
while (d * d <= x) {
|
||||
while (x % d === 0) {
|
||||
out.push(d)
|
||||
x /= d
|
||||
}
|
||||
d++
|
||||
}
|
||||
if (x > 1) out.push(x)
|
||||
return out
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const nums = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log('usage: factor [NUMBER]...\nPrint prime factors (trial division).')
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('factor: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
nums.push(a)
|
||||
}
|
||||
const parse = (s) => {
|
||||
const n = parseInt(String(s), 10)
|
||||
if (!Number.isFinite(n) || n < 0 || n > Number.MAX_SAFE_INTEGER) return null
|
||||
return n
|
||||
}
|
||||
if (!nums.length) {
|
||||
const lines = bareStdin(ctx).split(/\n')
|
||||
for (const line of lines) {
|
||||
const t = line.trim()
|
||||
if (!t) continue
|
||||
nums.push(t)
|
||||
}
|
||||
}
|
||||
for (const s of nums) {
|
||||
const n = parse(s)
|
||||
if (n == null || n < 2) {
|
||||
ctx.console.log(s + ':')
|
||||
continue
|
||||
}
|
||||
const f = factorOne(n)
|
||||
ctx.console.log(s + ': ' + f.join(' '))
|
||||
}
|
||||
}
|
||||
@@ -77,10 +77,12 @@ async function walk(ctx, dir, o, curDepth) {
|
||||
const nameOk = !o.nameRe || o.nameRe.test(n)
|
||||
let match = pathOk && nameOk && (!o.wantType || st.type === o.wantType)
|
||||
if (match && o.mtimeSpec) match = match && findMatchMtime(st, o.mtimeSpec)
|
||||
if (match && o.newerThanMs != null) match = match && st.mtimeMs > o.newerThanMs
|
||||
if (match && o.newerThanMs != null)
|
||||
match = match && st.mtimeMs > o.newerThanMs
|
||||
if (match && o.wantEmpty) {
|
||||
match = match && (await findIsEmpty(ctx, path, st))
|
||||
}
|
||||
if (match && o.regexPath && !o.regexPath.test(path)) match = false
|
||||
if (match && gnuDepth >= o.minDepth) {
|
||||
if (o.doDelete) {
|
||||
if (ctx.vfs.env.BARE_OS_FIND_DELETE !== '1') {
|
||||
@@ -100,6 +102,40 @@ async function walk(ctx, dir, o, curDepth) {
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
} else if (o.execTemplate && o.execTemplate.length) {
|
||||
if (typeof ctx.runBinCommand !== 'function') {
|
||||
const stw = /** @type {{ noRun?: boolean }} */ (o.deleteState)
|
||||
if (!stw.noRun) {
|
||||
ctx.console.error('find: -exec requires ctx.runBinCommand')
|
||||
stw.noRun = true
|
||||
}
|
||||
ctx.exitCode = 1
|
||||
} else if (o.execCount >= o.execMax) {
|
||||
const stw = /** @type {{ execCap?: boolean }} */ (o.deleteState)
|
||||
if (!stw.execCap) {
|
||||
ctx.console.error(
|
||||
'find: -exec/-ok: invocation limit (' +
|
||||
o.execMax +
|
||||
') exceeded (raise BARE_OS_FIND_EXEC_MAX)'
|
||||
)
|
||||
stw.execCap = true
|
||||
}
|
||||
ctx.exitCode = 1
|
||||
} else {
|
||||
const ok =
|
||||
!o.execUseOk ||
|
||||
ctx.vfs.env.BARE_OS_FIND_OK === '1' ||
|
||||
ctx.vfs.env.BARE_OS_FIND_OK === 'true'
|
||||
if (o.execUseOk && !ok) {
|
||||
/* skip without running */
|
||||
} else {
|
||||
const subst = o.execTemplate.map((arg) =>
|
||||
arg === '{}' ? path : arg.split('{}').join(path)
|
||||
)
|
||||
o.execCount++
|
||||
await ctx.runBinCommand(subst)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
findEmitLine(ctx, path, o.print0)
|
||||
}
|
||||
@@ -130,6 +166,11 @@ async function run(ctx, argv) {
|
||||
let prunePath = null
|
||||
let wantEmpty = false
|
||||
let doDelete = false
|
||||
/** @type {string[] | null} */
|
||||
let execTemplate = null
|
||||
let execUseOk = false
|
||||
/** @type {string | null} */
|
||||
let regexPathStr = null
|
||||
const rest = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
@@ -192,6 +233,39 @@ async function run(ctx, argv) {
|
||||
doDelete = true
|
||||
continue
|
||||
}
|
||||
if ((a === '-exec' || a === '-ok') && argv[i + 1]) {
|
||||
execUseOk = a === '-ok'
|
||||
i++
|
||||
const parts = []
|
||||
while (i < argv.length && argv[i] !== ';') {
|
||||
parts.push(argv[i++])
|
||||
}
|
||||
if (i >= argv.length || argv[i] !== ';') {
|
||||
ctx.console.error('find: ' + a + ' must be terminated with ;')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
i++
|
||||
if (!parts.length) {
|
||||
ctx.console.error('find: empty ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
execTemplate = parts
|
||||
continue
|
||||
}
|
||||
if (a === '-regex' && argv[i + 1]) {
|
||||
const pat = argv[++i]
|
||||
try {
|
||||
new RegExp(pat)
|
||||
regexPathStr = pat
|
||||
} catch {
|
||||
ctx.console.error('find: invalid -regex')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (a === '--') {
|
||||
rest.push(...argv.slice(i + 1))
|
||||
break
|
||||
@@ -244,6 +318,23 @@ async function run(ctx, argv) {
|
||||
if (prunePath) {
|
||||
pruneAbs = ctx.vfs.resolveLogical(prunePath).replace(/\/+$/, '') || '/'
|
||||
}
|
||||
/** @type {RegExp | null} */
|
||||
let regexPath = null
|
||||
if (regexPathStr) {
|
||||
try {
|
||||
regexPath = new RegExp(regexPathStr)
|
||||
} catch {
|
||||
ctx.console.error('find: invalid -regex')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
const execMaxRaw = ctx.vfs.env.BARE_OS_FIND_EXEC_MAX
|
||||
let execMax = 64
|
||||
if (execMaxRaw != null && String(execMaxRaw).trim() !== '') {
|
||||
const n = Number.parseInt(String(execMaxRaw), 10)
|
||||
if (Number.isFinite(n)) execMax = Math.min(4096, Math.max(1, n))
|
||||
}
|
||||
const abs = ctx.vfs.resolveLogical(root)
|
||||
const o = {
|
||||
maxDepth,
|
||||
@@ -257,6 +348,11 @@ async function run(ctx, argv) {
|
||||
pruneAbs,
|
||||
wantEmpty,
|
||||
doDelete,
|
||||
regexPath,
|
||||
execTemplate,
|
||||
execUseOk,
|
||||
execCount: 0,
|
||||
execMax,
|
||||
deleteState: {}
|
||||
}
|
||||
await walk(ctx, abs, o, 0)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
async function run(ctx, argv) {
|
||||
let width = 75
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: fmt [-w WIDTH] [FILE]...\nSimple paragraph reflow: join non-empty lines, wrap at spaces.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if ((a === '-w' || a === '--width') && argv[i + 1]) {
|
||||
width = parseInt(argv[++i], 10)
|
||||
if (!Number.isFinite(width) || width < 1) width = 75
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('fmt: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function flushPara(words) {
|
||||
if (!words.length) return
|
||||
let line = ''
|
||||
for (const w of words) {
|
||||
if (!line) line = w
|
||||
else if (line.length + 1 + w.length <= width) line += ' ' + w
|
||||
else {
|
||||
ctx.console.log(line)
|
||||
line = w
|
||||
}
|
||||
}
|
||||
if (line) ctx.console.log(line)
|
||||
}
|
||||
function proc(text) {
|
||||
const paras = text.split(/\n\n+/)
|
||||
for (const para of paras) {
|
||||
const words = para.replace(/\s+/g, ' ').trim().split(' ').filter(Boolean)
|
||||
flushPara(words)
|
||||
}
|
||||
}
|
||||
if (!paths.length) {
|
||||
proc(bareStdin(ctx))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
proc(bareStdin(ctx))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('fmt: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
proc(b4.toString(b))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
async function run(ctx, argv) {
|
||||
let width = 80
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log('usage: fold [-w WIDTH] [FILE]...\nWrap each input line to WIDTH columns.')
|
||||
return
|
||||
}
|
||||
if ((a === '-w' || a === '--width') && argv[i + 1]) {
|
||||
width = parseInt(argv[++i], 10)
|
||||
if (!Number.isFinite(width) || width < 1) width = 80
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('fold: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function wrapLine(line) {
|
||||
if (line.length <= width) return [line]
|
||||
const rows = []
|
||||
let rest = line
|
||||
while (rest.length > width) {
|
||||
rows.push(rest.slice(0, width))
|
||||
rest = rest.slice(width)
|
||||
}
|
||||
if (rest.length) rows.push(rest)
|
||||
return rows
|
||||
}
|
||||
function proc(text) {
|
||||
const lines = text.split('\n')
|
||||
for (let li = 0; li < lines.length; li++) {
|
||||
const isLast = li === lines.length - 1
|
||||
const line = lines[li]
|
||||
if (isLast && line === '' && lines.length > 1) continue
|
||||
for (const row of wrapLine(line)) ctx.console.log(row)
|
||||
}
|
||||
}
|
||||
if (!paths.length) {
|
||||
proc(bareStdin(ctx))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
proc(bareStdin(ctx))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('fold: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
proc(b4.toString(b))
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,15 @@ const CONF = {
|
||||
/** Defaults for simulated pipelines (override with BARE_OS_PIPELINE_* env); see handbook §3. */
|
||||
BARE_OS_PIPELINE_MAX_STAGES: '32',
|
||||
BARE_OS_PIPELINE_MAX_BYTES: '2097152',
|
||||
BARE_OS_PIPELINE_MAX_LINES: '50000'
|
||||
BARE_OS_PIPELINE_MAX_LINES: '50000',
|
||||
/** Default cap for find -exec/-ok invocations per run (override with env). */
|
||||
BARE_OS_FIND_EXEC_MAX: '64',
|
||||
/** Max lines `yes` prints before stopping (override with BARE_OS_YES_MAX_LINES). */
|
||||
BARE_OS_YES_MAX_LINES: '100000',
|
||||
/** Max input lines `shuf` will hold in memory (override with BARE_OS_SHUF_MAX_LINES). */
|
||||
BARE_OS_SHUF_MAX_LINES: '50000',
|
||||
/** Max output chunk files `split` may create (override with BARE_OS_SPLIT_MAX_FILES). */
|
||||
BARE_OS_SPLIT_MAX_FILES: '10000'
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
ctx.console.log('usage: groups [USER]\nPrint group memberships (session model; often one group).')
|
||||
return
|
||||
}
|
||||
}
|
||||
const e = ctx.vfs.env || {}
|
||||
const u = e.USER || e.LOGNAME || 'guest'
|
||||
const g = e.GROUP || u
|
||||
const extra = e.GROUPS
|
||||
if (extra && String(extra).trim()) {
|
||||
ctx.console.log(String(extra).replace(/,/g, ' '))
|
||||
return
|
||||
}
|
||||
ctx.console.log(g)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
async function run(ctx, argv) {
|
||||
ctx.console.log(
|
||||
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: awk basename cat chgrp chmod chown cksum clear cp crontab curl cut date dirname dircolors du edit echo env exit false find getconf grep head hdms help hostname id journalctl jq ln login logout logname ls man mkdir mkfifo mktemp nano mv nl od pathchk printenv printf pwd readlink rm rmdir savevault sed seq sleep sort stat systemctl tail tee test theme time touch tr true tty uname wc wget which whoami xargs'
|
||||
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
|
||||
BARE_OS_HELP_BIN_SPACED
|
||||
)
|
||||
ctx.console.log(
|
||||
'Docs: man <command> | man handbook (full handbook, section 7) | man bare-os-shell | man -k <word> | man -l'
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
ctx.console.log('usage: hostid\nPrint a numeric host identifier (session-derived stub).')
|
||||
return
|
||||
}
|
||||
if (argv[i].startsWith('-')) {
|
||||
ctx.console.error('hostid: unknown option ' + argv[i])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
const e = ctx.vfs.env || {}
|
||||
if (e.HOSTID && /^[0-9a-fA-F]{8}$/.test(e.HOSTID)) {
|
||||
ctx.console.log(e.HOSTID.toLowerCase())
|
||||
return
|
||||
}
|
||||
const seed = e.BARE_OS_SESSION_ID || e.HOSTNAME || 'bare-os'
|
||||
let h = 0
|
||||
for (let i = 0; i < seed.length; i++)
|
||||
h = (Math.imul(31, h) + seed.charCodeAt(i)) >>> 0
|
||||
ctx.console.log(h.toString(16).padStart(8, '0'))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
async function run(ctx, argv) {
|
||||
let mode = null
|
||||
const rest = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: install [-m MODE] SOURCE DEST\nCopy one file to DEST and optionally chmod.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-m' && argv[i + 1]) {
|
||||
const m = parseInt(argv[++i], 8)
|
||||
if (!Number.isFinite(m) || m < 0) {
|
||||
ctx.console.error('install: invalid mode')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
mode = m & 0o777
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('install: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
rest.push(a)
|
||||
}
|
||||
if (rest.length !== 2) {
|
||||
ctx.console.error('usage: install [-m MODE] SOURCE DEST')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const [src, dest] = rest
|
||||
const buf = await ctx.vfs.readFile(src)
|
||||
if (!buf) {
|
||||
ctx.console.error('install: cannot read ' + src)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ctx.vfs.writeFile(dest, buf)
|
||||
if (mode != null) await ctx.vfs.chmod(dest, mode)
|
||||
} catch (e) {
|
||||
ctx.console.error('install: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
async function readLines(ctx, path) {
|
||||
const b4 = ctx.b4a
|
||||
if (path === '-') return bareStdin(ctx).split('\n')
|
||||
const b = await ctx.vfs.readFile(path)
|
||||
if (!b) return null
|
||||
return b4.toString(b).split('\n')
|
||||
}
|
||||
|
||||
function field(line, delim, n) {
|
||||
if (delim === null) {
|
||||
const parts = line.split(/\s+/)
|
||||
return parts[n - 1] != null ? parts[n - 1] : ''
|
||||
}
|
||||
const parts = line.split(delim)
|
||||
return parts[n - 1] != null ? parts[n - 1] : ''
|
||||
}
|
||||
|
||||
function key(line, delim, n) {
|
||||
return field(line, delim, n)
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let delim = null
|
||||
let f1 = 1
|
||||
let f2 = 1
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: join [-t CHAR] [-1 N] [-2 N] FILE1 FILE2\nJoin lines on equal join fields (sorted input).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-t' && argv[i + 1]) {
|
||||
const d = argv[++i]
|
||||
delim = d === '\\t' ? '\t' : d.slice(0, 1)
|
||||
continue
|
||||
}
|
||||
if (a === '-1' && argv[i + 1]) {
|
||||
f1 = parseInt(argv[++i], 10)
|
||||
continue
|
||||
}
|
||||
if (a === '-2' && argv[i + 1]) {
|
||||
f2 = parseInt(argv[++i], 10)
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('join: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
if (paths.length !== 2) {
|
||||
ctx.console.error('usage: join [-t CHAR] [-1 N] [-2 N] FILE1 FILE2')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const A = await readLines(ctx, paths[0])
|
||||
const B = await readLines(ctx, paths[1])
|
||||
if (!A || !B) {
|
||||
ctx.console.error('join: missing input file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = (arr) => {
|
||||
if (arr.length && arr[arr.length - 1] === '') arr.pop()
|
||||
return arr
|
||||
}
|
||||
const a = trim(A.slice())
|
||||
const b = trim(B.slice())
|
||||
const sep = delim != null ? delim : ' '
|
||||
let i = 0
|
||||
let j = 0
|
||||
while (i < a.length && j < b.length) {
|
||||
const ka = key(a[i], delim, f1)
|
||||
const kb = key(b[j], delim, f2)
|
||||
const cmp = ka.localeCompare(kb)
|
||||
if (cmp < 0) i++
|
||||
else if (cmp > 0) j++
|
||||
else {
|
||||
const k = ka
|
||||
let i1 = i
|
||||
while (i1 < a.length && key(a[i1], delim, f1) === k) i1++
|
||||
let j1 = j
|
||||
while (j1 < b.length && key(b[j1], delim, f2) === k) j1++
|
||||
for (let ii = i; ii < i1; ii++) {
|
||||
for (let jj = j; jj < j1; jj++) {
|
||||
ctx.console.log(a[ii] + sep + b[jj])
|
||||
}
|
||||
}
|
||||
i = i1
|
||||
j = j1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,42 @@
|
||||
/**
|
||||
* @param {unknown} vfs
|
||||
* @param {string[]} names
|
||||
* @param {string} t
|
||||
* @param {string | null} singleEntryPath
|
||||
* @param {'time' | 'size' | null} sortBy
|
||||
* @param {boolean} reverse
|
||||
*/
|
||||
async function bareLsSortNames(
|
||||
vfs,
|
||||
names,
|
||||
t,
|
||||
singleEntryPath,
|
||||
sortBy,
|
||||
reverse
|
||||
) {
|
||||
if (singleEntryPath != null) return names
|
||||
if (!sortBy && !reverse) return names
|
||||
const pairs = []
|
||||
for (const n of names) {
|
||||
const sub = t === '.' || t === './' ? n : t.replace(/\/$/, '') + '/' + n
|
||||
let st = null
|
||||
try {
|
||||
st = await vfs.lstat(sub)
|
||||
} catch {
|
||||
st = null
|
||||
}
|
||||
pairs.push({ n, st })
|
||||
}
|
||||
pairs.sort((a, b) => {
|
||||
let cmp = 0
|
||||
if (sortBy === 'time') cmp = (b.st?.mtimeMs ?? 0) - (a.st?.mtimeMs ?? 0)
|
||||
else if (sortBy === 'size') cmp = (b.st?.size ?? 0) - (a.st?.size ?? 0)
|
||||
else cmp = a.n.localeCompare(b.n)
|
||||
return reverse ? -cmp : cmp
|
||||
})
|
||||
return pairs.map((p) => p.n)
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
let showAll = false
|
||||
@@ -6,6 +45,9 @@ async function run(ctx, argv) {
|
||||
let singleColumn = false
|
||||
/** @type {'never' | 'auto' | 'always'} */
|
||||
let colorMode = 'auto'
|
||||
/** @type {'time' | 'size' | null} */
|
||||
let sortBy = null
|
||||
let reverseSort = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
@@ -21,7 +63,8 @@ async function run(ctx, argv) {
|
||||
if (a.startsWith('--color=')) {
|
||||
const v = a.slice(8).toLowerCase()
|
||||
if (v === 'never' || v === 'none' || v === 'no') colorMode = 'never'
|
||||
else if (v === 'always' || v === 'yes' || v === 'force') colorMode = 'always'
|
||||
else if (v === 'always' || v === 'yes' || v === 'force')
|
||||
colorMode = 'always'
|
||||
else colorMode = 'auto'
|
||||
continue
|
||||
}
|
||||
@@ -29,6 +72,14 @@ async function run(ctx, argv) {
|
||||
singleColumn = true
|
||||
continue
|
||||
}
|
||||
if (a === '--sort=time' || a === '--sort=none') {
|
||||
sortBy = a.endsWith('time') ? 'time' : null
|
||||
continue
|
||||
}
|
||||
if (a === '--sort=size') {
|
||||
sortBy = 'size'
|
||||
continue
|
||||
}
|
||||
ctx.console.error('ls: unrecognized option ' + a)
|
||||
ctx.exitCode = 2
|
||||
return
|
||||
@@ -39,6 +90,9 @@ async function run(ctx, argv) {
|
||||
if (c === 'a') showAll = true
|
||||
else if (c === 'l') longFmt = true
|
||||
else if (c === '1') singleColumn = true
|
||||
else if (c === 't') sortBy = 'time'
|
||||
else if (c === 'S') sortBy = 'size'
|
||||
else if (c === 'r') reverseSort = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -46,8 +100,7 @@ async function run(ctx, argv) {
|
||||
}
|
||||
const targets = paths.length ? paths : ['.']
|
||||
const useColor = bareLsUseColor(ctx, colorMode)
|
||||
const onePerLine =
|
||||
singleColumn || ctx.bareOsStdoutCaptured === true
|
||||
const onePerLine = singleColumn || ctx.bareOsStdoutCaptured === true
|
||||
|
||||
for (const t of targets) {
|
||||
if (targets.length > 1) ctx.console.log(t + ':')
|
||||
@@ -68,6 +121,14 @@ async function run(ctx, argv) {
|
||||
continue
|
||||
}
|
||||
if (!showAll) names = names.filter((n) => !n.startsWith('.'))
|
||||
names = await bareLsSortNames(
|
||||
vfs,
|
||||
names,
|
||||
t,
|
||||
singleEntryPath,
|
||||
sortBy,
|
||||
reverseSort
|
||||
)
|
||||
if (!longFmt) {
|
||||
if (onePerLine) {
|
||||
for (const n of names) {
|
||||
@@ -161,6 +222,17 @@ async function run(ctx, argv) {
|
||||
st
|
||||
})
|
||||
}
|
||||
if ((sortBy || reverseSort) && singleEntryPath == null) {
|
||||
rows.sort((a, b) => {
|
||||
let cmp = 0
|
||||
const sta = a.st
|
||||
const stb = b.st
|
||||
if (sortBy === 'time') cmp = (stb?.mtimeMs ?? 0) - (sta?.mtimeMs ?? 0)
|
||||
else if (sortBy === 'size') cmp = (stb?.size ?? 0) - (sta?.size ?? 0)
|
||||
else cmp = a.name.localeCompare(b.name)
|
||||
return reverseSort ? -cmp : cmp
|
||||
})
|
||||
}
|
||||
if (singleEntryPath == null) ctx.console.log('total ' + totalBlocks)
|
||||
for (const r of rows) {
|
||||
const tail = bareLsColorLongTail(r.name, r.arrow, r.st, useColor, ctx)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: md5sum [FILE]...\n' +
|
||||
'With no FILE, or when FILE is -, read standard input. Uses bundled MD5 (no Web Crypto MD5).'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('md5sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = bareMd5DigestBytes(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error('md5sum: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('md5sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: nproc [--all]\nPrint number of processing units (from /proc/cpuinfo or 1). --all is accepted for compatibility.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '--all') {
|
||||
/* same count as default in this environment */
|
||||
} else if (a.startsWith('-')) {
|
||||
ctx.console.error('nproc: unknown option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
let n = 1
|
||||
try {
|
||||
const buf = await ctx.vfs.readFile('/proc/cpuinfo')
|
||||
if (buf) {
|
||||
const t = ctx.b4a.toString(buf)
|
||||
const m = t.match(/^processor\s*:/gim)
|
||||
if (m && m.length) n = m.length
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const e = ctx.vfs.env || {}
|
||||
const envN = e.BARE_OS_NPROC
|
||||
if (envN && /^\d+$/.test(envN)) n = Math.max(1, parseInt(envN, 10))
|
||||
ctx.console.log(String(n))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
function fmtIec(n, si) {
|
||||
const base = si ? 1000 : 1024
|
||||
const units = si
|
||||
? ['', 'k', 'M', 'G', 'T', 'P']
|
||||
: ['', 'K', 'M', 'G', 'T', 'P']
|
||||
if (n === 0) return '0'
|
||||
let sign = n < 0 ? -1 : 1
|
||||
let x = Math.abs(n)
|
||||
let u = 0
|
||||
while (x >= base && u < units.length - 1) {
|
||||
x /= base
|
||||
u++
|
||||
}
|
||||
const s =
|
||||
x >= 10 || u === 0 ? Math.round(x * 10) / 10 : Math.round(x * 100) / 100
|
||||
const t = String(s).replace(/\.0$/, '')
|
||||
return (sign < 0 ? '-' : '') + t + units[u]
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let toIec = false
|
||||
let toSi = false
|
||||
const rest = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: numfmt [--to=iec|--to=si] [NUMBER]...\nFormat numbers; reads stdin lines if no operands.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '--to=iec') toIec = true
|
||||
else if (a === '--to=si') toSi = true
|
||||
else if (a.startsWith('-')) {
|
||||
ctx.console.error('numfmt: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
} else rest.push(a)
|
||||
}
|
||||
if (!toIec && !toSi) toIec = true
|
||||
const nums = []
|
||||
if (!rest.length) {
|
||||
for (const line of bareStdin(ctx).split('\n')) {
|
||||
const t = line.trim()
|
||||
if (!t) continue
|
||||
nums.push(t)
|
||||
}
|
||||
} else nums.push(...rest)
|
||||
for (const s of nums) {
|
||||
const n = parseInt(s, 10)
|
||||
if (!Number.isFinite(n)) {
|
||||
ctx.console.error('numfmt: invalid number ' + s)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.log(fmtIec(n, toSi))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
async function readLines(ctx, path) {
|
||||
const b4 = ctx.b4a
|
||||
if (path === '-') return bareStdin(ctx).split('\n')
|
||||
const b = await ctx.vfs.readFile(path)
|
||||
if (!b) return null
|
||||
return b4.toString(b).split('\n')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let delims = '\t'
|
||||
let serial = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: paste [-d LIST] [-s] [FILE]...\nMerge corresponding lines; -s pastes one file per line.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if ((a === '-d' || a === '--delimiters') && argv[i + 1]) {
|
||||
delims = argv[++i]
|
||||
continue
|
||||
}
|
||||
if (a === '-s' || a === '--serial') {
|
||||
serial = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('paste: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
if (!paths.length) paths.push('-')
|
||||
const files = []
|
||||
for (const p of paths) {
|
||||
const L = await readLines(ctx, p)
|
||||
if (L == null) {
|
||||
ctx.console.error('paste: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
|
||||
files.push(trim)
|
||||
}
|
||||
if (serial) {
|
||||
for (const lines of files) {
|
||||
let out = ''
|
||||
for (let r = 0; r < lines.length; r++) {
|
||||
if (r) out += delims[0] || '\t'
|
||||
out += lines[r]
|
||||
}
|
||||
ctx.console.log(out)
|
||||
}
|
||||
return
|
||||
}
|
||||
const maxR = Math.max(...files.map((f) => f.length), 0)
|
||||
for (let r = 0; r < maxR; r++) {
|
||||
const parts = []
|
||||
for (let c = 0; c < files.length; c++) {
|
||||
parts.push(files[c][r] != null ? files[c][r] : '')
|
||||
}
|
||||
let line = ''
|
||||
for (let c = 0; c < parts.length; c++) {
|
||||
if (c) line += delims[c % delims.length] || '\t'
|
||||
line += parts[c]
|
||||
}
|
||||
ctx.console.log(line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
async function run(ctx, argv) {
|
||||
let width = 72
|
||||
let numberLines = false
|
||||
let sep = '\t'
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: pr [-w WIDTH] [-n] [-s CHAR] [FILE]...\nMinimal print: merge files side by side with optional line numbers.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if ((a === '-w' || a === '--width') && argv[i + 1]) {
|
||||
width = parseInt(argv[++i], 10)
|
||||
if (!Number.isFinite(width) || width < 1) width = 72
|
||||
continue
|
||||
}
|
||||
if (a === '-n' || a === '--number') {
|
||||
numberLines = true
|
||||
continue
|
||||
}
|
||||
if ((a === '-s' || a === '--separator') && argv[i + 1]) {
|
||||
sep = argv[++i] || '\t'
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('pr: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
const files = []
|
||||
if (!paths.length) {
|
||||
files.push(bareStdin(ctx).split('\n'))
|
||||
} else {
|
||||
for (const p of paths) {
|
||||
if (p === '-') files.push(bareStdin(ctx).split('\n'))
|
||||
else {
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('pr: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
files.push(b4.toString(b).split('\n'))
|
||||
}
|
||||
}
|
||||
}
|
||||
const maxRows = Math.max(...files.map((f) => f.length), 0)
|
||||
for (let r = 0; r < maxRows; r++) {
|
||||
const parts = []
|
||||
for (let c = 0; c < files.length; c++) {
|
||||
let cell = files[c][r] != null ? files[c][r] : ''
|
||||
if (numberLines && c === 0 && r < files[0].length)
|
||||
cell = String(r + 1).padStart(6, ' ') + '\t' + cell
|
||||
parts.push(cell)
|
||||
}
|
||||
let line = parts.join(sep)
|
||||
if (line.length > width) line = line.slice(0, width)
|
||||
ctx.console.log(line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* realpath — print resolved absolute path (VFS logical resolution).
|
||||
*/
|
||||
async function run(ctx, argv) {
|
||||
let missingOk = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: realpath [-m] FILE...\n' +
|
||||
' -m do not fail if the path does not exist (resolve only)'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a === '-m' || a === '--canonicalize-missing') {
|
||||
missingOk = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('realpath: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
if (!paths.length) {
|
||||
ctx.console.error('usage: realpath [-m] FILE...')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const vfs = ctx.vfs
|
||||
for (const p of paths) {
|
||||
try {
|
||||
if (!missingOk) {
|
||||
const st = await vfs.stat(p)
|
||||
if (!st) {
|
||||
ctx.console.error('realpath: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
const resolved = vfs.resolveLogical(p)
|
||||
ctx.console.log(resolved)
|
||||
} catch (e) {
|
||||
if (missingOk) {
|
||||
ctx.console.log(vfs.resolveLogical(p))
|
||||
} else {
|
||||
ctx.console.error('realpath: ' + p + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log('usage: rev [FILE]...\nReverse characters on each line.')
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('rev: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function procText(text) {
|
||||
const endsNl = text.endsWith('\n')
|
||||
const lines = text.split('\n')
|
||||
if (endsNl && lines.length && lines[lines.length - 1] === '') lines.pop()
|
||||
for (const line of lines) {
|
||||
ctx.console.log(line.split('').reverse().join(''))
|
||||
}
|
||||
}
|
||||
if (!paths.length) {
|
||||
procText(bareStdin(ctx))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
let text
|
||||
if (p === '-') text = bareStdin(ctx)
|
||||
else {
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('rev: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
text = b4.toString(b)
|
||||
}
|
||||
procText(text)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* rm — remove files or directories.
|
||||
* Flags: -r -R --recursive, -f --force, -- ; bundled e.g. -rf
|
||||
* Flags: -r -R --recursive, -f --force, -d --dir, -- ; bundled e.g. -rf
|
||||
*/
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
let recursive = false
|
||||
let force = false
|
||||
let dirEmptyOnly = false
|
||||
const files = []
|
||||
let dash = false
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
@@ -26,11 +27,16 @@ async function run(ctx, argv) {
|
||||
force = true
|
||||
continue
|
||||
}
|
||||
if (a === '--dir' || a === '-d') {
|
||||
dirEmptyOnly = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-') && a.length > 1) {
|
||||
for (let j = 1; j < a.length; j++) {
|
||||
const c = a[j]
|
||||
if (c === 'r' || c === 'R') recursive = true
|
||||
else if (c === 'f') force = true
|
||||
else if (c === 'd') dirEmptyOnly = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -41,6 +47,11 @@ async function run(ctx, argv) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (dirEmptyOnly && recursive) {
|
||||
ctx.console.error('rm: cannot combine -d and -r')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const doRm =
|
||||
vfs && typeof vfs.rm === 'function'
|
||||
? (p) => vfs.rm(p, { recursive, force })
|
||||
@@ -50,6 +61,27 @@ async function run(ctx, argv) {
|
||||
}
|
||||
for (const f of files) {
|
||||
try {
|
||||
if (dirEmptyOnly) {
|
||||
const st = await vfs.lstat(f)
|
||||
if (!st) {
|
||||
if (!force) {
|
||||
ctx.console.error('rm: ' + f + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (st.type !== 'directory') {
|
||||
ctx.console.error('rm: cannot remove ' + f + ': Not a directory')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
if (typeof vfs.rmdir === 'function') await vfs.rmdir(f)
|
||||
else {
|
||||
ctx.console.error('rm: rmdir not supported by this VFS')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
await doRm(f)
|
||||
} catch (e) {
|
||||
if (force) continue
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
async function sha1Hex(u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error('crypto.subtle.digest (SHA-1) is not available')
|
||||
}
|
||||
const hash = await subtle.digest('SHA-1', u8)
|
||||
return [...new Uint8Array(hash)]
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: sha1sum [FILE]...\n' +
|
||||
'With no FILE, or when FILE is -, read standard input.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('sha1sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await sha1Hex(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error('sha1sum: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('sha1sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* sha256sum — compute SHA-256 checksums (hex), GNU-like output line.
|
||||
*/
|
||||
async function sha256Hex(u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error('crypto.subtle.digest (SHA-256) is not available')
|
||||
}
|
||||
const hash = await subtle.digest('SHA-256', u8)
|
||||
return [...new Uint8Array(hash)]
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: sha256sum [FILE]...\n' +
|
||||
'With no FILE, or when FILE is -, read standard input.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('sha256sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await sha256Hex(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error('sha256sum: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
const buf = b4.from(bareStdin(ctx))
|
||||
await one('-', buf)
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('sha256sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
async function sha512Hex(u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error('crypto.subtle.digest (SHA-512) is not available')
|
||||
}
|
||||
const hash = await subtle.digest('SHA-512', u8)
|
||||
return [...new Uint8Array(hash)]
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: sha512sum [FILE]...\n' +
|
||||
'With no FILE, or when FILE is -, read standard input.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('sha512sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await sha512Hex(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error('sha512sum: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('sha512sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
async function readLines(ctx, path) {
|
||||
const b4 = ctx.b4a
|
||||
if (path === '-') return bareStdin(ctx).split('\n')
|
||||
const b = await ctx.vfs.readFile(path)
|
||||
if (!b) return null
|
||||
return b4.toString(b).split('\n')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: shuf [FILE]...\nShuffle lines; memory-capped via BARE_OS_SHUF_MAX_LINES.'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('shuf: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const cap =
|
||||
parseInt(ctx.vfs.env.BARE_OS_SHUF_MAX_LINES || '50000', 10) || 50000
|
||||
if (!paths.length) paths.push('-')
|
||||
const b4 = ctx.b4a
|
||||
const lines = []
|
||||
for (const p of paths) {
|
||||
const L = await readLines(ctx, p)
|
||||
if (L == null) {
|
||||
ctx.console.error('shuf: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
|
||||
for (const ln of trim) {
|
||||
if (lines.length >= cap) {
|
||||
ctx.console.error('shuf: input exceeds line cap')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
lines.push(ln)
|
||||
}
|
||||
}
|
||||
for (let i = lines.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
const t = lines[i]
|
||||
lines[i] = lines[j]
|
||||
lines[j] = t
|
||||
}
|
||||
for (const ln of lines) ctx.console.log(ln)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
function splitSuffix(prefix, i) {
|
||||
const a = 'abcdefghijklmnopqrstuvwxyz'
|
||||
const hi = Math.floor(i / 26) % 26
|
||||
const lo = i % 26
|
||||
return prefix + a[hi] + a[lo]
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let lineCount = 1000
|
||||
let byteCount = null
|
||||
let prefix = 'x'
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: split [-l N] [-b N] [INPUT [PREFIX]]\nSplit INPUT into pieces (default -l 1000).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-l' && argv[i + 1]) {
|
||||
lineCount = parseInt(argv[++i], 10)
|
||||
byteCount = null
|
||||
if (!Number.isFinite(lineCount) || lineCount < 1) lineCount = 1000
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-l') && a.length > 2) {
|
||||
lineCount = parseInt(a.slice(2), 10)
|
||||
byteCount = null
|
||||
continue
|
||||
}
|
||||
if (a === '-b' && argv[i + 1]) {
|
||||
byteCount = parseInt(argv[++i], 10)
|
||||
lineCount = null
|
||||
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('split: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const maxFiles =
|
||||
parseInt(ctx.vfs.env.BARE_OS_SPLIT_MAX_FILES || '10000', 10) || 10000
|
||||
let inputPath = '-'
|
||||
if (paths.length === 1) inputPath = paths[0]
|
||||
else if (paths.length >= 2) {
|
||||
inputPath = paths[0]
|
||||
prefix = paths[1]
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
let text
|
||||
if (inputPath === '-') text = bareStdin(ctx)
|
||||
else {
|
||||
const b = await ctx.vfs.readFile(inputPath)
|
||||
if (!b) {
|
||||
ctx.console.error('split: cannot read ' + inputPath)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
text = b4.toString(b)
|
||||
}
|
||||
if (byteCount != null) {
|
||||
const u8 = b4.from(text)
|
||||
let off = 0
|
||||
let idx = 0
|
||||
while (off < u8.length) {
|
||||
if (idx >= maxFiles) {
|
||||
ctx.console.error('split: too many output files')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const chunk = u8.subarray(off, off + byteCount)
|
||||
off += byteCount
|
||||
await ctx.vfs.writeFile(splitSuffix(prefix, idx), chunk)
|
||||
idx++
|
||||
}
|
||||
return
|
||||
}
|
||||
const endsNl = text.endsWith('\n')
|
||||
const lines = text.split('\n')
|
||||
if (endsNl && lines.length && lines[lines.length - 1] === '') lines.pop()
|
||||
let idx = 0
|
||||
let batch = []
|
||||
function flush(forceNl) {
|
||||
if (!batch.length) return
|
||||
if (idx >= maxFiles) {
|
||||
ctx.console.error('split: too many output files')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const body =
|
||||
batch.join('\n') + (forceNl || batch.length === lineCount ? '\n' : '')
|
||||
const p = splitSuffix(prefix, idx)
|
||||
idx++
|
||||
batch = []
|
||||
return ctx.vfs.writeFile(p, b4.from(body))
|
||||
}
|
||||
for (let li = 0; li < lines.length; li++) {
|
||||
batch.push(lines[li])
|
||||
if (batch.length >= lineCount) {
|
||||
await flush(true)
|
||||
if (ctx.exitCode) return
|
||||
}
|
||||
}
|
||||
if (batch.length) {
|
||||
const tailNl = endsNl
|
||||
const body = batch.join('\n') + (tailNl ? '\n' : '')
|
||||
if (idx >= maxFiles) {
|
||||
ctx.console.error('split: too many output files')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
await ctx.vfs.writeFile(splitSuffix(prefix, idx), b4.from(body))
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@ function statApplyFormat(st, displayPath, fmt) {
|
||||
for (let i = 0; i < fmt.length; i++) {
|
||||
if (fmt[i] === '%' && i + 1 < fmt.length) {
|
||||
const c = fmt[++i]
|
||||
if (c === 'n')
|
||||
out += displayPath.split('/').pop() || displayPath
|
||||
if (c === 'n') out += displayPath.split('/').pop() || displayPath
|
||||
else if (c === 'N') out += displayPath
|
||||
else if (c === 's') out += String(st.size ?? 0)
|
||||
else if (c === 'Y')
|
||||
@@ -18,13 +17,17 @@ function statApplyFormat(st, displayPath, fmt) {
|
||||
(typeof st.mtimeMs === 'number' ? st.mtimeMs : Date.now()) / 1000
|
||||
)
|
||||
)
|
||||
else if (c === 'A')
|
||||
out += bareFormatModeString(st.mode, st.type)
|
||||
else if (c === 'A') out += bareFormatModeString(st.mode, st.type)
|
||||
else if (c === 'U') out += String(st.user ?? '')
|
||||
else if (c === 'G') out += String(st.group ?? '')
|
||||
else if (c === 'u') out += String(st.uid ?? 0)
|
||||
else if (c === 'g') out += String(st.gid ?? 0)
|
||||
else if (c === '%') out += '%'
|
||||
else if (c === 'F') {
|
||||
const ty = st.type
|
||||
if (ty === 'directory') out += 'directory'
|
||||
else if (ty === 'symlink') out += 'symbolic link'
|
||||
else out += 'regular file'
|
||||
} else if (c === '%') out += '%'
|
||||
else out += '%' + c
|
||||
} else {
|
||||
out += fmt[i]
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
function sumSysv(u8) {
|
||||
let crc = 0
|
||||
for (let i = 0; i < u8.length; i++) crc += u8[i]
|
||||
crc = (crc & 0xffff) + ((crc >> 16) & 0xffff)
|
||||
crc = (crc & 0xffff) + ((crc >> 16) & 0xffff)
|
||||
return crc & 0xffff
|
||||
}
|
||||
|
||||
function sumBsd(u8) {
|
||||
let cksum = 0
|
||||
for (let i = 0; i < u8.length; i++) {
|
||||
cksum = (cksum >> 1) + ((cksum & 1) << 15)
|
||||
cksum = (cksum + u8[i]) & 0xffff
|
||||
}
|
||||
return cksum
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let bsd = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: sum [-r] [FILE]...\n' +
|
||||
' -r BSD algorithm (default is SysV / CRC16-style sum)'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a === '-r') bsd = true
|
||||
else if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
} else paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
const blocks = Math.ceil(u8.length / 512) || 1
|
||||
const v = bsd ? sumBsd(u8) : sumSysv(u8)
|
||||
ctx.console.log(v + '\t' + blocks + '\t' + name)
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
ctx.console.log('usage: sync\nFlush filesystem buffers (no-op on Bare OS; exits 0).')
|
||||
return
|
||||
}
|
||||
if (argv[i].startsWith('-')) {
|
||||
ctx.console.error('sync: unknown option ' + argv[i])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log('usage: tac [FILE]...\nConcatenate and print lines in reverse order.')
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('tac: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function tacText(text) {
|
||||
const raw = text.endsWith('\n') ? text.slice(0, -1) : text
|
||||
if (raw === '') {
|
||||
ctx.console.log('')
|
||||
return
|
||||
}
|
||||
const lines = raw.split('\n')
|
||||
lines.reverse()
|
||||
ctx.console.log(lines.join('\n'))
|
||||
}
|
||||
if (!paths.length) {
|
||||
tacText(bareStdin(ctx))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
let text
|
||||
if (p === '-') text = bareStdin(ctx)
|
||||
else {
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('tac: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
text = b4.toString(b)
|
||||
}
|
||||
tacText(text)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
async function run(ctx, argv) {
|
||||
let size = null
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: truncate -s SIZE FILE\nSet file length to SIZE bytes (padded with zeros if growing).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if ((a === '-s' || a === '--size') && argv[i + 1]) {
|
||||
const raw = argv[++i]
|
||||
if (raw.startsWith('+') || raw.startsWith('-')) {
|
||||
ctx.console.error('truncate: relative sizes not supported')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
size = parseInt(raw, 10)
|
||||
if (!Number.isFinite(size) || size < 0) {
|
||||
ctx.console.error('truncate: invalid size')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('truncate: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
if (size == null || paths.length !== 1) {
|
||||
ctx.console.error('usage: truncate -s SIZE FILE')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const file = paths[0]
|
||||
let cur = new Uint8Array(0)
|
||||
try {
|
||||
const b = await ctx.vfs.readFile(file)
|
||||
if (b) cur = b instanceof Uint8Array ? b : new Uint8Array(b)
|
||||
} catch {
|
||||
/* new file */
|
||||
}
|
||||
const out = new Uint8Array(size)
|
||||
out.set(cur.subarray(0, Math.min(cur.length, size)))
|
||||
try {
|
||||
await ctx.vfs.writeFile(file, out)
|
||||
} catch (e) {
|
||||
ctx.console.error('truncate: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: tsort [FILE]\nTopological sort of directed edges (one pair per line: A B).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('tsort: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
let text
|
||||
if (!paths.length || paths[0] === '-') text = bareStdin(ctx)
|
||||
else {
|
||||
const b = await ctx.vfs.readFile(paths[0])
|
||||
if (!b) {
|
||||
ctx.console.error('tsort: cannot read ' + paths[0])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
text = b4.toString(b)
|
||||
}
|
||||
const edges = []
|
||||
const nodes = new Set()
|
||||
for (const line of text.split('\n')) {
|
||||
const t = line.trim()
|
||||
if (!t) continue
|
||||
const parts = t.split(/\s+/)
|
||||
if (parts.length < 2) continue
|
||||
const u = parts[0]
|
||||
const v = parts[1]
|
||||
edges.push([u, v])
|
||||
nodes.add(u)
|
||||
nodes.add(v)
|
||||
}
|
||||
const indeg = new Map()
|
||||
const adj = new Map()
|
||||
for (const n of nodes) {
|
||||
indeg.set(n, 0)
|
||||
adj.set(n, [])
|
||||
}
|
||||
for (const [u, v] of edges) {
|
||||
indeg.set(v, (indeg.get(v) || 0) + 1)
|
||||
adj.get(u).push(v)
|
||||
}
|
||||
const q = []
|
||||
for (const [n, d] of indeg) {
|
||||
if (d === 0) q.push(n)
|
||||
}
|
||||
q.sort()
|
||||
const out = []
|
||||
while (q.length) {
|
||||
const u = q.shift()
|
||||
out.push(u)
|
||||
for (const v of adj.get(u) || []) {
|
||||
indeg.set(v, indeg.get(v) - 1)
|
||||
if (indeg.get(v) === 0) {
|
||||
q.push(v)
|
||||
q.sort()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (out.length !== nodes.size) {
|
||||
ctx.console.error('tsort: cycle in input')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
for (const n of out) ctx.console.log(n)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
function parseWidth(tabArg) {
|
||||
const n = parseInt(String(tabArg).split(',')[0].trim(), 10)
|
||||
return Number.isFinite(n) && n > 0 ? n : 8
|
||||
}
|
||||
|
||||
function unexpandLine(line, w) {
|
||||
let out = ''
|
||||
let col = 0
|
||||
let i = 0
|
||||
while (i < line.length) {
|
||||
if (line[i] === ' ') {
|
||||
let j = i
|
||||
while (j < line.length && line[j] === ' ') j++
|
||||
const n = j - i
|
||||
const posMod = col % w
|
||||
if (posMod === 0 && n >= w) {
|
||||
const tabs = Math.floor(n / w)
|
||||
const rest = n % w
|
||||
for (let k = 0; k < tabs; k++) out += '\t'
|
||||
col += tabs * w
|
||||
i += tabs * w
|
||||
for (let k = 0; k < rest; k++) {
|
||||
out += ' '
|
||||
col++
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
const toNext = posMod === 0 ? w : w - posMod
|
||||
if (n >= toNext && toNext > 0 && posMod !== 0) {
|
||||
out += '\t'
|
||||
col += toNext
|
||||
i += toNext
|
||||
continue
|
||||
}
|
||||
out += line.slice(i, j)
|
||||
col += n
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
const ch = line[i]
|
||||
out += ch
|
||||
if (ch === '\t') col = (Math.floor(col / w) + 1) * w
|
||||
else if (ch === '\n' || ch === '\r') col = 0
|
||||
else col++
|
||||
i++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
let w = 8
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: unexpand [-t N] [FILE]...\nConvert runs of spaces to tabs (width N, default 8).'
|
||||
)
|
||||
return
|
||||
}
|
||||
if (a === '-t' && argv[i + 1]) {
|
||||
w = parseWidth(argv[++i])
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-t') && a.length > 2) {
|
||||
w = parseWidth(a.slice(2))
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('--tabs=')) {
|
||||
w = parseWidth(a.slice(7))
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('unexpand: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function proc(text) {
|
||||
const lines = text.split('\n')
|
||||
for (let li = 0; li < lines.length; li++) {
|
||||
const isLast = li === lines.length - 1
|
||||
const line = lines[li]
|
||||
if (isLast && line === '' && lines.length > 1) continue
|
||||
ctx.console.log(unexpandLine(line, w))
|
||||
}
|
||||
}
|
||||
if (!paths.length) {
|
||||
proc(bareStdin(ctx))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
proc(bareStdin(ctx))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('unexpand: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
proc(b4.toString(b))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* uniq — filter adjacent duplicate lines from sorted input.
|
||||
*/
|
||||
async function run(ctx, argv) {
|
||||
let count = false
|
||||
let onlyDup = false
|
||||
let onlyUniq = false
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: uniq [-c] [-d] [-u] [INPUT [OUTPUT]]\n' +
|
||||
' -c prefix lines with repeat count\n' +
|
||||
' -d only print duplicate lines (one of each group)\n' +
|
||||
' -u only print lines that are not repeated\n' +
|
||||
'Reads stdin if INPUT omitted; OUTPUT is ignored (VFS single-output via redirect).'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a === '-c' || a === '--count') {
|
||||
count = true
|
||||
continue
|
||||
}
|
||||
if (a === '-d' || a === '--repeated') {
|
||||
onlyDup = true
|
||||
continue
|
||||
}
|
||||
if (a === '-u' || a === '--unique') {
|
||||
onlyUniq = true
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('uniq: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
if (onlyDup && onlyUniq) {
|
||||
ctx.console.error('uniq: -d and -u are mutually exclusive')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
let text
|
||||
if (!paths.length) {
|
||||
text = bareStdin(ctx)
|
||||
} else {
|
||||
const b = await ctx.vfs.readFile(paths[0])
|
||||
if (!b) {
|
||||
ctx.console.error('uniq: cannot read ' + paths[0])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
text = ctx.b4a.toString(b)
|
||||
}
|
||||
const raw = text.replace(/\r\n/g, '\n')
|
||||
const lines = raw.length ? raw.split('\n') : []
|
||||
if (lines.length && lines[lines.length - 1] === '') lines.pop()
|
||||
let i = 0
|
||||
while (i < lines.length) {
|
||||
let j = i + 1
|
||||
while (j < lines.length && lines[j] === lines[i]) j++
|
||||
const reps = j - i
|
||||
if (onlyDup && reps === 1) {
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
if (onlyUniq && reps > 1) {
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
if (count) ctx.console.log(String(reps) + ' ' + lines[i])
|
||||
else ctx.console.log(lines[i])
|
||||
i = j
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
async function run(ctx, argv) {
|
||||
const args = argv.slice(1).filter((a) => a !== '--')
|
||||
if (args.length === 1 && (args[0] === '-h' || args[0] === '--help')) {
|
||||
ctx.console.log('usage: unlink FILE\nCall unlink(2) on one file.')
|
||||
return
|
||||
}
|
||||
if (args.length !== 1) {
|
||||
ctx.console.error('usage: unlink FILE')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (args[0].startsWith('-')) {
|
||||
ctx.console.error('unlink: unsupported option ' + args[0])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ctx.vfs.unlink(args[0])
|
||||
} catch (e) {
|
||||
ctx.console.error('unlink: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
ctx.console.log('usage: uptime\nPrint load average and uptime from /proc/uptime when available.')
|
||||
return
|
||||
}
|
||||
if (argv[i].startsWith('-')) {
|
||||
ctx.console.error('uptime: unknown option ' + argv[i])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
let up = 0
|
||||
let idle = 0
|
||||
try {
|
||||
const buf = await ctx.vfs.readFile('/proc/uptime')
|
||||
if (buf) {
|
||||
const parts = ctx.b4a.toString(buf).trim().split(/\s+/)
|
||||
up = parseFloat(parts[0]) || 0
|
||||
idle = parseFloat(parts[1]) || 0
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const now = new Date()
|
||||
const timeStr = now.toTimeString().slice(0, 8)
|
||||
const days = Math.floor(up / 86400)
|
||||
const hrs = Math.floor((up % 86400) / 3600)
|
||||
const mins = Math.floor((up % 3600) / 60)
|
||||
let upStr =
|
||||
days > 0
|
||||
? days + ' day' + (days === 1 ? '' : 's') + ', '
|
||||
: ''
|
||||
upStr += String(hrs).padStart(2, '0') + ':' + String(mins).padStart(2, '0')
|
||||
let load = '0.00 0.00 0.00'
|
||||
try {
|
||||
const la = await ctx.vfs.readFile('/proc/loadavg')
|
||||
if (la) {
|
||||
const t = ctx.b4a.toString(la).trim().split(/\s+/)
|
||||
if (t.length >= 3) load = t[0] + ' ' + t[1] + ' ' + t[2]
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
void idle
|
||||
ctx.console.log(
|
||||
timeStr +
|
||||
' up ' +
|
||||
upStr +
|
||||
', load average: ' +
|
||||
load
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
async function run(ctx, argv) {
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '-h' || argv[i] === '--help') {
|
||||
ctx.console.log('usage: users\nPrint login names (single-session stub).')
|
||||
return
|
||||
}
|
||||
if (argv[i].startsWith('-')) {
|
||||
ctx.console.error('users: unknown option ' + argv[i])
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
const e = ctx.vfs.env || {}
|
||||
ctx.console.log(e.USER || e.LOGNAME || 'guest')
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
async function run(ctx, argv) {
|
||||
if (typeof ctx.runBinCommand !== 'function') {
|
||||
ctx.console.error('vdir: runBinCommand not available')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
await ctx.runBinCommand(['ls', '-l'].concat(argv.slice(1)))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user