Updates to coreutils
This commit is contained in:
@@ -39,13 +39,15 @@ Or `node packages/bare-os-coreutils/build.mjs`.
|
||||
|
||||
**`ls`** prepends **[`bare-os-lscolors`](../bare-os-lscolors/bare-os-lscolors.js)** for **`LS_COLORS`** / dircolors parsing. **`dircolors`** and **`theme`** integrate with the booter’s **`bare-os-theme-presets.js`** (see [docs/themes/README.md](../../docs/themes/README.md)).
|
||||
|
||||
**`grep`** uses JavaScript **`RegExp`** (and **`-F`** fixed strings); POSIX/GNU-like **subset** (including **`-x`**, **`-m`**, **`-o`** among common flags).
|
||||
**`grep`** uses JavaScript **`RegExp`** (and **`-F`** fixed strings); POSIX/GNU-like **subset** (including **`-x`**, **`-m`**, **`-o`** among common flags). It is **not** PCRE- or GNU-bit-identical; use **`-F`** when literals must not be treated as regex.
|
||||
|
||||
**`sed`** / **`awk`** use large interpreters in **`lib/sed-engine.js`** and **`lib/awk-engine.js`** — capable, but not guaranteed to match every POSIX or GNU edge case.
|
||||
**`sed`** / **`awk`** use large interpreters in **`lib/sed-engine.js`** and **`lib/awk-engine.js`** — capable, but **not** guaranteed to match every POSIX or GNU edge case; treat parity as best-effort.
|
||||
|
||||
**`mkfifo`** creates simulated named pipes under **`/run/bare-os/ipc/<name>`** (in-memory; see booter VFS). **`getconf`** and **`xargs`** implement a documented Bare-specific subset (see `src/getconf.js`, `src/xargs.js`).
|
||||
|
||||
**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`**, **`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`**, **`sort -k/-t`**, **`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.
|
||||
**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.
|
||||
|
||||
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,8 +2,8 @@
|
||||
"name": "ls",
|
||||
"section": 1,
|
||||
"title": "list directory contents",
|
||||
"synopsis": ["ls [-1al] [--color[=never|auto|always]] [FILE...]"],
|
||||
"description": "Lists directory contents. Long format shows mode, links, owner, group, size, mtime, and symlink targets. With color (default auto on a TTY), directories, symlinks, executables, and permission bits are highlighted.",
|
||||
"synopsis": ["ls [-1al] [--color[=never|auto|always]] [--format=single-column] [FILE...]"],
|
||||
"description": "Lists directory contents. Long format shows mode, links, owner, group, size, mtime, and symlink targets. With color (default auto on a TTY), directories, symlinks, executables, and permission bits are highlighted. When stdout is captured by the shell (pipeline or **>** redirect), short format lists **one name per line** (GNU-like), so tools such as **grep** and **sort** see one entry per line.",
|
||||
"options": [
|
||||
{
|
||||
"flag": "-a",
|
||||
@@ -14,8 +14,8 @@
|
||||
"meaning": "Long listing"
|
||||
},
|
||||
{
|
||||
"flag": "-1",
|
||||
"meaning": "One name per line (short format)"
|
||||
"flag": "-1, --format=single-column, --format=vertical",
|
||||
"meaning": "One name per line (short format); same layout when stdout is piped or redirected"
|
||||
},
|
||||
{
|
||||
"flag": "--color[=never|auto|always]",
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
"name": "sort",
|
||||
"section": 1,
|
||||
"title": "sort lines",
|
||||
"synopsis": ["sort [OPTION]... [OPERAND]..."],
|
||||
"description": "Sorts lines from files or stdin. Supports numeric sort, reverse, unique consecutive lines, and case fold for sort keys.",
|
||||
"synopsis": ["sort [OPTION]... [FILE]..."],
|
||||
"description": "Sorts lines from files or stdin. Collation follows JavaScript string ordering (and numeric keys when **`-n`**). Stable ordering is only guaranteed when **`-s`** is set.",
|
||||
"options": [
|
||||
{ "flag": "-c, --check", "meaning": "Check whether input is already sorted; exit **1** if not; print a **disorder** diagnostic to stderr (GNU-style)" },
|
||||
{ "flag": "-C, --check=quiet, --check=silent", "meaning": "Like **`-c`** but no stderr message on failure" },
|
||||
{ "flag": "-o, --output FILE", "meaning": "Write result to **FILE** instead of stdout (place **`-o`** before file operands)" },
|
||||
{ "flag": "-s, --stable", "meaning": "Stable sort (preserve original order when keys compare equal)" },
|
||||
{ "flag": "-n, --numeric-sort, -g", "meaning": "Sort by leading numeric prefix" },
|
||||
{ "flag": "-r, --reverse", "meaning": "Reverse sort order" },
|
||||
{ "flag": "-u, --unique", "meaning": "Suppress duplicate lines after sorting" },
|
||||
{ "flag": "-u, --unique", "meaning": "Suppress duplicate lines after sorting; with **`-c`**, require strictly increasing keys (no adjacent duplicates)" },
|
||||
{ "flag": "-f, --ignore-case", "meaning": "Fold case for ordering" },
|
||||
{ "flag": "-t, --field-separator SEP", "meaning": "Field delimiter for **`-k`** (use **\\t** for tab)" },
|
||||
{ "flag": "-k, --key POS", "meaning": "Sort by 1-based field **POS** or **START,END** (bare subset; blank-separated fields when **`-t`** omitted)" },
|
||||
{ "flag": "-k, --key POS", "meaning": "Sort by 1-based field **POS** or **START,END** (blank-separated fields when **`-t`** omitted)" },
|
||||
{ "flag": "-", "meaning": "Operand reads stdin" }
|
||||
],
|
||||
"keywords": ["sort", "bare-os", "coreutils"],
|
||||
@@ -26,6 +30,14 @@
|
||||
{
|
||||
"caption": "unique",
|
||||
"code": "sort -u tags.txt"
|
||||
},
|
||||
{
|
||||
"caption": "verify sorted",
|
||||
"code": "sort -c sorted.txt"
|
||||
},
|
||||
{
|
||||
"caption": "write to file",
|
||||
"code": "sort -o out.txt -n nums.txt"
|
||||
}
|
||||
],
|
||||
"listCategory": "coreutils"
|
||||
|
||||
@@ -2,6 +2,8 @@ async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
let showAll = false
|
||||
let longFmt = false
|
||||
/** One name per line (GNU **`-1`** / **`--format=single-column`**, or piped/captured stdout). */
|
||||
let singleColumn = false
|
||||
/** @type {'never' | 'auto' | 'always'} */
|
||||
let colorMode = 'auto'
|
||||
const paths = []
|
||||
@@ -23,6 +25,10 @@ async function run(ctx, argv) {
|
||||
else colorMode = 'auto'
|
||||
continue
|
||||
}
|
||||
if (a === '--format=single-column' || a === '--format=vertical') {
|
||||
singleColumn = true
|
||||
continue
|
||||
}
|
||||
ctx.console.error('ls: unrecognized option ' + a)
|
||||
ctx.exitCode = 2
|
||||
return
|
||||
@@ -32,7 +38,7 @@ async function run(ctx, argv) {
|
||||
const c = a[j]
|
||||
if (c === 'a') showAll = true
|
||||
else if (c === 'l') longFmt = true
|
||||
else if (c === '1') longFmt = false
|
||||
else if (c === '1') singleColumn = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -40,6 +46,8 @@ async function run(ctx, argv) {
|
||||
}
|
||||
const targets = paths.length ? paths : ['.']
|
||||
const useColor = bareLsUseColor(ctx, colorMode)
|
||||
const onePerLine =
|
||||
singleColumn || ctx.bareOsStdoutCaptured === true
|
||||
|
||||
for (const t of targets) {
|
||||
if (targets.length > 1) ctx.console.log(t + ':')
|
||||
@@ -61,7 +69,27 @@ async function run(ctx, argv) {
|
||||
}
|
||||
if (!showAll) names = names.filter((n) => !n.startsWith('.'))
|
||||
if (!longFmt) {
|
||||
if (!useColor) {
|
||||
if (onePerLine) {
|
||||
for (const n of names) {
|
||||
if (!useColor) {
|
||||
ctx.console.log(n)
|
||||
} else {
|
||||
const sub =
|
||||
singleEntryPath != null
|
||||
? singleEntryPath
|
||||
: t === '.' || t === './'
|
||||
? n
|
||||
: t.replace(/\/$/, '') + '/' + n
|
||||
let st = null
|
||||
try {
|
||||
st = await vfs.lstat(sub)
|
||||
} catch {
|
||||
st = null
|
||||
}
|
||||
ctx.console.log(bareLsColorWrap(n, st, true, ctx))
|
||||
}
|
||||
}
|
||||
} else if (!useColor) {
|
||||
ctx.console.log(names.join(' '))
|
||||
} else {
|
||||
const parts = []
|
||||
|
||||
@@ -21,12 +21,75 @@ function sortKeySlice(line, delim, start1, end1) {
|
||||
return slice.join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} o
|
||||
* @param {string} o.line
|
||||
* @param {boolean} o.numeric
|
||||
* @param {boolean} o.fold
|
||||
* @param {number | null} o.keyStart
|
||||
* @param {number | null} o.keyEnd
|
||||
* @param {string | null} o.dForKey
|
||||
*/
|
||||
function sortKeyObj(o) {
|
||||
const keyText =
|
||||
o.keyStart != null
|
||||
? sortKeySlice(o.line, o.dForKey, o.keyStart, o.keyEnd)
|
||||
: o.line
|
||||
if (o.numeric) {
|
||||
const m = String(keyText).match(
|
||||
/^\s*(-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/
|
||||
)
|
||||
if (m) return { n: Number(m[1]), raw: keyText }
|
||||
return { n: Number.POSITIVE_INFINITY, raw: keyText }
|
||||
}
|
||||
if (o.fold) return { n: 0, raw: keyText.toLowerCase() }
|
||||
return { n: 0, raw: keyText }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} x
|
||||
* @param {string} y
|
||||
* @param {object} opts
|
||||
* @param {boolean} opts.numeric
|
||||
* @param {boolean} opts.reverse
|
||||
* @param {boolean} opts.fold
|
||||
* @param {number | null} opts.keyStart
|
||||
* @param {number | null} opts.keyEnd
|
||||
* @param {string | null} opts.dForKey
|
||||
* @returns {number}
|
||||
*/
|
||||
function sortCompareLines(x, y, opts) {
|
||||
const base = {
|
||||
numeric: opts.numeric,
|
||||
fold: opts.fold,
|
||||
keyStart: opts.keyStart,
|
||||
keyEnd: opts.keyEnd,
|
||||
dForKey: opts.dForKey
|
||||
}
|
||||
const kx = sortKeyObj({ ...base, line: x })
|
||||
const ky = sortKeyObj({ ...base, line: y })
|
||||
if (opts.numeric) {
|
||||
if (kx.n !== ky.n) {
|
||||
const ord = kx.n < ky.n ? -1 : 1
|
||||
return opts.reverse ? -ord : ord
|
||||
}
|
||||
}
|
||||
const cmp =
|
||||
kx.raw < ky.raw ? -1 : kx.raw > ky.raw ? 1 : x < y ? -1 : x > y ? 1 : 0
|
||||
return opts.reverse ? -cmp : cmp
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
let numeric = false
|
||||
let reverse = false
|
||||
let uniq = false
|
||||
let fold = false
|
||||
/** @type {'off' | 'verbose' | 'quiet'} */
|
||||
let checkMode = 'off'
|
||||
let stable = false
|
||||
/** @type {string | null} */
|
||||
let outFile = null
|
||||
/** @type {string | undefined} */
|
||||
let delim
|
||||
/** @type {number | null} */
|
||||
@@ -40,6 +103,52 @@ async function run(ctx, argv) {
|
||||
i++
|
||||
break
|
||||
}
|
||||
if (a === '--check' || a === '--check=diagnose-first') {
|
||||
checkMode = 'verbose'
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '--check=silent' || a === '--check=quiet') {
|
||||
checkMode = 'quiet'
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-C') {
|
||||
checkMode = 'quiet'
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-c') {
|
||||
checkMode = 'verbose'
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-s' || a === '--stable') {
|
||||
stable = true
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-o' || a === '--output') {
|
||||
const f = argv[++i]
|
||||
if (f === undefined) {
|
||||
ctx.console.error('sort: option requires an argument -- output')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
outFile = f
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('--output=')) {
|
||||
outFile = a.slice('--output='.length) || null
|
||||
if (!outFile) {
|
||||
ctx.console.error('sort: option requires an argument -- output')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (a === '-n' || a === '--numeric-sort' || a === '-g') {
|
||||
numeric = true
|
||||
i++
|
||||
@@ -130,6 +239,9 @@ async function run(ctx, argv) {
|
||||
else if (c === 'r') reverse = true
|
||||
else if (c === 'u') uniq = true
|
||||
else if (c === 'f') fold = true
|
||||
else if (c === 'c') checkMode = 'verbose'
|
||||
else if (c === 'C') checkMode = 'quiet'
|
||||
else if (c === 's') stable = true
|
||||
else {
|
||||
ok = false
|
||||
break
|
||||
@@ -172,37 +284,73 @@ async function run(ctx, argv) {
|
||||
}
|
||||
|
||||
const dForKey = delim === undefined ? null : delim
|
||||
|
||||
/** @param {string} s */
|
||||
function sortKey(s) {
|
||||
const keyText =
|
||||
keyStart != null
|
||||
? sortKeySlice(s, dForKey, keyStart, keyEnd)
|
||||
: s
|
||||
if (numeric) {
|
||||
const m = String(keyText).match(
|
||||
/^\s*(-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/
|
||||
)
|
||||
if (m) return { n: Number(m[1]), raw: keyText }
|
||||
return { n: Number.POSITIVE_INFINITY, raw: keyText }
|
||||
}
|
||||
if (fold) return { n: 0, raw: keyText.toLowerCase() }
|
||||
return { n: 0, raw: keyText }
|
||||
const cmpOpts = {
|
||||
numeric,
|
||||
reverse,
|
||||
fold,
|
||||
keyStart,
|
||||
keyEnd,
|
||||
dForKey
|
||||
}
|
||||
|
||||
lines.sort((x, y) => {
|
||||
const kx = sortKey(x)
|
||||
const ky = sortKey(y)
|
||||
if (numeric) {
|
||||
if (kx.n !== ky.n) {
|
||||
const ord = kx.n < ky.n ? -1 : 1
|
||||
return reverse ? -ord : ord
|
||||
if (checkMode !== 'off') {
|
||||
const checkLabel = files.length === 1 ? files[0] : '-'
|
||||
for (let li = 0; li < lines.length - 1; li++) {
|
||||
const aLine = lines[li]
|
||||
const bLine = lines[li + 1]
|
||||
const c = sortCompareLines(aLine, bLine, cmpOpts)
|
||||
if (uniq && c === 0) {
|
||||
ctx.exitCode = 1
|
||||
if (checkMode === 'verbose') {
|
||||
ctx.console.error(
|
||||
'sort: ' +
|
||||
checkLabel +
|
||||
':' +
|
||||
(li + 2) +
|
||||
': disorder: ' +
|
||||
bLine
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (c > 0) {
|
||||
ctx.exitCode = 1
|
||||
if (checkMode === 'verbose') {
|
||||
ctx.console.error(
|
||||
'sort: ' +
|
||||
checkLabel +
|
||||
':' +
|
||||
(li + 2) +
|
||||
': disorder: ' +
|
||||
bLine
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
const cmp =
|
||||
kx.raw < ky.raw ? -1 : kx.raw > ky.raw ? 1 : x < y ? -1 : x > y ? 1 : 0
|
||||
return reverse ? -cmp : cmp
|
||||
})
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
|
||||
/** @param {string} x
|
||||
* @param {string} y */
|
||||
function cmpPair(x, y) {
|
||||
const primary = sortCompareLines(x, y, cmpOpts)
|
||||
if (primary !== 0 || !stable) return primary
|
||||
return 0
|
||||
}
|
||||
|
||||
if (stable) {
|
||||
const decorated = lines.map((line, idx) => ({ line, idx }))
|
||||
decorated.sort((a, b) => {
|
||||
const p = sortCompareLines(a.line, b.line, cmpOpts)
|
||||
if (p !== 0) return p
|
||||
return a.idx - b.idx
|
||||
})
|
||||
lines = decorated.map((d) => d.line)
|
||||
} else {
|
||||
lines.sort(cmpPair)
|
||||
}
|
||||
|
||||
if (uniq) {
|
||||
const out = []
|
||||
@@ -214,5 +362,17 @@ async function run(ctx, argv) {
|
||||
lines = out
|
||||
}
|
||||
|
||||
if (lines.length) ctx.console.log(lines.join('\n'))
|
||||
const body = lines.length ? lines.join('\n') + '\n' : ''
|
||||
|
||||
if (outFile != null) {
|
||||
try {
|
||||
await vfs.writeFile(outFile, ctx.b4a.from(body))
|
||||
} catch (e) {
|
||||
ctx.console.error('sort: ' + outFile + ': ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
} else if (lines.length) {
|
||||
ctx.console.log(lines.join('\n'))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user