Skip to content

[Feature Request] Add nul delimited output support #1218

@d7sd6u

Description

@d7sd6u

Yes, filenames with newlines are cursed and generally should not be used ever, but sometimes you have to be sure operations on paths work reliably. find, grep, perl, xargs, fd, rg, dirname, basename, realpath, readlink and pretty much any established tool that deals with linux paths can consume and/or produce nul delimited output. xz works fantastically as a glue with business logic that hooks into native binaries piggy-backing on their performance, so sometimes using JS glob is just not the preferable way to implement a script. We have .json(), .lines(), .text(), .blob(), .buffer(), [Symbol.asyncIterator] so maybe this list can be extended with something like this:

type ProcessOutput = {
  nulLines: (() => Promise<string[]>) & { [Symbol.asyncIterator](): AsyncIterator<string> }
};

Currently the only options are either to allocate (potentially) enourmous string and split by nul bytes or reimplement an async iterator from scratch every time:

import { Readable } from 'node:stream';
import 'zx/globals';

const root = import.meta.dirname;
cd(root);
$.verbose = true;

await $`mkdir -p testdir`;
await $`touch ${'testdir/absolutely\nlegitemate\nlinux\nfilename'}`;
await $`touch testdir/justafile`;

const entries = await $`find ./testdir -type f -print0`.lines();
console.log(entries.length); // 4

const split = await $`find ./testdir -type f -print0`
  .text()
  .then((v) => v.slice(0, -1).split('\0'));
console.log(split.length); // 2

async function* nulIterator(stream: Readable) {
  let buf = Buffer.alloc(0);

  for await (const chunk of stream) {
    buf = Buffer.concat([buf, chunk]);
    let idx;
    while ((idx = buf.indexOf(0)) !== -1) {
      yield buf.subarray(0, idx).toString();
      buf = buf.subarray(idx + 1);
    }
  }
  if (buf.length) yield buf.toString();
}

for await (const entry of nulIterator(
  $`find ./testdir -type f -print0`.stdout,
)) {
  console.log(entry); // 2 invocations
}

This may not fit the size constraints and/or the vision of the package, so (obviously) feel free to close as wontfix

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions