Proc-Spawner version 0.09 (EXPERIMENTAL) ======================================== Compiles and runs shell one-liners (or portable shell scripts) from Perl without /bin/sh and "without" fork, matching dash/bash: the strings are parsed by one of the Proc::ShellSubset parsers and run by Proc::ShellSpawn, which starts programs with posix_spawn(3) and runs loops, subshells, $( ) and pipelines of compound commands inside the Perl process. (All the started programs are children of the initial Perl process, as opposed to descendants.) Proc::Spawner start children (every posix_spawn setting); the event loop Proc::Spawner::Graph the executable graph of children, pipes and files Proc::ShellSpawn the shell layer Proc::ShellSubset, Proc::ShellSubset::Small, Proc::ShellSubset::Large parsers of three sizes of the shell language WHAT IS OPEN: the file TODO lists every postponed thing in one place, first the decisions waiting for the author (with where to read and how to act), then the work, then what to watch. HOW CLOSE TO DASH AND BASH (0.09; doc/large-subset/large-subset-state.md) Of 41,952 shell strings from six public collections (tldr, NL2Bash, ShellCheck's tests, the bash Cookbook, the pure sh and bash bibles), the large parser accepts 40,410. The rest are mostly bash-only syntax, which the subset rejects on purpose; 44 are forms both shells run alike, the work list (section 4). Each accepted string was then run by dash, by bash --posix and by Proc::ShellSpawn, isolated, with every program but the plain text tools replaced by a recorder of its arguments (section 7; the limits: known issue X27): run same only the dash and bash dash and bash messages agree, ours differ differ differs coreutils involved 1,344 892 313 5 131 no coreutils 39,066 14,859 23,083 2 1,108 all 40,410 15,751 23,396 7 1,239 (and 17 strings that never end, or not in ours: section 7) "Coreutils involved": the string calls echo, printf, test, [, kill or pwd, which the subset runs as programs where the shells have builtins. "Only the messages differ" is mostly dash's "x: not found" against bash's "x: command not found". Where dash and bash differ, the subset follows dash in 971, bash in 98, and neither in 170 (mostly $0, dates, random data). The 7 where ours differs: printf -v (2, decided), printf '%0.s' (1, open), echo into a broken pipe in a subshell (1), and 3 that depend on chance. Of the 40,410, 365 are shown not to depend on that environment at all, and 34,293 more not under named assumptions (mostly that a program does the same in each run), so their agreement is a real one; the other 5,752 may depend on files, variables, programs' output or status, /tmp and the like (known issue X27: doc/large-subset/large-subset-environment.md, and a record by snippet ID, doc/large-subset/large-subset-provenance.tsv). For the programs alone, each call the strings make was also run as dash's builtin, as bash's builtin and as the program: of 757 calls (1,268 uses), 620 (1,107) agree, 42 (46) differ in messages only, and 95 (115) differ: mostly echo -e and backslashes (dash's builtin against bash and the program), unary -a -o -v in [ ] (bash's builtin against dash and the program), and kill with a word that is no process ID (dash against bash and the program). Section 7 has them all, with the decisions taken so far. Tested on Linux with glibc 2.39 (also built as if some or all of the extensions were missing: PROC_SPAWNER_WITHOUT="PIDFD_SPAWN ..." perl Makefile.PL), and with /proc hidden, as on FreeBSD (RT #182479; FreeBSD itself is not tested here): without /proc, t/graph.t and two batteries of t/dash.t are skipped, and Perl's own writes into a pipe the caller gave may block (known issue X32). Parts that flip process-wide state for a while (umask, signal actions) are bullet-proof only in non-threaded execution. INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES Only modules that come with Perl. Building needs a C compiler. Makefile.PL finds whether the libc has posix_spawn, and which of its extensions (chdir, fchdir, closefrom, tcsetpgrp, pidfd_spawn, cgroup), by compiling a call to each. What the libc lacks is done in Perl's process around the call where that can be undone (chdir, the fds, the signal mask, ...); the rest fails with ENOTSUP, or is dropped, as %Proc::Spawner::UNSUPPORTED says. Without posix_spawn the primitive is fork and exec. Resource limits, ignored signals and the nice value, which posix_spawn has no option for, are set around the call too. Tests: t/spawner.t (Proc::Spawner and the graph), t/graph.t (hand-written graphs), t/parsers.t (1,117 strings through the parsers), and t/dash.t (every battery string run by dash and by the shell layer; skipped without dash; t/dash-known.txt lists the known differences). Their fixtures are in t/: t/batteries/, t/parser/, t/sgraph/, t/try.pl (runs one string: perl t/try.pl 'STRING'). COPYRIGHT AND LICENCE Copyright (C) 2026 by Ilya Zakharevich This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.22.2 or, at your option, any later version of Perl 5 you may have available.