{"id":13537167,"url":"https://github.com/modernish/modernish","last_synced_at":"2025-05-15T10:01:09.537Z","repository":{"id":44207749,"uuid":"51036457","full_name":"modernish/modernish","owner":"modernish","description":"Modernish is a library for writing robust, portable, readable, and powerful programs for POSIX-based shells and utilities.","archived":false,"fork":false,"pushed_at":"2024-11-03T11:41:38.000Z","size":4725,"stargazers_count":773,"open_issues_count":5,"forks_count":21,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-04-14T16:53:35.810Z","etag":null,"topics":["ash","bash","dash","ksh","ksh93","library","mksh","posix","posix-compatible","posix-compliant","posix-sh","sh","shell","shell-extension","shell-scripting","shellcode","yash","zsh"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/modernish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-02-03T22:48:38.000Z","updated_at":"2025-03-30T01:27:26.000Z","dependencies_parsed_at":"2024-03-31T13:37:57.276Z","dependency_job_id":"f12d098f-33f9-4bd5-b548-d5fd61c97f5c","html_url":"https://github.com/modernish/modernish","commit_stats":{"total_commits":1515,"total_committers":3,"mean_commits":505.0,"dds":"0.0013201320132013583","last_synced_commit":"e3c01f491ddc5c3b081b4c77fe8ae3f929770801"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modernish%2Fmodernish","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modernish%2Fmodernish/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modernish%2Fmodernish/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modernish%2Fmodernish/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modernish","download_url":"https://codeload.github.com/modernish/modernish/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254319715,"owners_count":22051072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ash","bash","dash","ksh","ksh93","library","mksh","posix","posix-compatible","posix-compliant","posix-sh","sh","shell","shell-extension","shell-scripting","shellcode","yash","zsh"],"created_at":"2024-08-01T09:00:55.816Z","updated_at":"2025-05-15T10:01:08.593Z","avatar_url":"https://github.com/modernish.png","language":"Shell","readme":"\u003cp align=\"center\"\u003e\u003cstrong\u003e\u003ca href=\"https://github.com/modernish/modernish/releases\"\u003eReleases\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003cstrong\u003eFor code examples, see\n\u003ca href=\"https://github.com/modernish/modernish/blob/master/EXAMPLES.md\"\u003e\n\u003ccode\u003eEXAMPLES.md\u003c/code\u003e\u003c/a\u003e\nand\n\u003ca href=\"https://github.com/modernish/modernish/tree/master/share/doc/modernish/examples\"\u003e\n\u003ccode\u003eshare/doc/modernish/examples\u003c/code\u003e\u003c/a\u003e\n\u003c/strong\u003e\u003c/p\u003e\n\n# modernish – harness the shell #\n\n-   *Sick of quoting hell and split/glob pitfalls?*\n-   *Tired of brittle shell scripts going haywire and causing damage?*\n-   *Mystified by line noise commands like `[`, `[[`, `((` ?*\n-   *Is scripting basic things just too hard?*\n-   *Ever wish that `find` were a built-in shell loop?*\n-   *Do you want your script to work on nearly any shell on any Unix-like OS?*\n\nModernish is a library for shell script programming which provides features\nlike safer variable and command expansion, new language constructs for loop\niteration, and much more. Modernish programs are shell programs; the new\nconstructs are mixed with shell syntax so that the programmer can take\nadvantage of the best of both.\n\nThere is no compiled code to install, as modernish is written entirely in the\nshell language. It can be deployed in embedded or multi-user systems in which\nnew binary executables may not be introduced for security reasons, and is\nportable among numerous shell implementations. The installer can also\n[bundle](#user-content-appendix-f-bundling-modernish-with-your-script)\na reduced copy of the library with your scripts, so they can run portably with\na known version of modernish without requiring prior installation.\n\n**Join us and help breathe some new life into the shell!** We\nare looking for testers, early adopters, and developers to join us.\n[Download the latest release](https://github.com/modernish/modernish/releases)\nor check out the very latest development code from the master branch.\nRead through the documentation below. Play with the example scripts and\nwrite your own. Try to break the library and send reports of breakage.\n\n\n## Table of contents ##\n\n* [Getting started](#user-content-getting-started)\n* [Two basic forms of a modernish program](#user-content-two-basic-forms-of-a-modernish-program)\n    * [Simple form](#user-content-simple-form)\n    * [Portable form](#user-content-portable-form)\n* [Interactive use](#user-content-interactive-use)\n* [Non-interactive command line use](#user-content-non-interactive-command-line-use)\n    * [Non-interactive usage examples](#user-content-non-interactive-usage-examples)\n* [Shell capability detection](#user-content-shell-capability-detection)\n* [Names and identifiers](#user-content-names-and-identifiers)\n    * [Internal namespace](#user-content-internal-namespace)\n    * [Modernish system constants](#user-content-modernish-system-constants)\n    * [Control character, whitespace and shell-safe character constants](#user-content-control-character-whitespace-and-shell-safe-character-constants)\n* [Reliable emergency halt](#user-content-reliable-emergency-halt)\n* [Low-level shell utilities](#user-content-low-level-shell-utilities)\n    * [Outputting strings](#user-content-outputting-strings)\n    * [Legibility aliases: `not`, `so`, `forever`](#user-content-legibility-aliases-not-so-forever)\n    * [Enhanced `exit`](#user-content-enhanced-exit)\n    * [`chdir`](#user-content-chdir)\n    * [`insubshell`](#user-content-insubshell)\n    * [`isset`](#user-content-isset)\n    * [`setstatus`](#user-content-setstatus)\n* [Testing numbers, strings and files](#user-content-testing-numbers-strings-and-files)\n    * [Integer number arithmetic tests and operations](#user-content-integer-number-arithmetic-tests-and-operations)\n        * [The arithmetic command `let`](#user-content-the-arithmetic-command-let)\n        * [Arithmetic shortcuts](#user-content-arithmetic-shortcuts)\n    * [String and file tests](#user-content-string-and-file-tests)\n        * [String tests](#user-content-string-tests)\n            * [Unary string tests](#user-content-unary-string-tests)\n            * [Binary string matching tests](#user-content-binary-string-matching-tests)\n            * [Multi-matching option](#user-content-multi-matching-option)\n        * [File type tests](#user-content-file-type-tests)\n        * [File comparison tests](#user-content-file-comparison-tests)\n        * [File status tests](#user-content-file-status-tests)\n        * [I/O tests](#user-content-io-tests)\n        * [File permission tests](#user-content-file-permission-tests)\n* [The stack](#user-content-the-stack)\n    * [The shell options stack](#user-content-the-shell-options-stack)\n    * [The trap stack](#user-content-the-trap-stack)\n* [Modules](#user-content-modules)\n    * [`use safe`](#user-content-use-safe)\n        * [Why the safe mode?](#user-content-why-the-safe-mode)\n        * [How the safe mode works](#user-content-how-the-safe-mode-works)\n        * [Important notes for safe mode](#user-content-important-notes-for-safe-mode)\n        * [Extra options for the safe mode](#user-content-extra-options-for-the-safe-mode)\n    * [`use var/loop`](#user-content-use-varloop)\n        * [Simple repeat loop](#user-content-simple-repeat-loop)\n        * [BASIC-style arithmetic `for` loop](#user-content-basic-style-arithmetic-for-loop)\n        * [C-style arithmetic `for` loop](#user-content-c-style-arithmetic-for-loop)\n        * [Enumerative `for`/`select` loop with safe split/glob](#user-content-enumerative-forselect-loop-with-safe-splitglob)\n        * [The `find` loop](#user-content-the-find-loop)\n            * [Available *options*](#user-content-available-options)\n            * [Available *find-expression* operands](#user-content-available-find-expression-operands)\n            * [Picking a `find` utility](#user-content-picking-a-find-utility)\n            * [Compatibility mode for obsolete `find` utilities](#user-content-compatibility-mode-for-obsolete-find-utilities)\n            * [`find` loop usage examples](#user-content-find-loop-usage-examples)\n        * [Creating your own loop](#user-content-creating-your-own-loop)\n    * [`use var/local`](#user-content-use-varlocal)\n        * [Important `var/local` usage notes](#user-content-important-varlocal-usage-notes)\n    * [`use var/arith`](#user-content-use-vararith)\n        * [Arithmetic operator shortcuts](#user-content-arithmetic-operator-shortcuts)\n        * [Arithmetic comparison shortcuts](#user-content-arithmetic-comparison-shortcuts)\n    * [`use var/assign`](#user-content-use-varassign)\n    * [`use var/readf`](#user-content-use-varreadf)\n    * [`use var/shellquote`](#user-content-use-varshellquote)\n        * [`shellquote`](#user-content-shellquote)\n        * [`shellquoteparams`](#user-content-shellquoteparams)\n    * [`use var/stack`](#user-content-use-varstack)\n        * [`use var/stack/extra`](#user-content-use-varstackextra)\n        * [`use var/stack/trap`](#user-content-use-varstacktrap)\n            * [Trap stack compatibility considerations](#user-content-trap-stack-compatibility-considerations)\n            * [The new `DIE` pseudosignal](#user-content-the-new-die-pseudosignal)\n    * [`use var/string`](#user-content-use-varstring)\n        * [`use var/string/touplow`](#user-content-use-varstringtouplow)\n        * [`use var/string/trim`](#user-content-use-varstringtrim)\n        * [`use var/string/replacein`](#user-content-use-varstringreplacein)\n        * [`use var/string/append`](#user-content-use-varstringappend)\n    * [`use var/unexport`](#user-content-use-varunexport)\n    * [`use var/genoptparser`](#user-content-use-vargenoptparser)\n    * [`use sys/base`](#user-content-use-sysbase)\n        * [`use sys/base/mktemp`](#user-content-use-sysbasemktemp)\n        * [`use sys/base/readlink`](#user-content-use-sysbasereadlink)\n        * [`use sys/base/rev`](#user-content-use-sysbaserev)\n        * [`use sys/base/seq`](#user-content-use-sysbaseseq)\n            * [Differences with GNU and BSD `seq`](#user-content-differences-with-gnu-and-bsd-seq)\n        * [`use sys/base/shuf`](#user-content-use-sysbaseshuf)\n        * [`use sys/base/tac`](#user-content-use-sysbasetac)\n        * [`use sys/base/which`](#user-content-use-sysbasewhich)\n        * [`use sys/base/yes`](#user-content-use-sysbaseyes)\n    * [`use sys/cmd`](#user-content-use-syscmd)\n        * [`use sys/cmd/extern`](#user-content-use-syscmdextern)\n        * [`use sys/cmd/harden`](#user-content-use-syscmdharden)\n            * [Important note on variable assignments](#user-content-important-note-on-variable-assignments)\n            * [Hardening while allowing for broken pipes](#user-content-hardening-while-allowing-for-broken-pipes)\n            * [Tracing the execution of hardened commands](#user-content-tracing-the-execution-of-hardened-commands)\n            * [Simple tracing of commands](#user-content-simple-tracing-of-commands)\n        * [`use sys/cmd/mapr`](#user-content-use-syscmdmapr)\n            * [Differences from `mapfile`](#user-content-differences-from-mapfile)\n            * [Differences from `xargs`](#user-content-differences-from-xargs)\n        * [`use sys/cmd/procsubst`](#user-content-use-syscmdprocsubst)\n        * [`use sys/cmd/source`](#user-content-use-syscmdsource)\n    * [`use sys/dir`](#user-content-use-sysdir)\n        * [`use sys/dir/countfiles`](#user-content-use-sysdircountfiles)\n        * [`use sys/dir/mkcd`](#user-content-use-sysdirmkcd)\n    * [`use sys/term`](#user-content-use-systerm)\n        * [`use sys/term/putr`](#user-content-use-systermputr)\n        * [`use sys/term/readkey`](#user-content-use-systermreadkey)\n* [Appendix A: List of shell cap IDs](#user-content-appendix-a-list-of-shell-cap-ids)\n    * [Capabilities](#user-content-capabilities)\n    * [Quirks](#user-content-quirks)\n    * [Bugs](#user-content-bugs)\n    * [Warning IDs](#user-content-warning-ids)\n* [Appendix B: Regression test suite](#user-content-appendix-b-regression-test-suite)\n    * [Difference between capability detection and regression tests](#user-content-difference-between-capability-detection-and-regression-tests)\n    * [Testing modernish on all your shells](#user-content-testing-modernish-on-all-your-shells)\n* [Appendix C: Supported locales](#user-content-appendix-c-supported-locales)\n* [Appendix D: Supported shells](#user-content-appendix-d-supported-shells)\n* [Appendix E: zsh: integration with native scripts](#user-content-appendix-e-zsh-integration-with-native-scripts)\n* [Appendix F: Bundling modernish with your script](#user-content-appendix-f-bundling-modernish-with-your-script)\n\n\n## Getting started ##\n\nRun `install.sh` and follow instructions, choosing your preferred shell\nand install location. After successful installation you can run modernish\nshell scripts and write your own. Run `uninstall.sh` to remove modernish.\n\nBoth the install and uninstall scripts are interactive by default, but\nsupport fully automated (non-interactive) operation as well. Command\nline options are as follows:\n\n`install.sh` [ `-n` ] [ `-s` *shell* ] [ `-f` ] [ `-P` *pathspec* ]\n[ `-d` *installroot* ] [ `-D` *prefix* ] [ `-B` *scriptfile* ... ]\n\n* `-n`: non-interactive operation\n* `-s`: specify default shell to execute modernish\n* `-f`: force unconditional installation on specified shell\n* `-P`: specify an alternative [`DEFPATH`](#user-content-modernish-system-constants)\n        for the installation (be careful; usually *not* recommended)\n* `-d`: specify root directory for installation\n* `-D`: extra destination directory prefix (for packagers)\n* `-B:` bundle modernish with your scripts (`-D` required, `-n` implied), see\n        [Appendix F](#user-content-appendix-f-bundling-modernish-with-your-script)\n\n`uninstall.sh` [ `-n` ] [ `-f` ] [ `-d` *installroot* ]\n\n* `-n`: non-interactive operation\n* `-f`: delete `*/modernish` directories even if files left\n* `-d`: specify root directory of modernish installation to uninstall\n\n\n## Two basic forms of a modernish program ##\n\nIn the *simple form*, modernish is added to a script written for a specific\nshell. In the *portable form*, your script is shell-agnostic and may run on any\n[shell that can run modernish](#user-content-appendix-d-supported-shells).\n\n### Simple form ###\n\nThe **simplest** way to write a modernish program is to source modernish as a\ndot script. For example, if you write for bash:\n\n```sh\n#! /bin/bash\n. modernish\nuse safe\nuse sys/base\n...your program starts here...\n```\n\nThe modernish `use` command load modules with optional functionality. The\n`safe` module initialises the [safe mode](#user-content-use-safe).\nThe `sys/base` module contains modernish versions of certain basic but\nnon-standardised utilities (e.g. `readlink`, `mktemp`, `which`), guaranteeing\nthat modernish programs all have a known version at their disposal. There are\nmany other modules as well. See [Modules](#user-content-modules) for more\ninformation.\n\nThe above method makes the program dependent on one particular shell (in this\ncase, bash). So it is okay to mix and match functionality specific to that\nparticular shell with modernish functionality.\n\n(On **zsh**, there is a way to integrate modernish with native zsh scripts. See\n[Appendix E](#user-content-appendix-e-zsh-integration-with-native-scripts).)\n\n### Portable form ###\n\nThe **most portable** way to write a modernish program is to use the special\ngeneric hashbang path for modernish programs. For example:\n\n```sh\n#! /usr/bin/env modernish\n#! use safe\n#! use sys/base\n...your program begins here...\n```\n\nFor portability, it is important there is no space after `env modernish`;\nNetBSD and OpenBSD consider trailing spaces part of the name, so `env` will\nfail to find modernish.\n\nA program in this form is executed by whatever shell the user who installed\nmodernish on the local system chose as the default shell. Since you as the\nprogrammer can't know what shell this is (other than the fact that it passed\nsome rigorous POSIX compliance testing executed by modernish), a program in\nthis form *must be strictly POSIX compliant* – except, of course, that it\nshould also make full use of the rich functionality offered by modernish.\n\nNote that modules are loaded in a different way: the `use` commands are part of\nhashbang comment (starting with `#!` like the initial hashbang path). Only such\nlines that *immediately* follow the initial hashbang path are evaluated; even\nan empty line in between causes the rest to be ignored.\nThis special way of pre-loading modules is needed to make any aliases they\ndefine work reliably on all shells.\n\n\n## Interactive use ##\n\nModernish is primarily designed to enhance shell programs/scripts, but also\noffers features for use in interactive shells. For instance, the new `repeat`\nloop construct from the `var/loop` module can be quite practical to repeat\nan action x times, and the `safe` module on interactive shells provides\nconvenience functions for manipulating, saving and restoring the state of\nfield splitting and globbing.\n\nTo use modernish on your favourite interactive shell, you have to add it to\nyour `.profile`, `.bashrc` or similar init file.\n\n**Important:** Upon initialising, modernish adapts itself to\nother settings, such as the locale. It also removes certain aliases that\nmay keep modernish from initialising properly. So you have to organise your\n`.profile` or similar file in the following order:\n\n* *first*, define general system settings (`PATH`, locale, etc.);\n* *then*, `. modernish` and `use` any modules you want;\n* *then* define anything that may depend on modernish, and set your aliases.\n\n\n## Non-interactive command line use ##\n\nAfter installation, the `modernish` command can be invoked as if it were a\nshell, with the standard command line options from other shells (such as\n`-c` to specify a command or script directly on the command line), plus some\nenhancements. The effect is that the shell chosen at installation time will\nbe run enhanced with modernish functionality. It is not possible to use\nmodernish as an interactive shell in this way.\n\nUsage:\n\n1. `modernish` [ `--use=`*module* | *shelloption* ... ]\n   [ *scriptfile* ] [ *arguments* ]\n2. `modernish` [ `--use=`*module* | *shelloption* ... ]\n   `-c` [ *script* [ *me-name* [ *arguments* ] ] ]\n3. `modernish --test` [ *testoption* ... ]\n4. `modernish` [ `--version` | `--help` ]\n\nIn the first form, the script in the file *scriptfile* is\nloaded and executed with any *arguments* assigned to the positional parameters.\n\nIn the second form, `-c` executes the specified modernish\n*script*, optionally with the *me-name* assigned to `$ME` and the\n*arguments* assigned to the positional parameters.\n\nThe `--use` option pre-loads any given modernish [modules](#user-content-modules)\nbefore executing the script.\nThe *module* argument to each specified `--use` option is split using\nstandard shell field splitting. The first field is the module name and any\nfurther fields become arguments to that module's initialisation routine.\n\nAny given short-form or long-form *shelloption*s are\nset or unset before executing the script. Both POSIX\n[shell options](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_25_03)\nand shell-specific options are supported, depending on\n[the shell executing modernish](#user-content-appendix-d-supported-shells).\nUsing the shell option `-e` or `-o errexit` is an error, because modernish\n[does not support it](#user-content-use-syscmdharden) and\nwould break.\n\nThe `--test` option runs the regression test suite and exits. This verifies\nthat the modernish installation is functioning correctly. See\n[Appendix B](#user-content-appendix-b-regression-test-suite)\nfor more information.\n\nThe `--version` and `--help` options output the relative information and exit.\n\n### Non-interactive usage examples ###\n\n* Count to 10 using a [basic loop](#user-content-use-varloop):    \n  `modernish --use=var/loop -c 'LOOP for i=1 to 10; DO putln \"$i\"; DONE'`\n* Run a [portable-form](#user-content-portable-form)\n  modernish program using zsh and enhanced-prompt xtrace:    \n  `zsh /usr/local/bin/modernish -o xtrace /path/to/program.sh`\n\n## Shell capability detection ##\n\nModernish includes a battery of shell feature, quirk and bug detection\ntests, each of which is given a special capability ID.\nSee [Appendix A](#user-content-appendix-a-list-of-shell-cap-ids) for a\nlist of shell capabilities that modernish currently detects, as well\nas further general information on the capability detection framework.\n\n`thisshellhas` is the central function of the capability detection\nframework. It not only tests for the presence of shell features/quirks/bugs,\nbut can also detect specific shell built-in commands, shell reserved words,\nshell options (short or long form), and signals.\n\nModernish itself extensively uses capability detection to adapt itself to the\nshell it's running on. This is how it works around shell bugs and takes\nadvantage of efficient features not all shells have. But any script using\nthe library can do this in the same way, with the help of this function.\n\nTest results are cached in memory, so repeated checks using `thisshellhas`\nare efficient and there is no need to avoid calling it to optimise\nperformance.\n\nUsage:\n\n`thisshellhas` *item* ...\n\n* If *item* contains only ASCII capital letters A-Z, digits 0-9 or `_`,\n  return the result status of the associated modernish\n  [capability detection test](#user-content-appendix-a-list-of-shell-cap-ids).\n* If *item* is any other ASCII word, check if it is a shell reserved\n  word or built-in command on the current shell.\n* If *item* is `--` (end-of-options delimiter), disable the recognition of\n  operators starting with `-` for subsequent items.\n* If *item* starts with `--rw=` or `--kw=`, check if the identifier\n  immediately following these characters is a shell reserved word\n  (a.k.a. shell keyword).\n* If *item* starts with `--bi=`, similarly check for a shell built-in command.\n* If *item* starts with `--sig=`, check if the shell knows about a signal\n  (usable by `kill`, `trap`, etc.) by the name or number following the `=`.\n  If a number \\\u003e 128 is given, the remainder of its division by 128 is checked.\n  If the signal is found, its canonicalised signal name is left in the\n  `REPLY` variable, otherwise `REPLY` is unset. (If multiple `--sig=` items\n  are given and all are found, `REPLY` contains only the last one.)\n* If *item* is `-o` followed by a separate word, check if this shell has a\n  long-form shell option by that name.\n* If *item* is any other letter or digit preceded by a single `-`, check if\n  this shell has a short-form shell option by that character.\n* *item* can also be one of the following two operators.\n    * `--cache` runs all external modernish shell capability tests\n      that have not yet been run, causing the cache to be complete.\n    * `--show` performs a `--cache` and then outputs all the IDs of\n      positive results, one per line.\n\n`thisshellhas` continues to process *item*s until one of them produces a\nnegative result or is found invalid, at which point any further *item*s are\nignored. So the function only returns successfully if all the *item*s\nspecified were found on the current shell. (To check if either one *item* or\nanother is present, use separate `thisshellhas` invocations separated by the\n`||` shell operator.)\n\nExit status: 0 if this shell has all the *items* in question; 1 if not; 2 if\nan *item* was encountered that is not recognised as a valid identifier.\n\n**Note:** The tests for the presence of reserved words, built-in commands,\nshell options, and signals are different from capability detection tests in an\nimportant way: they only check if an item by that name exists on this shell,\nand don't verify that it does the same thing as on another shell.\n\n\n## Names and identifiers ##\n\nAll modernish functions require portable variable and shell function names,\nthat is, ones consisting of ASCII uppercase and lowercase letters, digits,\nand the underscore character `_`, and that don't begin with digit. For shell\noption names, the constraints are the same except a dash `-` is also\naccepted. An invalid identifier is generally treated as a fatal error.\n\n### Internal namespace ###\n\nFunction-local variables are not supported by the standard POSIX shell; only\nglobal variables are provided for. Modernish needs a way to store its\ninternal state without interfering with the program using it. So most of the\nmodernish functionality uses an internal namespace `_Msh_*` for variables,\nfunctions and aliases. All these names may change at any time without\nnotice. *Any names starting with `_Msh_` should be considered sacrosanct and\nuntouchable; modernish programs should never directly use them in any way.*\nOf course this is not enforceable, but names starting with `_Msh_` should be\nuncommon enough that no unintentional conflict is likely to occur.\n\n### Modernish system constants ###\n\nModernish provides certain constants (read-only variables) to make life easier.\nThese include:\n\n* `$MSH_VERSION`: The version of modernish.\n* `$MSH_PREFIX`: Installation prefix for this modernish installation (e.g.\n  /usr/local).\n* `$MSH_MDL`: Main [modules](#user-content-modules) directory.\n* `$MSH_AUX`: Main helper scripts directory.\n* `$MSH_CONFIG`: Path to modernish user configuration directory.\n* `$ME`: Path to the current program. Replacement for `$0`. This is\n  necessary if the hashbang path `#!/usr/bin/env modernish` is used, or if\n  the program is launched like `sh /path/to/bin/modernish\n  /path/to/script.sh`, as these set `$0` to the path to bin/modernish and\n  not your program's path.\n* `$MSH_SHELL`: Path to the default shell for this modernish installation,\n  chosen at install time (e.g. /bin/sh). This is a shell that is known to\n  have passed all the modernish tests for fatal bugs. Cross-platform scripts\n  should use it instead of hard-coding /bin/sh, because on some operating\n  systems (NetBSD, OpenBSD, Solaris) /bin/sh is not POSIX compliant.\n* `$SIGPIPESTATUS`: The exit status of a command killed by `SIGPIPE` (a\n  broken pipe). For instance, if you use `grep something somefile.txt |\n  more` and you quit `more` before `grep` is finished, `grep` is killed by\n  `SIGPIPE` and exits with that particular status.\n  Hardened commands or functions may need to handle such a `SIGPIPE` exit\n  specially to avoid unduly killing the program. The exact value of this\n  exit status is shell-specific, so modernish runs a quick test to determine\n  it at initialisation time.    \n  If `SIGPIPE` was set to ignore by the process that invoked the current\n  shell, `$SIGPIPESTATUS` can't be detected and is set to the special value\n  99999. See also the description of the\n  [`WRN_NOSIGPIPE`](#user-content-warning-ids)\n  ID for\n  [`thisshellhas`](#user-content-shell-capability-detection).\n* `$DEFPATH`: The default system path guaranteed to find compliant POSIX\n  utilities, as given by `getconf PATH`.\n* `$ERROR`: A guaranteed unset variable that can be used to trigger an\n   error that exits the (sub)shell, for instance:\n   `: \"${4+${ERROR:?excess arguments}}\"` (error on 4 or more arguments)\n\n### Control character, whitespace and shell-safe character constants ###\n\nPOSIX does not provide for the quoted C-style escape codes commonly used in\nbash, ksh and zsh (such as `$'\\n'` to represent a newline character),\nleaving the standard shell without a convenient way to refer to control\ncharacters. Modernish provides control character constants (read-only\nvariables) with hexadecimal suffixes `$CC01` .. `$CC1F` and `$CC7F`, as well as `$CCe`,\n`$CCa`, `$CCb`, `$CCf`, `$CCn`, `$CCr`, `$CCt`, `$CCv` (corresponding with\n`printf` backslash escape codes). This makes it easy to insert control\ncharacters in double-quoted strings.\n\nMore convenience constants, handy for use in bracket glob patterns for use\nwith `case` or modernish `match`:\n\n* `$CONTROLCHARS`: All ASCII control characters.\n* `$WHITESPACE`: All ASCII whitespace characters.\n* `$ASCIIUPPER`: The ASCII uppercase letters A to Z.\n* `$ASCIILOWER`: The ASCII lowercase letters a to z.\n* `$ASCIIALNUM`: The ASCII alphanumeric characters 0-9, A-Z and a-z.\n* `$SHELLSAFECHARS`: Safe-list for shell-quoting.\n* `$ASCIICHARS`: The complete set of ASCII characters (minus NUL).\n\nUsage examples:\n\n```sh\n# Use a glob pattern to check against control characters in a string:\n\tif str match \"$var\" \"*[$CONTROLCHARS]*\"; then\n\t\tputln \"\\$var contains at least one control character\"\n\tfi\n# Use '!' (not '^') to check for characters *not* part of a particular set:\n\tif str match \"$var\" \"*[!$ASCIICHARS]*\"; then\n\t\tputln \"\\$var contains at least one non-ASCII character\" ;;\n\tfi\n# Safely split fields at any whitespace, comma or slash (requires safe mode):\n\tuse safe\n\tLOOP for --split=$WHITESPACE,/ field in $my_items; DO\n\t\tputln \"Item: $field\"\n\tDONE\n```\n\n## Reliable emergency halt ##\n\nThe `die` function reliably halts program execution, even from within\n[subshells](#user-content-insubshell), optionally\nprinting an error message. Note that `die` is meant for an emergency program\nhalt only, i.e. in situations were continuing would mean the program is in an\ninconsistent or undefined state. Shell scripts running in an inconsistent or\nundefined state may wreak all sorts of havoc. They are also notoriously\ndifficult to terminate correctly, especially if the fatal error occurs within\na subshell: `exit` won't work then. That's why `die` is optimised for\nkilling *all* the program's processes (including subshells and external\ncommands launched by it) as quickly as possible. It should never be used for\nexiting the program normally.\n\nOn interactive shells, `die` behaves differently. It does not kill or exit your\nshell; instead, it issues `SIGINT` to the shell to abort the execution of your\nrunning command(s), which is equivalent to pressing Ctrl+C.\nIn addition, if `die` is invoked from a subshell such as a background job, it\nkills all processes belonging to that job, but leaves other running jobs alone.\n\nUsage: `die` [ *message* ]\n\nIf the [trap stack module](#user-content-use-varstacktrap)\nis active, a special\n[`DIE` pseudosignal](#user-content-the-new-die-pseudosignal)\ncan be trapped (using plain old `trap` or\n[`pushtrap`](#user-content-the-trap-stack))\nto perform emergency cleanup commands upon invoking `die`.\n\nIf the `MSH_HAVE_MERCY` variable is set in a script and `die` is invoked\nfrom a subshell, then `die` will only terminate the current subshell and its\nsubprocesses and will not execute `DIE` traps, allowing the script to resume\nexecution in the parent process. This is for use in special cases, such as\nregression tests, and is strongly discouraged for general use. Modernish\nunsets the variable on init so it cannot be inherited from the environment.\n\n\n## Low-level shell utilities ##\n\n### Outputting strings ###\n\nThe POSIX shell lacks a simple, straightforward and portable way to output\narbitrary strings of text, so modernish adds two commands for this.\n\n* `put` prints each argument separated by a space, without a trailing newline.\n* `putln` prints each argument, terminating each with a newline character.\n\nThere is no processing of options or escape codes. (Modernish constants\n[`$CCn`, etc.](#user-content-control-character-whitespace-and-shell-safe-character-constants)\ncan be used to insert control characters in double-quoted strings. To process escape codes, use\n[`printf`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html)\ninstead.)\n\nThe `echo` command is notoriously unportable and kind of broken, so is\n**deprecated** in favour of `put` and `putln`. Modernish does provide its own\nversion of `echo`, but it is only activated for\n[portable-form](#user-content-portable-form))\nscripts. Otherwise, the shell-specific version of `echo` is left intact.\nThe modernish version of `echo` does not interpret any escape codes\nand supports only one option, `-n`, which, like BSD `echo`, suppresses the\nfinal newline. However, unlike BSD `echo`, if `-n` is the only argument, it is\nnot interpreted as an option and the string `-n` is printed instead. This makes\nit safe to output arbitrary data using this version of `echo` as long as it is\ngiven as a single argument (using quoting if needed).\n\n### Legibility aliases: `not`, `so`, `forever` ###\n\nModernish sets three aliases that can help to make the shell language look\nslightly friendlier. Their use is optional.\n\n`not` is a new synonym for `!`. They can be used interchangeably.\n\n`so` is a command that tests if the previous command exited with a status\nof zero, so you can test the preceding command's success with `if so` or\n`if not so`.\n\n`forever` is a new synonym for `while :;`. This allows simple infinite loops\nof the form: `forever do` *stuff*`; done`.\n\n### Enhanced `exit` ###\n\nThe `exit` command can be used as normal, but has gained capabilities.\n\nExtended usage: `exit` [ `-u` ] [ *status* [ *message* ] ]\n\n* As per standard, if *status* is not specified, it defaults to the exit\n  status of the command executed immediately prior to `exit`.\n  Otherwise, it is evaluated as a shell arithmetic expression. If it is\n  invalid as such, the shell exits immediately with an arithmetic error.\n* Any remaining arguments after *status* are combined, separated by spaces,\n  and taken as a *message* to print on exit. The message shown is preceded by\n  the name of the current program (`$ME` minus directories). Note that it is\n  not possible to skip *status* while specifying a *message*.\n* If the `-u` option is given, and the shell function `showusage` is defined,\n  that function is run in a subshell before exiting. It is intended to print\n  a message showing how the command should be invoked. The `-u` option has no\n  effect if the script has not defined a `showusage` function.\n* If *status* is non-zero, the *message* and the output of the `showusage`\n  function are redirected to standard error.\n\n### `chdir` ###\n\n`chdir` is a robust `cd` replacement for use in scripts.\n\nThe [standard `cd` command](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html)\nis designed for interactive shells and appropriate to use there.\nHowever, for scripts, its features create serious pitfalls:\n\n* The `$CDPATH` variable is searched. A script may inherit a user's\n  exported `$CDPATH`, so `cd` may change to an unintended directory.\n* `cd` cannot be used with arbitrary directory names (such as untrusted user\n  input), as some operands have special meanings, even after `--`. POSIX\n  specifies that `-` changes directory to `$OLDPWD`. On zsh (even in sh mode\n  on zsh \\\u003c= 5.7.1), numeric operands such as `+12` or `-345` represent\n  directory stack entries. All such paths need escaping by prefixing `./`.\n* Symbolic links in directory path components are not resolved by default,\n  leaving a potential symlink attack vector.\n\nThus, robust and portable use of `cd` in scripts is unreasonably difficult.\nThe modernish `chdir` function calls `cd` in a way that takes care of all\nthese issues automatically: it disables `$CDPATH` and special operand\nmeanings, and resolves symbolic links by default.\n\nUsage: `chdir` [ `-f` ] [ `-L` ] [ `-P` ] [ `--` ] *directorypath*\n\nNormally, failure to change the present working directory to *directorypath*\nis a fatal error that ends the program. To tolerate failure, add the `-f`\noption; in that case, exit status 0 signifies success and exit status 1\nsignifies failure, and scripts should always check and handle exceptions.\n\nThe options `-L` (logical: don't resolve symlinks) and `-P` (physical:\nresolve symlinks) are the same as in `cd`, except that `-P` is the default.\nNote that on a shell with [`BUG_CDNOLOGIC`](#user-content-bugs) (NetBSD sh),\nthe `-L` option to `chdir` does nothing.\n\nTo use arbitrary directory names (e.g. directory names input by the user or\nother untrusted input) always use the `--` separator that signals the end of\noptions, or paths starting with `-` may be misinterpreted as options.\n\n### `insubshell` ###\n\nThe `insubshell` function checks if you're currently running in a\n[subshell environment](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12)\n(usually called simply *subshell*).\n\nA *subshell* is a copy of the parent shell that starts out as an exact\nduplicate (including non-exported variables, functions, etc.), except for\ntraps. A new subshell is invoked by constructs like `(`parentheses`)`,\n`$(`command substitutions`)`, pipe`|`lines, and `\u0026` (to launch a background\nsubshell). Upon exiting a subshell, all changes to its state are lost.\n\nThis is not to be confused with a newly initialised shell that is\nmerely a child process of the current shell, which is sometimes\n(confusingly and **wrongly**) called a \"subshell\" as well.\nThis documentation avoids such a misleading use of the term.\n\nUsage: `insubshell` [ `-p` | `-u` ]\n\nThis function returns success (0) if it was called from within a subshell\nand non-success (1) if not. One of two options can be given:\n* `-p`: Store the process ID (PID) of the current subshell or main shell\n  in `REPLY`.\n* `-u`: Store an identifier in `REPLY` that is useful for determining if\n  you've entered a subshell relative to a previously stored identifier. The\n  content and format are unspecified and shell-dependent.\n\n### `isset` ###\n\n`isset` checks if a variable, shell function or option is set, or has\ncertain attributes. Usage:\n\n* `isset` *varname*: Check if a variable is set.\n* `isset -v` *varname*: Id.\n* `isset -x` *varname*: Check if variable is exported.\n* `isset -r` *varname*: Check if variable is read-only.\n* `isset -f` *funcname*: Check if a shell function is set.\n* `isset -`*optionletter* (e.g. `isset -C`): Check if shell option is set.\n* `isset -o` *optionname*: Check if shell option is set by long name.\n\nExit status: 0 if the item is set; 1 if not; 2 if the argument is not\nrecognised as a [valid identifier](#user-content-names-and-identifiers).\nUnlike most other modernish commands, `isset` does not treat an invalid\nidentifier as a fatal error.\n\nWhen checking a shell option, a nonexistent shell option is not an error,\nbut returns the same result as an unset shell option. (To check if a shell\noption exists, use [`thisshellhas`](#user-content-shell-capability-detection).\n\nNote: just `isset -f` checks if shell option `-f` (a.k.a. `-o noglob`) is\nset, but with an extra argument, it checks if a shell function is set.\nSimilarly, `isset -x` checks if shell option `-x` (a.k.a `-o xtrace`)\nis set, but `isset -x` *varname* checks if a variable is exported. If you\nuse unquoted variable expansions here, make sure they're not empty, or\nthe shell's empty removal mechanism will cause the wrong thing to be checked\n(even in the [safe mode](#user-content-use-safe)).\n\n### `setstatus` ###\n\n`setstatus` manually sets the exit status `$?` to the desired value. The\nfunction exits with the status indicated. This is useful in conditional\nconstructs if you want to prepare a particular exit status for a subsequent\n`exit` or `return` command to inherit under certain circumstances.\nThe status argument is a parsed as a shell arithmetic expression. A negative\nvalue is treated as a fatal error. The behaviour of values greater than 255\nis not standardised and depends on your particular shell.\n\n\n## Testing numbers, strings and files ##\n\nThe `test`/`[` command is the bane of casual shell scripters. Even advanced\nshell programmers are frequently caught unaware by one of the many pitfalls\nof its arcane, hackish syntax. It attempts to look like shell grammar without\n*being* shell grammar, causing myriad problems\n([1](http://wiki.bash-hackers.org/commands/classictest),\n[2](https://mywiki.wooledge.org/BashPitfalls)).\nIts `-a`, `-o`, `(` and `)` operators are *inherently and fatally broken* as\nthere is no way to reliably distinguish operators from operands, so POSIX\n[deprecates their use](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html#tag_20_128_16);\nhowever, most manual pages do not include this essential information, and\neven the few that do will not tell you what to do instead.\n\nKsh, zsh and bash offer a `[[` alternative that fixes many of these problems,\nas it is integrated into the shell grammar. Nevertheless, it increases\nconfusion, as entirely different grammar and quoting rules apply\nwithin `[[`...`]]` than outside it, yet many scripts end up using them\ninterchangeably. It is also not available on all POSIX shells. (To make\nmatters worse, Busybox ash has a false-friend `[[` that is just an alias\nof `[`, with none of the shell grammar integration!)\n\nFinally, the POSIX `test`/`[` command is incompatible with the modernish\n\"safe mode\" which aims to eliminate most of the need to quote variables.\nSee [`use safe`](#user-content-use-safe) for more information.\n\nModernish deprecates `test`/`[` and `[[` completely. Instead, it offers a\ncomprehensive alternative command design that works with the usual shell\ngrammar in a safer way while offering various feature enhancements. The\nfollowing replacements are available:\n\n### Integer number arithmetic tests and operations ###\n\nTo test if a string is a valid number in shell syntax, `str isint` is\navailable. See [String tests](#user-content-string-tests).\n\n#### The arithmetic command `let` ####\nAn implementation of `let` as in ksh, bash and zsh is now available to all\nPOSIX shells. This makes C-style signed integer arithmetic evaluation\navailable to every\n[supported shell](#user-content-appendix-d-supported-shells),\n*with the exception of the unary `++` and `--` operators*\n(which are a nonstandard shell capability detected by modernish under the ID of\n[`ARITHPP`](#user-content-appendix-a-list-of-shell-cap-ids)).\n\nThis means `let` should be used for operations and tests, e.g. both\n`let \"x=5\"` and `if let \"x==5\"; then`... are supported (note: single `=` for\nassignment, double `==` for comparison). See POSIX\n[2.6.4 Arithmetic Expansion](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_04)\nfor more information on the supported operators.\n\nMultiple expressions are supported, one per argument. The exit status of `let`\nis zero (the shell's idea of success/true) if the last expression argument\nevaluates to non-zero (the arithmetic idea of true), and 1 otherwise.\n\nIt is recommended to adopt the habit to quote each `let` expression with\n`\"`double quotes`\"`, as this consistently makes everything work as expected:\ndouble quotes protect operators that would otherwise be misinterpreted as\nshell grammar, while shell expansions starting with `$` continue to work.\n\n#### Arithmetic shortcuts ####\nVarious handy functions that make common arithmetic operations\nand comparisons easier to program are available from the\n[`var/arith`](#user-content-use-vararith) module.\n\n### String and file tests ###\n\nThe following notes apply to all commands described in the subsections of\nthis section:\n1. \"True\" is understood to mean exit status 0, and \"false\" is understood to\n   mean a non-zero exit status – specifically 1.\n2. Passing *more* than the number of arguments specified for each command\n   is a [fatal error](#user-content-reliable-emergency-halt). (If the\n   [safe mode](#user-content-use-safe) is not used, excessive arguments\n   may be generated accidentally if you forget to quote a variable. The\n   test result would have been wrong anyway, so modernish kills the\n   program immediately, which makes the problem much easier to trace.)\n3. Passing *fewer* than the number of arguments specified to the command is\n   assumed to be the result of removal of an empty unquoted expansion.\n   Where possible, this is not treated as an error, and an exit status\n   corresponding to the omitted argument(s) being empty is returned instead.\n   (This helps make the [safe mode](#user-content-use-safe) possible; unlike\n   with `test`/`[`, paranoid quoting to avoid empty removal is not needed.)\n\n#### String tests ####\nThe `str` function offers various operators for tests on strings. For\nexample, `str in $foo \"bar\"` tests if the variable `foo` contains \"bar\".\n\nThe `str` function takes unary (one-argument) operators that check a property\nof a single word, binary (two-argument) operators that check a word against a\npattern, as well as an option that makes binary operators check multiple words\nagainst a pattern.\n\n##### Unary string tests ####\nUsage: `str` *operator* [ *word* ]\n\nThe *word* is checked for the property indicated by *operator*; if the result\nis true, `str` returns status 0, otherwise it returns status 1.\n\nThe available unary string test *operator*s are:\n\n* `empty`: The *word* is empty.\n* `isint`: The *word* is a decimal, octal or hexadecimal integer number in\n  valid POSIX shell syntax, safe to use with `let`, `$((`...`))` and other\n  arithmetic contexts on all POSIX-derived shells. This operator ignores\n  leading (but not trailing) spaces and tabs.\n* `isvarname`: The *word* is a valid portable shell variable or function name.\n\nIf *word* is omitted, it is treated as empty, on the assumption that it is\nan unquoted empty variable. Passing more than one argument after the\n*operator* is a fatal error.\n\n##### Binary string matching tests #####\nUsage: `str` *operator* [ [ *word* ] *pattern* ]\n\nThe *word* is compared to the *pattern* according to the *operator*; if it\nmatches, `str` returns status 0, otherwise it returns status 1.\nThe available binary matching *operator*s are:\n\n* `eq`: *word* is equal to *pattern*.\n* `ne`: *word* is not equal to *pattern*.\n* `in`: *word* includes *pattern*.\n* `begin`: *word* begins with *pattern*.\n* `end`: *word* ends with *pattern*.\n* `match`: *word* matches *pattern* as a shell glob pattern\n  (as in the shell's native `case` construct).\n  A *pattern* that ends in an unescaped backslash is considered invalid\n  and causes `str` to return status 2.\n* `ematch`: *word* matches *pattern* as a POSIX\n  [extended regular expression](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04).\n  An empty *pattern* is a fatal error.\n  (In UTF-8 locales, check if\n  \u003ccode\u003ethisshellhas [WRN_EREMBYTE](#user-content-warning-ids)\u003c/code\u003e\n  before matching multi-byte characters.)\n* `lt`: *word* lexically sorts before (is 'less than') *pattern*.\n* `le`: *word* is lexically 'less than or equal to' *pattern*.\n* `gt`: *word* lexically sorts after (is 'greater than') *pattern*.\n* `ge`: *word* is lexically 'greater than or equal to' *pattern*.\n\nIf *word* is omitted, it is treated as empty on the assumption that it is an\nunquoted empty variable, and the single remaining argument is assumed to be\nthe *pattern*. Similarly, if both *word* and *pattern* are omitted, an empty\n*word* is matched against an empty *pattern*. Passing more than two\narguments after the *operator* is a fatal error.\n\n##### Multi-matching option #####\nUsage: `str -M` *operator* [ [ *word* ... ] *pattern* ]\n\nThe `-M` option causes `str` to compare any number of *word*s to the\n*pattern*. The available *operator*s are the same as the binary string\nmatching operators listed above.\n\nAll matching *word*s are stored in the `REPLY` variable, separated\nby newline characters (`$CCn`) if there is more than one match.\nIf no *word*s match, `REPLY` is unset.\n\nThe exit status returned by `str -M` is as follows:\n\n* If no *word*s match, the exit status is 1.\n* If one *word* matches, the exit status is 0.\n* If between two and 254 *word*s match, the exit status is the number of matches.\n* If 255 or more *word*s match, the exit status is 255.\n\nUsage example: the following matches a given GNU-style long-form command\nline option `$1` against a series of available options. To make it possible\nfor the options to be abbreviated, we check if any of the options begin with\nthe given argument `$1`.\n\n```sh\nif str -M begin --fee --fi --fo --fum --foo --bar --baz --quux \"$1\"; then\n\tputln \"OK. The given option $1 matched $REPLY\"\nelse\n\tcase $? in\n\t( 1 )\tputln \"No such option: $1\" \u003e\u00262 ;;\n\t( * )\tputln \"Ambiguous option: $1\" \"Did you mean:\" \"$REPLY\" \u003e\u00262 ;;\n\tesac\nfi\n```\n\n#### File type tests ####\nThese avoid the snags with symlinks you get with `[` and `[[`.\nBy default, symlinks are *not* followed. Add `-L` to operate on files\npointed to by symlinks instead of symlinks themselves (the `-L` makes\nno difference if the operands are not symlinks).\n\nThese commands all take one argument. If the argument is absent, they return\nfalse. More than one argument is a fatal error. See notes 1-3 in the\n[parent section](#user-content-string-and-file-tests).\n\n`is present` *file*: Returns true if the file is present in the file\nsystem (even if it is a broken symlink).\n\n`is -L present` *file*: Returns true if the file is present in the file\nsystem and is not a broken symlink.\n\n`is sym` *file*: Returns true if the file is a symbolic link (symlink).\n\n`is -L sym` *file*: Returns true if the file is a non-broken symlink, i.e.\na symlink that points (either directly or indirectly via other symlinks)\nto a non-symlink file that is present in the file system.\n\n`is reg` *file*: Returns true if *file* is a regular data file.\n\n`is -L reg` *file*: Returns true if *file* is either a regular data file\nor a symlink pointing (either directly or indirectly via other symlinks)\nto a regular data file.\n\nOther commands are available that work exactly like `is reg` and `is -L reg`\nbut test for other file types. To test for them, replace `reg` with one of:\n* `dir` for a directory\n* `fifo` for a named pipe (FIFO)\n* `socket` for a socket\n* `blockspecial` for a block special file\n* `charspecial` for a character special file\n\n#### File comparison tests ####\nThe following notes apply to these commands:\n* Symlinks are *not* resolved/followed by default. To operate on files pointed\n  to by symlinks, add `-L` before the operator argument, e.g. `is -L newer`.\n* Omitting any argument is a fatal error, because no empty argument (removed or\n  otherwise) would make sense for these commands.\n\n`is newer` *file1* *file2*: Compares file timestamps, returning true if *file1*\nis newer than *file2*. Also returns true if *file1* exists, but *file2* does\nnot; this is consistent for all shells (unlike `test file1 -nt file2`).\n\n`is older` *file1* *file2*: Compares file timestamps, returning true if *file1*\nis older than *file2*. Also returns true if *file1* does not exist, but *file2*\ndoes; this is consistent for all shells (unlike `test file1 -ot file2`).\n\n`is samefile` *file1* *file2*: Returns true if *file1* and *file2* are the same\nfile (hardlinks).\n\n`is onsamefs` *file1* *file2*: Returns true if *file1* and *file2* are on the\nsame file system. If any non-regular, non-directory files are specified, their\nparent directory is tested instead of the file itself.\n\n#### File status tests ####\nThese always follow symlinks.\n\n`is nonempty` *file*: Returns true if the *file* exists, is not a broken\nsymlink, and is not empty. Unlike `[ -s file ]`, this also works\nfor directories, as long as you have read permission in them.\n\n`is setuid` *file*: Returns true if the *file* has its set-user-ID flag set.\n\n`is setgid` *file*: Returns true if the *file* has its set-group-ID flag set.\n\n#### I/O tests ####\n`is onterminal` *FD*: Returns true if file descriptor *FD* is associated\nwith a terminal. The *FD* may be a non-negative integer number or one of the\nspecial identifiers `stdin`, `stdout` and `stderr` which are equivalent to\n0, 1, and 2. For instance, `is onterminal stdout` returns true if commands\nthat write to standard output (FD 1), such as `putln`, would write to the\nterminal, and false if the output is redirected to a file or pipeline.\n\n#### File permission tests ####\nAny symlinks given are resolved, as these tests would be meaningless\nfor a symlink itself.\n\n`can read` *file*: True if the file's permission bits indicate that you can read\nthe file - i.e., if an `r` bit is set and applies to your user.\n\n`can write` *file*: True if the file's permission bits indicate that you can\nwrite to the file: for non-directories, if a `w` bit is set and applies to your\nuser; for directories, both `w` and `x`.\n\n`can exec` *file*: True if the file's type and permission bits indicate that\nyou can execute the file: for regular files, if an `x` bit is set and applies\nto your user; for other file types, never.\n\n`can traverse` *file*: True if the file is a directory and its permission bits\nindicate that a path can traverse through it to reach its subdirectories: for\ndirectories, if an `x` bit is set and applies to your user; for other file\ntypes, never.\n\n\n## The stack ##\n\nIn modernish, every variable and shell option gets its own stack. Arbitrary\nvalues/states can be pushed onto the stack and popped off it in reverse\norder. For variables, both the value and the set/unset state is (re)stored.\n\nUsage:\n\n* `push` [ `--key=`*value* ] *item* [ *item* ... ]\n* `pop` [ `--keepstatus` ] [ `--key=`*value* ] *item* [ *item* ... ]\n\nwhere *item* is a valid portable variable name, a short-form shell option\n(dash plus letter), or a long-form shell option (`-o` followed by an option\nname, as two arguments).\n\nBefore pushing or popping anything, both functions check if all the given\narguments are valid and `pop` checks all items have a non-empty stack. This\nallows pushing and popping groups of items with a check for the integrity of\nthe entire group. `pop` exits with status 0 if all items were popped\nsuccessfully, and with status 1 if one or more of the given items could not\nbe popped (and no action was taken at all).\n\nThe `--key=` option is an advanced feature that can help different modules\nor functions to use the same variable stack safely. If a key is given to\n`push`, then for each *item*, the given key *value* is stored along with the\nvariable's value for that position in the stack. Subsequently, restoring\nthat value with `pop` will only succeed if the key option with the same key\nvalue is given to the `pop` invocation. Similarly, popping a keyless value\nonly succeeds if no key is given to `pop`. If there is any key mismatch, no\nchanges are made and `pop` returns status 2.  Note that this is\na robustness/convenience feature, not a security feature; the keys are not\nhidden in any way.\n\nIf the `--keepstatus` option is given, `pop` will exit with the\nexit status of the command executed immediately prior to calling `pop`. This\ncan avoid the need for awkward workarounds when restoring variables or shell\noptions at the end of a function. However, note that this makes failure to pop\n(stack empty or key mismatch) a fatal error that kills the program, as `pop`\nno longer has a way to communicate this through its exit status.\n\n### The shell options stack ###\n\n`push` and `pop` allow saving and restoring the state of any shell option\navailable to the `set` builtin. The precise shell options supported\n(other than the ones guaranteed by POSIX) depend on\n[the shell modernish is running on](#user-content-appendix-d-supported-shells).\nTo facilitate portability, nonexistent shell options are treated as unset.\n\nLong-form shell options are matched to their equivalent short-form shell\noptions, if they exist. For instance, on all POSIX shells, `-f` is\nequivalent to `-o noglob`, and `push -o noglob` followed by `pop -f` works\ncorrectly. This also works for shell-specific short \u0026 long option\nequivalents.\n\nOn shells with a dynamic `no` option name prefix, that is on ksh, zsh and\nyash (where, for example, `noglob` is the opposite of `glob`), the `no`\nprefix is ignored, so something like `push -o glob` followed by `pop -o\nnoglob` does the right thing. But this depends on the shell and should never\nbe used in portable scripts.\n\n### The trap stack ###\n\nModernish can also make traps stack-based, so that each\nprogram component or library module can set its own trap commands\nwithout interfering with others. This functionality is provided\nby the [`var/stack/trap`](#user-content-use-varstacktrap) module.\n\n\n## Modules ##\n\nAs modularity is one of modernish's\n[design principles](https://github.com/modernish/modernish/blob/master/share/doc/modernish/DESIGN.md),\nmuch of its essential functionality is provided in the form of loadable\nmodules, so the core library is kept lean. Modules are organised\nhierarchically, with names such as `safe`, `var/loop` and `sys/cmd/harden`. The\n`use` command loads and initialises a module or a combined directory of modules.\n\nInternally, modules exist in files with the name extension `.mm` in\nsubdirectories of `lib/modernish/mdl` – for example, the module\n`var/stack/trap` corresponds to the file `lib/modernish/mdl/var/stack/trap.mm`.\n\nUsage:\n* `use` *modulename* [ *argument* ... ]\n* `use` [ `-q` | `-e` ] *modulename*\n* `use -l`\n\nThe first form loads and initialises a module. All arguments, including the\nmodule name, are passed on to the dot script unmodified, so modules know\ntheir own name and can implement option parsing to influence their\ninitialisation. See also\n[Two basic forms of a modernish program](#user-content-two-basic-forms-of-a-modernish-program)\nfor information on how to use modules in portable-form scripts.\n\nIn the second form, the `-q` option queries if a module is loaded, and the `-e`\noption queries if a module exists. `use` returns status 0 for yes, 1 for no,\nand 2 if the module name is invalid.\n\nThe `-l` option lists all currently loaded modules in the order in which\nthey were originally loaded. Just add `| sort` for alphabetical order.\n\nIf a directory of modules, such as `sys/cmd` or even just `sys`, is given as the\n*modulename*, then all the modules in that directory and any subdirectories are\nloaded recursively. In this case, passing extra arguments is a fatal error.\n\nIf a module file `X.mm` exists along with a directory `X`, resolving to the\nsame *modulename*, then `use` will load the `X.mm` module file without\nautomatically loading any modules in the `X` directory, because it is expected\nthat `X.mm` handles the submodules in `X` manually. (This is currently the case\nfor `var/loop` which auto-loads submodules containing loop types on first use).\n\nThe complete `lib/modernish/mdl` directory path, which depends on where\nmodernish is installed, is stored in the system constant `$MSH_MDL`.\n\nThe following subchapters document the modules that come with modernish.\n\n### `use safe` ###\n\nThe `safe` module sets the 'safe mode' for the shell. It removes most of the\nneed to quote variables, parameter expansions, command substitutions, or glob\npatterns. It uses shell settings and modernish library functionality to secure\nand demystify split and glob mechanisms. This creates a new and safer way of\nshell script programming, essentially building a new shell language dialect\nwhile still running on all POSIX-compliant shells.\n\n#### Why the safe mode? ####\nOne of the most common headaches with shell scripting is caused by a\nfundamental flaw in the shell as a scripting language: *constantly\nactive field splitting* (a.k.a. word splitting) *and pathname expansion*\n(a.k.a. globbing). To cope with this situation, it is hammered into\nprogrammers of shell scripts to be absolutely paranoid about properly\n[quoting](https://mywiki.wooledge.org/Quotes) nearly everything, including\nvariable and parameter expansions, command substitutions, and patterns passed\nto commands like `find`.\n\nThese mechanisms were designed for interactive command line usage, where they\ndo come in very handy. But when the shell language is used as a programming\nlanguage, splitting and globbing often ends up being applied unexpectedly to\nunquoted expansions and command substitutions, helping cause thousands of\nbuggy, brittle, or outright dangerous shell scripts.\n\nOne could blame the programmer for forgetting to quote an expansion properly,\n*or* one could blame a pitfall-ridden scripting language design where hammering\npunctilious and counterintuitive habits into casual shell script programmers is\nnecessary. Modernish does the latter, then fixes it.\n\n#### How the safe mode works ####\nEvery POSIX shell comes with a little-used ability to disable global field\nsplitting and pathname expansion: `IFS=''; set -f`. An empty `IFS` variable\ndisables split; the `-f` (or `-o noglob`) shell option disables pathname\nexpansion. The safe mode sets these, and two others (see below).\n\nThe reason these safer settings are hardly ever used is that they are not\npractical to use with the standard shell language. For instance, `for\ntextfile in *.txt`, or `for item in $(some command)` which both (!)\nfield-splits *and* pathname-expands the output of a command, all break.\n\nHowever, that is where modernish comes in. It introduces several powerful\nnew [loop constructs](#user-content-use-varloop), as well as arbitrary code\nblocks with [local settings](#user-content-use-varlocal), each of which\nhas straightforward, intuitive operators for safely applying field splitting\n*or* pathname expansion – to specific command arguments only. By default,\nthey are *not both* applied to the arguments, which is much safer. And your\nscript code as a whole is kept safe from them at all times.\n\nWith global field splitting and pathname expansion removed, a third issue\nstill affects the safe mode: the shell's *empty removal* mechanism. If the\nvalue of an unquoted expansion like `$var` is empty, it will not expand to\nan empty argument, but will be removed altogether, as if it were never\nthere. This behaviour cannot be disabled.\n\nThankfully, the vast majority of shell and Un*x commands order their arguments\nin a way that is actually designed with empty removal in mind, making it a\ngood thing. For instance, when doing `ls $option some_dir`, if `$option` is\n`-l` the listing will be long-format and if is empty it will be removed, which\nis the desired behaviour. (An empty argument there would cause an error.)\n\nHowever, one command that is used in almost all shell scripts, `test`/`[`,\nis *completely unable to cope with empty removal* due to its idiosyncratic\nand counterintuitive syntax. Potentially empty operands come before options,\nso operands removed as empty expansions cause errors or, worse, false\npositives. Thus, the safe mode does *not* remove the need for paranoid\nquoting of expansions used with `test`/`[` commands. Modernish fixes\nthis issue by *deprecating `test`/`[` completely* and offering\n[a safe command design](#user-content-testing-numbers-strings-and-files)\nto use instead, which correctly deals with empty removal.\n\nWith the 'safe mode' shell settings, plus the safe, explicit and readable\nsplit and glob operators and `test`/`[` replacements, the only quoting\nrequirements left are:\n1. a very occasional need to stop empty removal from happening;\n2. to quote `\"$@\"` and `\"$*\"` until shell bugs are fixed (see notes below).\n\nIn addition to the above, the safe mode also sets these shell options:\n* `set -C` (`set -o noclobber`) to prevent accidentally overwriting files using\n  output redirection. To force overwrite, use `\u003e|` instead of `\u003e`.\n* `set -u` (`set -o nounset`) to make it an error to use unset (that is,\n  uninitialised) variables by default. You'll notice this will catch many\n  typos before they cause you hard-to-trace problems. To bypass the check\n  for a specific variable, use `${var-}` instead of `$var` (be careful).\n\n#### Important notes for safe mode ####\n* The safe mode is *not* compatible with existing conventional shell scripts,\n  written in what we could now call the 'legacy mode'. Essentially, the safe\n  mode is a new way of shell script programming. That is why it is not enabled\n  by default, but activated by loading the `safe` module. *It is highly\n  recommended that new modernish scripts start out with `use safe`.*\n* The shell applies entirely different quoting rules to string matching glob\n  patterns within `case` constructs. The safe mode changes nothing here.\n* Due to [shell bugs](#user-content-bugs) ID'ed as `BUG_PP_*`, the positional\n  parameters expansions `$@` and `$*` should still *always* be quoted. As of\n  late 2018, these bugs have been fixed in the latest or upcoming release\n  versions of all\n  [supported shells](#user-content-appendix-d-supported-shells).\n  But, until buggy versions fall out of use\n  and modernish no longer supports any `BUG_PP_*` shell bugs, quoting `\"$@\"`\n  and `\"$*\"` remains mandatory even in safe mode (unless you know with\n  certainty that your script will be used on a shell with none of these bugs).\n* The behaviour of `\"$*\"` changes in safe mode. It uses the first character\n  of `$IFS` as the separator for combining all positional parameters into\n  one string. Since `IFS` is emptied in safe mode, there is no separator,\n  so it will string them together unseparated. You can use something like\n  [`push IFS; IFS=' '; var=\"$*\"; pop IFS`](#user-content-the-stack)\n  or [`LOCAL IFS=' '; BEGIN var=\"$*\"; END`](#user-content-use-varlocal)\n  to use the space character as a separator.\n  (If you're outputting the positional parameters, note that the\n  [`put`](#user-content-outputting-strings)\n  command always separates its arguments by spaces, so you can\n  safely pass it multiple arguments with `\"$@\"` instead.)\n\n#### Extra options for the safe mode ####\nUsage: `use safe` [ `-k` | `-K` ] [ `-i` ]\n\nThe `-k` and `-K` module options install an extra handler that\n[reliably kills the program](#user-content-reliable-emergency-halt)\nif it tries to execute a command that is not found, on shells that have the\nability to catch and handle 'command not found' errors (currently bash, yash,\nand zsh). This helps catch typos, forgetting to load a module, etc., and stops\nyour program from continuing in an inconsistent state and potentially causing\ndamage. The `MSH_NOT_FOUND_OK` variable may be set to temporarily disable this\ncheck. The uppercase `-K` module option aborts the program on shells that\ncannot handle 'command not found' errors (so should not be used for portable\nscripts), whereas the lowercase `-k` variant is ignored on such shells.\n\nIf the `-i` option is given, or the shell is interactive, two extra one-letter\nfunctions are loaded, `s` and `g`. These are pre-command modifiers for use when\nsplit and glob are globally disabled; they allow running a single command with\nlocal split and glob applied to that command's arguments only. They also have\nsome options designed to manipulate, examine, save, restore, and generally\nexperiment with the global split and glob state on interactive shells. Type\n`s --help` and `g --help` for more information. In general, the safe mode is\ndesigned for scripts and is not recommended for interactive shells.\n\n### `use var/loop` ###\n\nThe `var/loop` module provides an innovative, robust and extensible\nshell loop construct. Several powerful loop types are provided, while\nadvanced shell programmers may find it easy and fun to\n[create their own](#user-content-creating-your-own-loop).\nThis construct is also ideal for the\n[safe mode](#user-content-use-safe):\nthe `for`, `select` and `find` loop types allow you to selectively\napply field splitting and/or pathname expansion to specific arguments\nwithout subjecting a single line of your code to them.\n\nThe basic form is a bit different from native shell loops. Note the caps:    \n`LOOP` *looptype* *arguments*; `DO`    \n\u0026nbsp; \u0026nbsp; \u0026nbsp; *your commands here*    \n`DONE`\n\nThe familiar `do`...`done` block syntax cannot be used because the shell\nwill not allow modernish to add its own functionality to it. The\n`DO`...`DONE` block does behave in the same way as `do`...`done`: you can\nappend redirections at the end, pipe commands into a loop, etc. as usual.\nThe `break` and `continue` shell builtin commands also work as normal.\n\n**Remember:** *using lowercase `do`...`done` with modernish `LOOP` will\ncause the shell to throw a misleading syntax error.* So will using uppercase\n`DO`...`DONE` with the shell's native loops. To help you remember to use the\nuppercase variants for modernish loops, the `LOOP` keyword itself is also in\ncapitals.\n\nLoops exist in submodules of `var/loop` named after the loop type; for\ninstance, the `find` loop lives in the `var/loop/find` module. However, the\ncore `var/loop` module will automatically load a loop type's module when\nthat loop is first used, so `use`-ing individual loop submodules at your\nscript's startup time is optional.\n\nThe `LOOP` block internally uses file descriptor 8 to do\n[its thing](#user-content-creating-your-own-loop).\nIf your script happens to use FD 8 for other purposes, you should\nknow that FD 8 is made local to each loop block, and always appears\ninitially closed within `DO`...`DONE`.\n\n#### Simple repeat loop ####\nThis simply iterates the loop the number of times indicated. Before the first\niteration, the argument is evaluated as a shell integer arithmetic expression\nas in [`let`](#user-content-integer-number-arithmetic-tests-and-operations)\nand its value used as the number of iterations.\n\n```sh\nLOOP repeat 3; DO\n\tputln \"This line is repeated 3 times.\"\nDONE\n```\n\n#### BASIC-style arithmetic `for` loop ####\nThis is a slightly enhanced version of the\n[`FOR` loop in BASIC](https://en.wikipedia.org/wiki/BASIC#Origin).\nIt is more versatile than the `repeat` loop but still very easy to use.\n\n`LOOP for` *varname*`=`*initial* to *limit* [ `step` *increment* ]; DO    \n\u0026nbsp; \u0026nbsp; \u0026nbsp; *some commands*    \n`DONE`\n\nTo count from 1 to 20 in steps of 2:\n\n```sh\nLOOP for i=1 to 20 step 2; DO\n\tputln \"$i\"\nDONE\n```\n\nNote the *varname*`=`*initial* needs to be one argument as in a shell\nassignment (so no spaces around the `=`).\n\nIf \"`step` *increment*\" is omitted, *increment* defaults to 1 if *limit* is\nequal to or greater than *initial*, or to -1 if *limit* is less than\n*initial* (so counting backwards 'just works').\n\nTechnically precise description: On entry, the *initial*, *limit* and\n*increment* values are evaluated once as shell arithmetic expressions as in\n[`let`](#user-content-integer-number-arithmetic-tests-and-operations),\nthe value of *initial* is assigned to *varname*, and the loop iterates.\nBefore every subsequent iteration, the value of *increment* (as determined\non the first iteration) is added to the value of *varname*, then the *limit*\nexpression is re-evaluated; as long as the current value of *varname* is\nless (if *increment* is non-negative) or greater (if *increment* is\nnegative) than or equal to the current value of *limit*, the loop reiterates.\n\n#### C-style arithmetic `for` loop ####\nA C-style for loop akin to `for (( ))` in ksh93, bash and zsh is now\navailable on all POSIX-compliant shells, with a slightly different syntax.\nThe one loop argument contains three arithmetic expressions (as in\n[`let`](#user-content-integer-number-arithmetic-tests-and-operations)),\nseparated by semicolons within that argument. The first is only evaluated\nbefore the first iteration, so is typically used to assign an initial value.\nThe second is evaluated before each iteration to check whether to continue\nthe loop, so it typically contains some comparison operator. The third is\nevaluated before the second and further iterations, and typically increases\nor decreases a value. For example, to count from 1 to 10:\n\n```sh\nLOOP for \"i=1; i\u003c=10; i+=1\"; DO\n\tputln \"$i\"\nDONE\n```\n\nHowever, using complex expressions allows doing much more powerful things.\nAny or all of the three expressions may also be left empty (with their\nseparating `;` character remaining). If the second expression is empty, it\ndefaults to 1, creating an infinite loop.\n\n(Note that `++i` and `i++` can only be used on shells with\n[`ARITHPP`](#user-content-appendix-a-list-of-shell-cap-ids),\nbut `i+=1` or `i=i+1` can be used on all POSIX-compliant shells.)\n\n#### Enumerative `for`/`select` loop with safe split/glob ####\nThe enumarative `for` and `select` loop types mirror those already present in\nnative shell implementations. However, the modernish versions provide safe\nfield splitting and globbing (pathname expansion) functionality that can be\nused without globally enabling split or glob for any of your code – ideal\nfor the [safe](#user-content-use-safe) mode. They also add a unique operator\nfor processing text in fixed-size slices. The `select` loop type brings\n`select` functionality to all POSIX shells and not just ksh, zsh and bash.\n\nUsage:\n\n`LOOP` [ `for` | `select` ] [ *operators* ] *varname* `in` *argument* ... `;`\n`DO` *commands* `;` `DONE`\n\nSimple usage example:\n\n```sh\nLOOP select --glob textfile in *.txt; DO\n\tputln \"You chose text file $textfile.\"\nDONE\n```\n\nIf the loop type is `for`, the loop iterates once for each *argument*, storing\nit in the variable named *varname*.\n\nIf the loop type is `select`, the loop presents before each iteration a\nnumbered menu that allows the user to select one of the *argument*s. The prompt\nfrom the `PS3` variable is displayed and a reply read from standard input. The\nliteral reply is stored in the `REPLY` variable. If the reply was a number\ncorresponding to an *argument* in the menu, that *argument* is stored in the\nvariable named *varname*. Then the loop iterates. If the user enters ^D (end of\nfile), `REPLY` is cleared and the loop breaks with an exit status of 1. (To\nbreak the menu loop under other conditions, use the `break` command.)\n\nThe following operators are supported. Note that the split and glob\noperators are only for use in the [safe mode](#user-content-use-safe).\n* One of `--split` or `--split=`*characters*. This operator safely applies\n  the shell's field splitting mechanism to the *argument*s given. The simple\n  `--split` operator applies the shell's default field splitting by space,\n  tab, and newline. If you supply one or more of your own *characters* to\n  split by, each of these characters will be taken as a field separator if\n  it is whitespace, or field terminator if it is non-whitespace. (Note that\n  shells with [`QRK_IFSFINAL`](#user-content-quirks) treat both whitespace and\n  non-whitespace characters as separators.)\n* One of `--glob` or `--fglob`. These operators safely apply shell pathname\n  expansion (globbing) to the *argument*s given. Each *argument* is taken as\n  a pattern, whether or not it contains any wildcard characters. For any\n  resulting pathname that starts with `-` or `+` or is identical to `!` or\n  `(`, `./` is prefixed to keep various commands from misparsing it as an\n  option or operand. Non-matching patterns are treated as follows:\n    * `--glob`: Any non-matching patterns are quietly removed. If none match,\n      the loop will not iterate but break with exit status 103.\n    * `--fglob`: All patterns must match. Any nonexistent path terminates the\n      program. Use this if your program would not work after a non-match.\n* `--base=`*string*. This operator prefixes the given *string* to each of the\n  *arguments*, after first applying field splitting and/or pathname expansion\n  if specified.\n  If `--glob` or `--fglob` are given, then the *string* is used as a base\n  directory path for pathname expansion, without expanding any wildcard\n  characters in that base directory path itself.\n  If such base directory can't be entered, then if `--glob` was given, the loop\n  breaks with status 98, or if `--fglob` was given, the program terminates.\n* One of `--slice` or `--slice=`*number*. This operator divides the\n  *argument*s in slices of up to *number* characters. The default slice size\n  is 1 character, allowing for easy character-by-character processing.\n  (Note that shells with [`WRN_MULTIBYTE`](#user-content-warning-ids) will\n  not slice multi-byte characters correctly.)\n\nIf multiple operators are given, their mechanisms are applied in the\nfollowing order: split, glob, base, slice.\n\n#### The `find` loop ####\nThis powerful loop type turns your local POSIX-compliant\n[`find` utility](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html)\ninto a shell loop, safely integrating both `find`\nand `xargs` functionality into the POSIX shell. The infamous\n[pitfalls and limitations](https://dwheeler.com/essays/filenames-in-shell.html#find)\nof using `find` and `xargs` as external commands are gone, as all\nthe results from `find` are readily available to your main shell\nscript. Any \"dangerous\" characters in file names (including\nwhitespace and even newlines) \"just work\", especially if the\n[safe mode](#user-content-use-safe)\nis also active. This gives you the flexibility to use either the `find`\nexpression syntax, or shell commands (including your own shell functions), or\nsome combination of both, to decide whether and how to handle each file found.\n\nUsage:\n\n`LOOP find` [ *options* ] *varname* [ `in` *path* ... ]\n[ *find-expression* ] `;` `DO` *commands* `;` `DONE`\n\n`LOOP find` [ *options* ] `--xargs`[`=`*arrayname*] [ `in` *path* ... ]\n[ *find-expression* ] `;` `DO` *commands* `;` `DONE`\n\nThe loop recursively walks down the directory tree for each *path* given.\nFor each file encountered, it uses the *find-expression* to decide\nwhether to iterate the loop with the path to the file stored in the\nvariable referenced by *varname*. The *find-expression* is a standard\n[`find`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html)\nutility expression except as described below.\n\nAny number of paths to search may be specified after the `in` keyword.\nBy default, a nonexistent path is a [fatal error](#user-content-reliable-emergency-halt).\nThe entire `in` clause may be omitted, in which case it defaults to `in .`\nso the current working directory will be searched. Any argument that starts\nwith a `-`, or is identical to `!` or `(`, indicates the end of the *path*s\nand the beginning of the *find-expression*; if you need to explicitly\nspecify a path with such a name, prefix `./` to it.\n\nExcept for syntax errors, any errors or warnings issued by `find` are\nconsidered non-fatal and will cause the exit status of the loop to be\nnon-zero, so your script has the opportunity to handle the exception.\n\n##### Available *options* #####\n* Any single-letter options supported by your local `find` utility. Note that\n  [POSIX specifies](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html)\n  `-H` and `-L` only, so portable scripts should only use these.\n  Options that require arguments (`-f` on BSD `find`) are not supported.\n* `--xargs`. This operator is specified **instead** of the *varname*; it is a\n  syntax error to have both. Instead of one iteration per found item, as many\n  items as possible per iteration are stored into the positional parameters\n  (PPs), so your program can access them in the usual way (using `\"$@\"` and\n  friends). Note that `--xargs` therefore overwrites the current PPs (however,\n  a shell function or [`LOCAL`](#user-content-use-varlocal) block will give\n  you local PPs). Modernish clears the PPs upon completion of the loop, but if\n  the loop is exited prematurely (such as by `break`), the last chunk survives.\n    * On shells with the `KSHARRAY`\n      [capability](#user-content-appendix-a-list-of-shell-cap-ids), an\n      extra variant is available: `--xargs=`*arrayname* which uses the named\n      array instead of the PPs. It otherwise works identically.\n* `--try`. If this option is specified, then if one of the primaries used in\n  the *find-expression* is not supported by either the `find` utility used by\n  the loop or by modernish itself, `LOOP find` will not throw a\n  [fatal error](#user-content-reliable-emergency-halt)\n  but will instead quietly abort the loop without iterating it, set the loop's\n  exit status to 128, and leave the invalid primary in the `REPLY` variable.\n  (Expression errors other than 'unknown primary' remain fatal errors.)\n* One of `--split` or `--split=`*characters*. This operator, which is only\n  accepted in the [safe mode](#user-content-use-safe), safely applies the\n  shell's field splitting mechanism to the *path* name(s) given *(but **not**\n  to any patterns in the *find-expression*, which are passed on to the `find`\n  utility as given)*. The simple `--split` operator applies the shell's default\n  field splitting by space, tab, and newline. Alternatively, you can supply\n  one or more *characters* to split by. If any pathname resulting from the\n  split starts with `-` or `+` or is identical to `!` or `(`, `./` is prefixed.\n* One of `--glob` or `--fglob`. These operators are only accepted in the\n  [safe mode](#user-content-use-safe). They safely apply shell pathname\n  expansion (globbing) to the *path* name(s) given *(but **not** to any\n  patterns in the *find-expression*, which are passed on to the `find` utility\n  as given)*. All *path* names are taken as patterns, whether or not they\n  contain any wildcard characters. If any pathname resulting from the\n  expansion start with `-` or `+` or is identical to `!` or `(`, `./` is\n  prefixed. Non-matching patterns are treated as follows:\n    * `--glob`: Any pattern not matching an existing path will output a\n      warning to standard error and set the loop's exit status to 103 upon\n      normal completion, even if other existing paths are processed\n      successfully. If none match, the loop will not iterate.\n    * `--fglob`: Any pattern not matching an existing path is a fatal error.\n* `--base=`*basedirectory*. This operator prefixes the given *basedirectory*\n  to each of the *path* names (and thus to each path found by `find`), after\n  first applying field splitting and/or pathname expansion if specified.\n  If `--glob` or `--fglob` are given, then wildcard characters are only\n  expanded in the *path* names and not in the prefixed *basedirectory*.\n  If the *basedirectory* can't be entered, then either the loop breaks with\n  status 98, or if `--fglob` was given, the program terminates.\n\n##### Available *find-expression* operands #####\n`LOOP find` can use all expression operands supported by your local `find`\nutility; see its manual page. However, portable scripts should use only\n[operands specified by POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html#tag_20_47_05)\nalong with the modernish additions described below.\n\nThe modernish `-iterate` expression primary evaluates as true and causes the\nloop to iterate, executing your *commands* for each matching file. It may be\nused any number of times in the *find-expression* to start a corresponding\nseries of loop iterations. If it is not given, the loop acts as if the entire\n*find-expression* is enclosed in parentheses with `-iterate` appended. If the\nentire *find-expression* is omitted, it defaults to `-iterate`.\n\nThe modernish `-ask` primary asks confirmation of the user. The text of the\nprompt may be specified in one optional argument (which cannot start with `-`\nor be equal to `!` or `(`). Any occurrences of the characters `{}` within the\nprompt text are replaced with the current pathname. If not specified, the\ndefault prompt is: `\"{}\"?` If the answer is affirmative (`y` or `Y` in the\nPOSIX locale), `-ask` yields true, otherwise false. This can be used to make\nany part of the expression conditional upon user input, and (unlike commands in\nthe shell loop body) is capable of influencing directory traversal mid-run.\n\nThe standard `-exec` and `-ok` primaries are integrated into the main shell\nenvironment. When used with `LOOP find`, they can call a shell builtin command\nor your own shell function directly in the main shell (no subshell). Its exit\nstatus is used in the `find` expression as a true/false value capable of\ninfluencing directory traversal (for example, when combined with `-prune`),\njust as if it were an external command -exec'ed with the standard utility.\n\nSome familiar, easy-to-use but non-standard `find` operands from GNU and/or\nBSD may be used with `LOOP find` on all systems. Before invoking the `find`\nutility, modernish translates them internally to portable equivalents.\nThe following expression operands are made portable:\n\n* The `-or`, `-and` and `-not` operators: same as `-o`, `-a`, `!`.\n* The `-true` and `-false` primaries, which always yield true/false.\n* The BSD-style `-depth` *n* primary, e.g. `-depth +4` yields true on depth\n  greater than 4 (minimum 5), `-depth -4` yields true on depth less than 4\n  (maximum 3), and `-depth 4` yields true on a depth of exactly 4.\n* The GNU-style `-mindepth` and `-maxdepth` global options.\n  Unlike BSD `-depth`, these GNU-isms are pseudo-primaries that\n  always yield true and affect the entire `LOOP find` operation.\n\nExpression primaries that write output (`-print` and friends) may be used for\ndebugging or logging the loop. Their output is redirected to standard error.\n\n##### Picking a `find` utility #####\nUpon initialisation, the `var/loop/find` module searches for a POSIX-compliant\n`find` utility under various names in `$DEFPATH` and then in `$PATH`. To see a\ntrace of the full command lines of utility invocations when the loop runs, set\nthe `_loop_DEBUG` variable to any value.\n\nFor debugging or system-specific usage, it is possible to use a certain `find`\nutility in preference to any others on the system. To do this, add an argument\nto a `use var/loop/find` command before the first use of the loop. For example:\n\n* `use var/loop/find bsdfind` (prefer utility by this name)\n* `use var/loop/find /opt/local/bin` (look for a utility here first)\n* `use var/loop/find /opt/local/bin/gfind`  (try this one first)\n\n##### Compatibility mode for obsolete `find` utilities #####\nSome systems come with obsolete or broken `find` utilities that don't fully\nsupport `-exec ... {} +` aggregating functionality as specified by POSIX.\nNormally, this is a fatal error, but passing the `-b`/`-B` option to the\n`use` command, e.g. `use var/loop/find -b`, enables a compatibility mode\nthat tolerates this defect. If no compliant `find` is found, then an obsolete\nor broken `find` is used as a last resort, a warning is printed to standard\nerror, and the variable `_loop_find_broken` is set. The `-B` option is\nequivalent to `-b` but does not print a warning. Loop performance may suffer as\nmodernish adapts to using older `exec ... {} \\;` which is very inefficient.\n\nScripts using this compatibility mode should handle their logic using shell\ncode in the loop body as much as possible (after `DO`) and use only simple\n`find` expressions (before `DO`), as obsolete utilities are often buggy and\nbreakage is likely if complex expressions or advanced features are used.\n\n##### `find` loop usage examples #####\nSimple example script: without the safe mode, the `*.txt` pattern\nmust be quoted to prevent it from being expanded by the shell.\n\n```sh\n. modernish\nuse var/loop\nLOOP find TextFile in ~/Documents -name '*.txt'\nDO\n\tputln \"Found my text file: $TextFile\"\nDONE\n```\n\nExample script with [safe mode](#user-content-use-safe): the `--glob` option\nexpands the patterns of the `in` clause, but *not* the expression – so it\nis not necessary to quote any pattern.\n\n```sh\n. modernish\nuse safe\nuse var/loop\nLOOP find --glob lsProg in /*bin /*/*bin -type f -name ls*\nDO\n\tputln \"This command may list something: $lsProg\"\nDONE\n```\n\nExample use of the modernish `-ask` primary: ask the user if they want to\ndescend into each directory found. The shell loop body could skip unwanted\nresults, but cannot physically influence directory traversal, so skipping large\ndirectories would take long. A `find` expression can prevent directory\ntraversal using the standard `-prune` primary, which can be combined with\n`-ask`, so that unwanted directories never iterate the loop in the first place.\n\n```sh\n. modernish\nuse safe\nuse var/loop\nLOOP find file in ~/Documents \\\n\t-type d \\( -ask 'Descend into \"{}\" directory?' -or -prune \\) \\\n\t-or -iterate\nDO\n\tput \"File found: \"\n\tls -li $file\nDONE\n```\n\n#### Creating your own loop ####\nThe modernish loop construct is extensible. To define a new loop type, you\nonly need to define a shell function called `_loopgen_`*type* where *type*\nis the loop type. This function, called the *loop iteration generator*, is\nexpected to output lines of text to file descriptor 8, containing properly\n[shell-quoted](#user-content-use-varshellquote)\niteration commands for the shell to run, one line per iteration.\n\nThe internal commands expanded from `LOOP`, `DO` and `DONE` (which are\ndefined as aliases) launch that loop iteration generator function in the\nbackground with [safe](#user-content-use-safe) mode enabled, while causing\nthe main shell to read lines from that background process through a pipe,\n`eval`ing each line as a command before iterating the loop. As long as that\niteration command finishes with an exit status of zero, the loop keeps\niterating. If it has a nonzero exit status or if there are no more commands\nto read, iteration terminates and execution continues beyond the loop.\n\nInstead of the normal [internal namespace](#user-content-internal-namespace)\nwhich is considered off-limits for modernish scripts, `var/loop` and its\nsubmodules use a `_loop_*` internal namespace for variables, which is also\nfor use by user-implemented loop iteration generator functions.\n\nThe above is just the general principle. For the details, study the comments\nand the code in `lib/modernish/mdl/var/loop.mm` and the loop generators in\n`lib/modernish/mdl/var/loop/*.mm`.\n\n### `use var/local` ###\n\nThis module defines a new `LOCAL`...`BEGIN`...`END` shell code block\nconstruct with local variables, local positional parameters and local shell\noptions. The local positional parameters can be filled using safe field\nsplitting and pathname expansion operators similar to those in the `LOOP`\nconstruct described [above](#user-content-use-varloop).\n\nUsage: `LOCAL` [ *localitem* | *operator* ... ] [ `--` [ *word* ... ] ] `;`\n`BEGIN` *commands* `;` `END`\n\nThe *commands* are executed once, with the specified *localitem*s applied.\nEach *localitem* can be:\n* A variable name with or without a `=` immediately followed by a value.\n  This renders that variable local to the block, initially either unsetting\n  it or assigning the value, which may be empty.\n* A shell option letter immediately preceded by a `-` or `+` sign. This\n  locally turns that shell option on or off, respectively. This follows the\n  counterintuitive syntax of `set`. Long-form shell options like `-o`\n  *optionname* and `+o` *optionname* are also supported. It depends on the\n  shell what options are supported. Specifying a nonexistent option is a\n  fatal error. Use [`thisshellhas`](#user-content-shell-capability-detection) to check\n  for a non-POSIX option's existence on the current shell before using it.\n\nModernish implements `LOCAL` blocks as one-time shell functions that use\n[the stack](#user-content-the-stack)\nto save and restore variables and settings. So the `return` command exits the\nblock, causing the global variables and settings to be restored and resuming\nexecution at the point immediately following `END`. Like any shell function, a\n`LOCAL` block exits with the exit status of the last command executed within\nit, or with the status passed on by or given as an argument to `return`.\n\nThe positional parameters (`$@`, `$1`, etc.) are always local to the block, but\na copy is inherited from outside the block by default. Any changes to the\npositional parameters made within the block will be discarded upon exiting it.\n\nHowever, if a double-dash `--` argument is given in the `LOCAL` command line,\nthe positional parameters outside the block are ignored and the set of *word*s\nafter `--` (which may be empty) becomes the positional parameters instead.\n\nThese *word*s can be modified prior to entering the `LOCAL` block using the\nfollowing *operator*s. The safe glob and split operators are only accepted in\nthe [safe mode](#user-content-use-safe). The operators are:\n\n* One of `--split` or `--split=`*characters*. This operator safely applies\n  the shell's field splitting mechanism to the *word*s given. The simple\n  `--split` operator applies the shell's default field splitting by space,\n  tab, and newline. If you supply one or more of your own *characters* to\n  split by, each of these characters will be taken as a field separator if\n  it is whitespace, or field terminator if it is non-whitespace. (Note that\n  shells with [`QRK_IFSFINAL`](#user-content-quirks) treat both whitespace and\n  non-whitespace characters as separators.)\n* One of `--glob` or `--fglob`. These operators safely apply shell pathname\n  expansion (globbing) to the *word*s given. Each *word* is taken as a pattern,\n  whether or not it contains any wildcard characters. For any resulting\n  pathname that starts with `-` or `+` or is identical to `!` or `(`, `./`\n  is prefixed to keep various commands from misparsing it as an option\n  or operand. Non-matching patterns are treated as follows:\n    * `--glob`: Any non-matching patterns are quietly removed.\n    * `--fglob`: All patterns must match. Any nonexistent path terminates the\n      program. Use this if your program would not work after a non-match.\n* `--base=`*string*. This operator prefixes the given *string* to each of the\n  *word*s, after first applying field splitting and/or pathname expansion\n  if specified.\n  If `--glob` or `--fglob` are given, then the *string* is used as a base\n  directory path for pathname expansion, without expanding any wildcard\n  characters in that base directory path itself.\n  If such base directory can't be entered, then if `--glob` was given, all\n  *word*s are removed, or if `--fglob` was given, the program terminates.\n* One of `--slice` or `--slice=`*number*. This operator divides the\n  *word*s in slices of up to *number* characters. The default slice size\n  is 1 character, allowing for easy character-by-character processing.\n  (Note that shells with [`WRN_MULTIBYTE`](#user-content-warning-ids) will\n  not slice multi-byte characters correctly.)\n\nIf multiple operators are given, their mechanisms are applied in the\nfollowing order: split, glob, base, slice.\n\n#### Important `var/local` usage notes ####\n* Due to the limitations of aliases and shell reserved words, `LOCAL` has\n  to use its own `BEGIN`...`END` block instead of the shell's `do`...`done`.\n  Using the latter results in a misleading shell syntax error.\n* `LOCAL` blocks do **not** mix well with use of the shell capability\n  [`LOCALVARS`](#user-content-user-content-capabilities)\n  (shell-native functionality for local variables), especially not on shells\n  with `QRK_LOCALUNS` or `QRK_LOCALUNS2`. Using both with the same variables\n  causes unpredictable behaviour, depending on the shell.\n* **Warning!** Never use `break` or `continue` within a `LOCAL` block to\n  resume or break from enclosing loops outside the block! Shells with\n  [`QRK_BCDANGER`](#user-content-quirks) allow this, preventing `END` from\n  restoring the global settings and corrupting the stack; shells without\n  this quirk will throw an error if you try this. A proper way to do what\n  you want is to exit the block with a nonzero status using something like\n  `return 1`, then append something like `|| break` or `|| continue` to\n  `END`. Note that this caveat only applies when crossing `BEGIN`...`END`\n  boundaries. Using `continue` and `break` to continue or break loops\n  entirely *within* the block is fine.\n\n### `use var/arith` ###\n\nThese shortcut functions are alternatives for using\n[`let`](#user-content-the-arithmetic-command-let).\n\n#### Arithmetic operator shortcuts ####\n`inc`, `dec`, `mult`, `div`, `mod`: simple integer arithmetic shortcuts. The first\nargument is a variable name. The optional second argument is an\narithmetic expression, but a sane default value is assumed (1 for inc\nand dec, 2 for mult and div, 256 for mod). For instance, `inc X` is\nequivalent to `X=$((X+1))` and `mult X Y-2` is equivalent to `X=$((X*(Y-2)))`.\n\n`ndiv` is like `div` but with correct rounding down for negative numbers.\nStandard shell integer division simply chops off any digits after the\ndecimal point, which has the effect of rounding down for positive numbers\nand rounding up for negative numbers. `ndiv` consistently rounds down.\n\n#### Arithmetic comparison shortcuts ####\nThese have the same name as their `test`/`[` option equivalents. Unlike\nwith `test`, the arguments are shell integer arith expressions, which can be\nanything from simple numbers to complex expressions. As with `$(( ))`,\nvariable names are expanded to their values even without the `$`.\n\n    Function:         Returns successfully if:\n    eq \u003cexpr\u003e \u003cexpr\u003e  the two expressions evaluate to the same number\n    ne \u003cexpr\u003e \u003cexpr\u003e  the two expressions evaluate to different numbers\n    lt \u003cexpr\u003e \u003cexpr\u003e  the 1st expr evaluates to a smaller number than the 2nd\n    le \u003cexpr\u003e \u003cexpr\u003e  the 1st expr eval's to smaller than or equal to the 2nd\n    gt \u003cexpr\u003e \u003cexpr\u003e  the 1st expr evaluates to a greater number than the 2nd\n    ge \u003cexpr\u003e \u003cexpr\u003e  the 1st expr eval's to greater than or equal to the 2nd\n\n### `use var/assign` ###\n\nThis module is provided to solve a common POSIX shell language annoyance: in a\nnormal shell variable assignment, only literal variable names are accepted, so\nit is impossible to use a variable whose name is stored in another variable.\nThe only way around this is to use `eval` which is too difficult to use safely.\nInstead, you can now use the `assign` command.\n\nUsage: `assign` [ [ `+r` ] *variable*`=`*value* ... ] | [ `-r` *variable*`=`*variable2* ... ] ...\n\n`assign` safely processes assignment-arguments in the same form as customarily\ngiven to the `readonly` and `export` commands, but it only assigns *value*s to\n*variable*s without setting any attributes. Each argument is grammatically an\nordinary shell word, so any part or all of it may result from an expansion. The\nabsence of a `=` character in any argument is a fatal error. The text preceding\nthe first `=` is taken as the variable name in which to store the *value*; an\ninvalid *variable* name is a fatal error. No whitespace is accepted before the\n`=` and any whitespace after the `=` is part of the *value* to be assigned.\n\nThe `-r` (reference) option causes the part to the right of the `=` to be\ntaken as a second variable name *variable2*, and its value is assigned to\n*variable* instead. `+r` turns this option back off.\n\n**Examples:** Each of the lines below assigns the value 'hello world' to the\nvariable `greeting`.\n\n```sh\nvar=greeting; assign $var='hello world'\nvar=greeting; assign \"$var=hello world\"\ntag='greeting=hello world'; assign \"$tag\"\nvar=greeting; gvar=myinput; myinput='hello world'; assign -r $var=$gvar\n```\n\n### `use var/readf` ###\n\n`readf` reads arbitrary data from standard input into a variable until end\nof file, converting it into a format suitable for passing to the\n[`printf`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html)\nutility. For example, `readf var \u003cfoo; printf \"$var\" \u003ebar` will copy foo to\nbar. Thus, `readf` allows storing both text and binary files into shell\nvariables in a textual format suitable for manipulation with standard shell\nfacilities.\n\nAll non-printable, non-ASCII characters are converted to `printf` octal or\none-letter escape codes, except newlines. Not encoding newline characters\nallows for better processing by line-based utilities such as `grep`, `sed`,\n`awk`, etc. However, if the file ends in a newline, that final newline is\nencoded to `\\n` to protect it from being stripped by command substitutions.\n\nUsage: `readf` [ `-h` ] *varname*\n\nThe `-h` option disables conversion of high-byte characters (accented letters,\nnon-Latin scripts). Do not use for binary files; this is only guaranteed to\nwork for text files in an encoding compatible with the current locale.\n\nCaveats:\n* Best for small-ish files. The encoded file is stored in memory (a shell\n  variable). For a binary file, encoding in `printf` format typically\n  about doubles the size, though it could be up to four times as large.\n* If the shell executing your program does not have `printf` as a builtin\n  command, the external `printf` command will fail if the encoded file\n  size exceeds the maximum length of arguments to external commands\n  (`getconf ARG_MAX` will obtain this limit for your system). Shell builtin\n  commands do not have this limit. Check for a `printf` builtin using\n  [`thisshellhas`](#user-content-shell-capability-detection) if you need to be sure,\n  and always [`harden`](#user-content-use-syscmdharden)\n  `printf`!\n\n### `use var/shellquote` ###\n\nThis module provides an efficient, fast, safe and portable shell-quoting\nalgorithm for quoting arbitrary data in such a way that the quoted values are\nsafe to pass to the shell for parsing as string literals. This is essential\nfor any context where the shell must grammatically parse untrusted input,\nsuch as when supplying arbitrary values to `trap` or `eval`.\n\nThe shell-quoting algorithm is optimised to minimise exponential growth when\nquoting repeatedly. By default, it also ensures that quoted strings are\nalways one single printable line, making them safe for terminal output and\nprocessing by line-oriented utilities.\n\n#### `shellquote` ####\nUsage: `shellquote` [ `-f`|`+f`|`-P`|`+P` ] *varname*[`=`*value*] ...\n\nThe values of the variables specified by name are shell-quoted and stored\nback into those variables.\nRepeating a variable name will add another level of shell-quoting.\nIf a `=` plus a *value* (which may be empty) is appended to the *varname*,\nthat value is shell-quoted and assigned to the variable.\n\nOptions modify the algorithm for variable names following them, as follows:\n\n* By default, newlines and any control characters are converted into\n  [`${CC*}`](#user-content-control-character-whitespace-and-shell-safe-character-constants)\n  expansions and quoted with double quotes, ensuring that the quoted string\n  consists of a single line of printable text. The `-P` option forces pure\n  POSIX quoted strings that may span multiple lines; `+P` turns this back off.\n\n* By default, a value is only quoted if it contains characters not present\n  in `$SHELLSAFECHARS`. The `-f` option forces unconditional quoting,\n  disabling optimisations that may leave shell-safe characters unquoted;\n  `+f` turns this back off.\n\n`shellquote` will [die](#user-content-reliable-emergency-halt) if you\nattempt to quote an unset variable (because there is no value to quote).\n\n#### `shellquoteparams` ####\nThe `shellquoteparams` command shell-quotes the current positional\nparameters in place using the default quoting method of `shellquote`. No\noptions are supported and any attempt to add arguments results in a syntax\nerror.\n\n### `use var/stack` ###\n\nModules that extend [the stack](#user-content-the-stack).\n\n#### `use var/stack/extra` ####\nThis module contains stack query and maintenance functions.\n\nIf you only need one or two of these functions, they can also be loaded as\nindividual submodules of `var/stack/extra`.\n\nFor the four functions below, *item* can be:\n* a valid portable variable name\n* a short-form shell option: dash plus letter\n* a long-form shell option: `-o` followed by an option name (two arguments)\n* `--trap=`*SIGNAME* to refer to the trap stack for the indicated signal\n  (as set by `pushtrap` from [`var/stack/trap`](#user-content-use-varstacktrap))\n\n`stackempty` [ `--key=`*value* ] [ `--force` ] *item*: Tests if the stack\nfor an item is empty. Returns status 0 if it is, 1 if it is not. The key\nfeature works as in [`pop`](#user-content-the-stack): by default, a key\nmismatch is considered equivalent to an empty stack. If `--force` is given,\nthis function ignores keys altogether.\n\n`clearstack` [ `--key=`*value* ] [ `--force` ] *item* [ *item* ... ]:\nClears one or more stacks, discarding all items on it.\nIf (part of) the stack is keyed or a `--key` is given, only clears until a\nkey mismatch is encountered. The `--force` option overrides this and always\nclears the entire stack (be careful, e.g. don't use within\n[`LOCAL` ... `BEGIN` ... `END`](#user-content-use-varlocal)).\nReturns status 0 on success, 1 if that stack was already empty, 2 if\nthere was nothing to clear due to a key mismatch.\n\n`stacksize` [ `--silent` | `--quiet` ] *item*: Leaves the size of a stack in\nthe `REPLY` variable and, if option `--silent` or `--quiet` is not given,\nwrites it to standard output.\nThe size of the complete stack is returned, even if some values are keyed.\n\n`printstack` [ `--quote` ] *item*: Outputs a stack's content.\nOption `--quote` shell-quotes each stack value before printing it, allowing\nfor parsing multi-line or otherwise complicated values.\nColumn 1 to 7 of the output contain the number of the item (down to 0).\nIf the item is set, column 8 and 9 contain a colon and a space, and\nif the value is non-empty or quoted, column 10 and up contain the value.\nSets of values that were pushed with a key are started with a special\nline containing `--- key: `*value*. A subsequent set pushed with no key is\nstarted with a line containing `--- (key off)`.\nReturns status 0 on success, 1 if that stack is empty.\n\n#### `use var/stack/trap` ####\nThis module provides `pushtrap` and `poptrap`. These functions integrate\nwith the [main modernish stack](#user-content-the-stack)\nto make traps stack-based, so that each\nprogram component or library module can set its own trap commands without\ninterfering with others.\n\nThis module also provides a new\n[`DIE` pseudosignal](#user-content-the-new-die-pseudosignal)\nthat allows pushing traps to execute when\n[`die`](#user-content-reliable-emergency-halt)\nis called.\n\nNote an important difference between the trap stack and stacks for variables\nand shell options: pushing traps does not save them for restoring later, but\nadds them alongside other traps on the same signal. All pushed traps are\nactive at the same time and are executed from last-pushed to first-pushed\nwhen the respective signal is triggered. Traps cannot be pushed and popped\nusing `push` and `pop` but use dedicated commands as follows.\n\nUsage:\n\n* `pushtrap` [ `--key=`*value* ] [ `--nosubshell` ] [ `--` ] *command* *sigspec* [ *sigspec* ... ]\n* `poptrap` [ `--key=`*value* ] [ `-R` ] [ `--` ] *sigspec* [ *sigspec* ... ]\n\n`pushtrap` works like regular `trap`, with the following exceptions:\n\n* Adds traps for a signal without overwriting previous ones.\n* An invalid signal is a fatal error. When using non-standard signals, check if\n  [`thisshellhas --sig=`*yoursignal*](#user-content-shell-capability-detection)\n  before using it.\n* Unlike regular traps, a stack-based trap does not cause a signal to be\n  ignored. Setting one will cause it to be executed upon the shell receiving\n  that signal, but after the stack traps complete execution, modernish re-sends\n  the signal to the main shell, causing it to behave as if no trap were set\n  (unless a regular POSIX trap is also active).\n  Thus, `pushtrap` does not accept an empty *command* as it would be pointless.\n* Each stack trap is executed in a new\n  [subshell](#user-content-insubshell)\n  to keep it from interfering\n  with others. This means a stack trap cannot change variables except within\n  its own environment, and `exit` will only exit the trap and not the program.\n  The `--nosubshell` option overrides this behaviour, causing that particular\n  trap to be executed in the main shell environment instead. This is not\n  recommended if not absolutely needed, as you have to be extra careful to\n  avoid exiting the shell or otherwise interfere with other stack traps.\n  This option cannot be used with\n  [`DIE` traps](#user-content-the-new-die-pseudosignal).\n* Each stack trap is executed with `$?` initially set to the exit status\n  that was active at the time the signal was triggered.\n* Stack traps do not have access to the positional parameters.\n* `pushtrap` stores current `$IFS` (field splitting) and `$-` (shell options)\n  along with the pushed trap. Within the subshell executing each stack trap,\n  modernish restores `IFS` and the shell options `f` (`noglob`), `u`\n  (`nounset`) and `C` (`noclobber`) to the values in effect during the\n  corresponding `pushtrap`. This is to avoid unexpected effects in case a trap\n  is triggered while temporary settings are in effect.\n  The `--nosubshell` option disables this functionality for the trap pushed.\n* The `--key` option applies the keying functionality inherited from\n  [plain `push`](#user-content-the-stack) to the trap stack.\n  It works the same way, so the description is not repeated here.\n\n`poptrap` takes just signal names or numbers as arguments. It takes the\nlast-pushed trap for each signal off the stack. By default, it discards\nthe trap commands. If the `-R` option is given, it stores commands to\nrestore those traps into the `REPLY` variable, in a format suitable for\nre-entry into the shell. Again, the `--key` option works as in\n[plain `pop`](#user-content-the-stack).\n\nWith the sole exception of\n[`DIE` traps](#user-content-the-new-die-pseudosignal),\nall stack-based traps, like native shell traps, are reset upon entering a\n[subshell](#user-content-insubshell).\nHowever, commands for printing traps will print the traps for\nthe parent shell, until another `trap`, `pushtrap` or `poptrap` command is\ninvoked, at which point all memory of the parent shell's traps is erased.\n\n##### Trap stack compatibility considerations #####\nModernish tries hard to avoid incompatibilities with existing trap practice.\nTo that end, it intercepts the regular POSIX `trap` command using an alias,\nreimplementing and interfacing it with the shell's builtin trap facility\nso that plain old regular traps play nicely with the trap stack. You should\nnot notice any changes in the POSIX `trap` command's behaviour, except for\nthe following:\n\n* The regular `trap` command does not overwrite stack traps (but still\n  overwrites existing regular traps).\n* Unlike zsh's native trap command, signal names are case insensitive.\n* Unlike dash's native trap command, signal names may have the `SIG` prefix;\n  that prefix is quietly accepted and discarded.\n* Setting an empty trap action to ignore a signal only works fully (passing\n  the ignoring on to child processes) if there are no stack traps associated\n  with the signal; otherwise, an empty trap action merely suppresses the\n  signal's default action for the current process – e.g., after executing\n  the stack traps, it keeps the shell from exiting.\n* The `trap` command with no arguments, which prints the traps that are set\n  in a format suitable for re-entry into the shell, now also prints the\n  stack traps as `pushtrap` commands. (`bash` users might notice the `SIG`\n  prefix is not included in the signal names written.)\n* The bash/yash-style `-p` option, including its yash-style `--print`\n  equivalent, is now supported on all shells. If further arguments are\n  given after that option, they are taken as signal specifications and\n  only the commands to recreate the traps for those signals are printed.\n* Saving the traps to a variable using command substitution (as in:\n  `var=$(trap)`) now works on every\n  [shell supported by modernish](#user-content-appendix-d-supported-shells),\n  including (d)ash, mksh and zsh which don't support this natively.\n* To reset (unset) a trap, the modernish `trap` command accepts both\n  [valid POSIX syntax](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_28_03)\n  and legacy bash/(d)ash/zsh syntax, like `trap INT` to unset a `SIGINT`\n  trap (which only works if the `trap` command is given exactly one\n  argument). Note that this is for compatibility with existing scripts only.\n* Bypassing the `trap` alias to set a trap using the shell builtin command\n  will cause an inconsistent state. This may be repaired with a simple `trap`\n  command; as modernish prints the traps, it will quietly detect ones it\n  doesn't yet know about and make them work nicely with the trap stack.\n\nPOSIX traps for each signal are always executed after that signal's stack-based\ntraps; this means they should not rely on modernish modules that use the trap\nstack to clean up after themselves on exit, as those cleanups would already\nhave been done.\n\n##### The new `DIE` pseudosignal #####\nThe `var/stack/trap` module adds new `DIE` pseudosignal whose traps are\nexecuted upon invoking [`die`](#user-content-reliable-emergency-halt).\nThis allows for emergency cleanup operations upon fatal program failure,\nas `EXIT` traps cannot be executed after `die` is invoked.\n\n* On non-interactive shells (as well as\n  [subshells](#user-content-insubshell)\n  of interactive shells), `DIE` is its own pseudosignal with its own trap\n  stack and POSIX trap. In order to kill the malfunctioning program as quickly\n  as possible (hopefully before it has a chance to delete all your data), `die`\n  doesn't wait for those traps to complete before killing the program. Instead,\n  it executes each `DIE` trap simultaneously as a background job, then gathers\n  the process IDs of the main shell and all its subprocesses, sending `SIGKILL`\n  to all of them except any `DIE` trap processes. Unlike other traps, `DIE`\n  traps are inherited by and survive in subshell processes, and `pushtrap` may\n  add to them within the subshell. Whatever shell process invokes `die` will\n  fork all `DIE` trap actions before being `SIGKILL`ed itself. (Note that any\n  `DIE` traps pushed or set within a subshell will still be forgotten upon\n  exiting the subshell.)\n* On an interactive shell (*not* including its\n  [subshells](#user-content-insubshell)),\n  `DIE` is simply an alias for `INT`, and `INT` traps\n  (both POSIX and stack) are cleared out after executing them once. This is\n  because `die` uses `SIGINT` for command interruption on interactive shells, and\n  it would not make sense to execute emergency cleanup commands repeatedly. As\n  a side effect of this special handling, `INT` traps on interactive shells do\n  not have access to the positional parameters and cannot return from functions.\n\n### `use var/string` ###\n\nString comparison and manipulation functions.\n\n#### `use var/string/touplow` ####\n`toupper` and `tolower`: convert case in variables.\n\nUsage:\n* `toupper` *varname* [ *varname* ... ]\n* `tolower` *varname* [ *varname* ... ]\n\nArguments are taken as variable names (note: they should be given without\nthe `$`) and case is converted in the contents of the specified variables,\nwithout reading input or writing output.\n\n`toupper` and `tolower` try hard to use the fastest available method on the\nparticular shell your program is running on. They use built-in shell\nfunctionality where available and working correctly, otherwise they fall back\non running an external utility.\n\nWhich external utility is chosen depends on whether the current locale uses\nthe Unicode UTF-8 character set or not. For non-UTF-8 locales, modernish\nassumes the POSIX/C locale and `tr` is always used. For UTF-8 locales,\nmodernish tries hard to find a way to correctly convert case even for\nnon-Latin alphabets. A few shells have this functionality built in with\n`typeset`. The rest need an external utility. Modernish initialisation\ntries `tr`, `awk`, GNU `awk` and GNU `sed` before giving up and setting\nthe variable `MSH_2UP2LOW_NOUTF8`. If `isset MSH_2UP2LOW_NOUTF8`, it\nmeans modernish is in a UTF-8 locale but has not found a way to convert\ncase for non-ASCII characters, so `toupper` and `tolower` will convert\nonly ASCII characters and leave any other characters in the string alone.\n\n#### `use var/string/trim` ####\n`trim`: strip whitespace from the beginning and end of a variable's value.\nWhitespace is defined by the `[:space:]` character class. In the POSIX\nlocale, this is tab, newline, vertical tab, form feed, carriage return, and\nspace, but in other locales it may be different.\n(On shells with [`BUG_NOCHCLASS`](#user-content-bugs),\n[`$WHITESPACE`](#user-content-control-character-whitespace-and-shell-safe-character-constants)\nis used to define whitespace instead.) Optionally, a string of literal\ncharacters can be provided in the second argument. Any characters appearing\nin that string will then be trimmed instead of whitespace.\nUsage: `trim` *varname* [ *characters* ]\n\n#### `use var/string/replacein` ####\n`replacein`: Replace leading, `-t`railing or `-a`ll occurrences of a string by\nanother string in a variable.    \nUsage: `replacein` [ `-t` | `-a` ] *varname* *oldstring* *newstring*\n\n#### `use var/string/append` ####\n`append` and `prepend`: Append or prepend zero or more strings to a\nvariable, separated by a string of zero or more characters, avoiding the\nhairy problem of dangling separators.\nUsage: `append`|`prepend` [ `--sep=`*separator* ] [ `-Q` ] *varname* [ *string* ... ]    \nIf the separator is not specified, it defaults to a space character.\nIf the `-Q` option is given, each *string* is\n[shell-quoted](#user-content-use-varshellquote)\nbefore appending or prepending.\n\n### `use var/unexport` ###\n\nThe `unexport` function clears the \"export\" bit of a variable, conserving\nits value, and/or assigns values to variables without setting the export\nbit. This works even if `set -a` (allexport) is active, allowing an \"export\nall variables, except these\" way of working.\n\nUsage is like `export`, with the caveat that variable assignment arguments\ncontaining non-shell-safe characters or expansions must be quoted as\nappropriate, unlike in some specific shell implementations of `export`.\n(To get rid of that headache, [`use safe`](#user-content-use-safe).)\n\nUnlike `export`, `unexport` does not work for read-only variables.\n\n### `use var/genoptparser` ###\n\nAs the `getopts` builtin is not portable when used in functions, this module\nprovides a command that generates modernish code to parse options for your\nshell function in a standards-compliant manner. The generated parser\nsupports short-form (one-character) options which can be stacked/combined.\n\nUsage:\n`generateoptionparser` [ `-o` ] [ `-f` *func* ] [ `-v` *varprefix* ]\n[ `-n` *options* ] [ -a *options* ] [ *varname* ]\n\n* `-o`: Write parser to standard output.\n* `-f`: Function name to prefix to error messages. Default: none.\n* `-v`: Variable name prefix for options. Default: `opt_`.\n* `-n`: String of options that do not take arguments.\n* `-a`: String of options that require arguments.\n* *varname*: Store parser in specified variable. Default: `REPLY`.\n\nAt least one of `-n` and `-a` is required. All other arguments are optional.\nOption characters must be valid components of portable variable names, so\nthey must be ASCII upper- or lowercase letters, digits, or the underscore.\n\n`generateoptionparser` stores the generated parser code in a variable: either\n`REPLY` or the *varname* specified as the first non-option argument. This makes\nit possible to generate and use the parser on the fly with a command like\n`eval \"$REPLY\"` immediately following the `generateoptionparser` invocation.\n\nFor better efficiency and readability, it will often be preferable to insert\nthe option parser code directly into your shell function instead. The `-o`\noption writes the parser code to standard output, so it can be redirected to\na file, inserted into your editor, etc.\n\nParsed options are shifted out of the positional parameters while setting or\nunsetting corresponding variables, until a non-option argument, a `--`\nend-of-options delimiter argument, or the end of arguments is encountered.\nUnlike with `getopts`, no additional `shift` command is required.\n\nEach specified option gets a corresponding variable with a name consisting\nof the *varprefix* (default: `opt_`) plus the option character. If an option\nis not passed to your function, the parser unsets its variable; otherwise it\nsets it to either the empty value or its option-argument if it requires one.\nThus, your function can check if any option `x` was given using\n[`isset`](#user-content-isset),\nfor example, `if isset opt_x; then`...\n\n### `use sys/base` ###\n\nSome very common and essential utilities are not specified by POSIX, differ\nwidely among systems, and are not always available. For instance, the\n`which` and `readlink` commands have incompatible options on various GNU and\nBSD variants and may be absent on other Unix-like systems. The `sys/base`\nmodule provides a complete re-implementation of such non-standard but basic\nutilities, written as modernish shell functions. Using the modernish version\nof these utilities can help a script to be fully portable. These versions\nalso have various enhancements over the GNU and BSD originals, some of which\nare made possible by their integration into the modernish shell environment.\n\n#### `use sys/base/mktemp` ####\nA cross-platform shell implementation of `mktemp` that aims to be just as\nsafe as native `mktemp`(1) implementations, while avoiding the problem of\nhaving various mutually incompatible versions and adding several unique\nfeatures of its own.\n\nCreates one or more unique temporary files, directories or named pipes,\natomically (i.e. avoiding race conditions) and with safe permissions.\nThe path name(s) are stored in `REPLY` and optionally written to stdout.\n\nUsage: `mktemp` [ `-dFsQCt` ] [ *template* ... ]\n\n* `-d`: Create a directory instead of a regular file.\n* `-F`: Create a FIFO (named pipe) instead of a regular file.\n* `-s`: Silent. Store output in `$REPLY`, don't write any output or message.\n* `-Q`: Shell-quote each unit of output. Separate by spaces, not newlines.\n* `-C`: Automated cleanup.\n        [Pushes a trap](#user-content-the-trap-stack)\n        to remove the files\n        on exit. On an interactive shell, that's all this option does. On a\n        non-interactive shell, the following applies: Clean up on receiving\n        `SIGPIPE` and `SIGTERM` as well. On receiving `SIGINT`, clean up if the\n        option was given at least twice, otherwise notify the user of files\n        left. On the invocation of\n        [`die`](#user-content-reliable-emergency-halt),\n        clean up if the option was given at least three times, otherwise notify\n        the user of files left.\n* `-t`: Prefix one temporary files directory to all the *template*s:\n        `$XDG_RUNTIME_DIR` or `$TMPDIR` if set, or `/tmp`. The *template*s\n        may not contain any slashes. If the template has neither any trailing\n        `X`es nor a trailing dot, a dot is added before the random suffix.\n\nThe template defaults to “`/tmp/temp.`”. An suffix of random shell-safe ASCII\ncharacters is added to the template to create the file. For compatibility with\nother `mktemp` implementations, any optional trailing `X` characters in the\ntemplate are removed. The length of the suffix will be equal to the amount of\n`X`es removed, or 10, whichever is more. The longer the random suffix, the\nhigher the security of using `mktemp` in a shared directory such as `tmp`.\n\nSince `/tmp` is a world-writable directory shared by other users, for best\nsecurity it is recommended to create a private subdirectory using `mktemp -d`\nand work within that.\n\nOption `-C` cannot be used without option `-s` when in a\n[subshell](#user-content-insubshell).\nModernish will detect this and treat it as a\nfatal error. The reason is that a typical command substitution like\n`tmpfile=$(mktemp -C)`\nis incompatible with auto-cleanup, as the cleanup EXIT trap would be\ntriggered not upon exiting the program but upon exiting the command\nsubstitution subshell that just ran `mktemp`, thereby immediately undoing\nthe creation of the file. Instead, do something like:\n`mktemp -sC; tmpfile=$REPLY`\n\nThis module depends on the trap stack to do auto-cleanup (the `-C` option),\nso it will automatically `use var/stack/trap` on initialisation.\n\n#### `use sys/base/readlink` ####\n`readlink` reads the target of a symbolic link, robustly handling strange\nfilenames such as those containing newline characters. It stores the result\nin the `REPLY` variable and optionally writes it on standard output.\n\nUsage: `readlink` [ `-nsefmQ` ] *path* [ *path* ... ]\n\n* `-n`: If writing output, don't add a trailing newline.\n  This does not remove the separating newlines if multiple *path*s are given.\n* `-s`: *S*ilent operation: don't write output, only store it in `REPLY`.\n* `-e`, `-f`, `-m`: Canonicalise. Convert each *path* found into a canonical\n  and absolute path that can be used starting from any working directory.\n  Relative *path*s are resolved starting from the present working directory.\n  Double slashes are removed. Any special pathname components\n  `.` and `..` are resolved. All symlinks encountered are\n  followed, but a *path* does not need to contain any symlinks. UNC network\n  paths (as on Cygwin) are supported. These options differ as follows:\n    * `-e`: All pathname components must exist to produce a result.\n    * `-f`: All but the last pathname component must exist to produce a result.\n    * `-m`: No pathname component needs to exist; this always produces a result.\n      Nonexistent pathname components are simulated as regular directories.\n* `-Q`: Shell-*q*uote each unit of output. Separate by spaces instead\n  of newlines. This generates a list of arguments in shell syntax,\n  guaranteed to be suitable for safe parsing by the shell, even if the\n  resulting pathnames should contain strange characters such as spaces or\n  newlines and other control characters.\n\nThe exit status of `readlink` is 0 on success and 1 if the *path* either is\nnot a symlink, or could not be canonicalised according to the option given.\n\n#### `use sys/base/rev` ####\n`rev` copies the specified files to the standard output, reversing the order\nof characters in every line. If no files are specified, the standard input\nis read.\n\nUsage: like `rev` on Linux and BSD, which is like `cat` except that `-` is\na filename and does not denote standard input. No options are supported.\n\n#### `use sys/base/seq` ####\nA cross-platform implementation of `seq` that is more powerful and versatile\nthan native GNU and BSD `seq`(1) implementations. The core is written in\n`bc`, the POSIX arbitrary-precision calculator language. That means this\n`seq` inherits the capacity to handle numbers with a precision and size only\nlimited by computer memory, as well as the ability to handle input numbers\nin any base from 1 to 16 and produce output in any base 1 and up.\n\nUsage: `seq` [ `-w` ] [ `-L` ] [ `-f` *format* ] [ `-s` *string* ] [ `-S` *scale* ]\n[ `-B` *base* ] [ `-b` *base* ] [ *first* [ *incr* ] ] *last*\n\n`seq` prints a sequence of arbitrary-precision floating point numbers, one\nper line, from *first* (default 1), to as near *last* as possible, in increments of\n*incr* (default 1). If *first* is larger than *last*, the default *incr* is -1.\nAn *incr* of zero is treated as a fatal error.\n\n* `-w`: Equalise width by padding with leading zeros. The longest of the\n\t*first*, *incr* or *last* arguments is taken as the length that each\n\toutput number should be padded to.\n* `-L`: Use the current locale's radix point in the output instead of the\n        full stop (`.`).\n* `-f`: `printf`-style floating-point format. The format string is passed on\n        (with an added `\\n`) to `awk`'s builtin `printf` function. Because\n        of that, the `-f` option can only be used if the output base is 10.\n        Note that `awk`'s floating point precision is limited, so very\n        large or long numbers will be rounded.\n* `-s`: Instead of writing one number per line, write all numbers on one\n        line separated by *string* and terminated by a newline character.\n* `-S`: Explicitly set the scale (number of digits after the\n        [radix point](https://en.wikipedia.org/wiki/Radix_point)).\n\tDefaults to the largest number of digits after the radix point\n\tamong the *first*, *incr* or *last* arguments.\n* `-B`: Set input and output base from 1 to 16. Defaults to 10.\n* `-b`: Set arbitrary output base from 1. Defaults to input base.\n        See the `bc`(1) manual for more information on the output format\n        for bases greater than 16.\n\nThe `-S`, `-B` and `-b` options take shell integer numbers as operands. This\nmeans a leading `0X` or `0x` denotes a hexadecimal number and a leading `0`\ndenotes an octal number.\n\nFor portability reasons, modernish `seq` uses a full stop (`.`) for the\n[radix point](https://en.wikipedia.org/wiki/Radix_point), regardless of the\nsystem locale. This applies both to command arguments and to output.\nThe `-L` option causes `seq` to use the current locale's radix point\ncharacter for output only.\n\n##### Differences with GNU and BSD `seq` #####\nThe `-S`, `-B` and `-b` options are modernish innovations.\nThe `-w`, `-f` and `-s` options are inspired by GNU and BSD `seq`.\nThe following differences apply:\n\n* Like GNU and unlike BSD, the separator specified by the `-s` option\n  is not appended to the final number and there is no `-t` option to\n  add a terminator character.\n* Like GNU and unlike BSD, the `-s` option-argument is taken as literal\n  characters and is not parsed for backslash escape codes like `\\n`.\n* Unlike GNU and like BSD, the output radix point defaults to a full stop,\n  regardless of the current locale.\n* Unlike GNU and like BSD, if *incr* is not specified,\n  it defaults to -1 if *first* \u003e *last*, 1 otherwise.\n  For example, `seq 5 1` counts backwards from 5 to 1, and\n  specifying `seq 5 -1 1` as with GNU is not needed.\n* Unlike GNU and like BSD, an *incr* of zero is not accepted.\n  To output the same number or string infinite times, use\n  [`yes`](#user-content-use-sysbaseyes) instead.\n* Unlike both GNU and BSD, the `-f` option accepts any format specifiers\n  accepted by `awk`'s `printf()` function.\n\nThe `sys/base/seq` module depends on, and automatically loads,\n[`var/string/touplow`](#user-content-use-varstringtouplow).\n\n#### `use sys/base/shuf` ####\nShuffle lines of text.\nA portable reimplementation of a commonly used GNU utility.\n\nUsage:\n\n* `shuf` [ `-n` *max* ] [ `-r` *rfile* ] *file*\n* `shuf` [ `-n` *max* ] [ `-r` *rfile* ] `-i` *low*`-`*high*\n* `shuf` [ `-n` *max* ] [ `-r` *rfile* ] `-e` *argument* ...\n\nBy default, `shuf` reads lines of text from standard input, or from *file*\n(the *file* `-` signifies standard input).\nIt writes the input lines to standard output in random order.\n\n* `-i`: Use sequence of non-negative integers *low* through *high* as input.\n* `-e`: Instead of reading input, use the *argument*s as lines of input.\n* `-n`: Output a maximum of *max* lines.\n* `-r`: Use *rfile* as the source of random bytes. Defaults to `/dev/urandom`.\n\nDifferences with GNU `shuf`:\n\n* Long option names are not supported.\n* The `-o`/`--output-file` option is not supported; use output redirection.\n  Safely shuffling files in-place is not supported; use a temporary file.\n* `--random-source=`*file* is changed to `-r` *file*.\n* The `-z`/`--zero-terminated` option is not supported.\n\n#### `use sys/base/tac` ####\n`tac` (the reverse of `cat`) is a cross-platform reimplementation of the GNU\n`tac` utility, with some extra features.\n\nUsage: `tac` [ `-rbBP` ] [ `-S` *separator* ] *file* [ *file* ... ]\n\n`tac` outputs the *file*s in reverse order of lines/records.\nIf *file* is `-` or is not given, `tac` reads from standard input.\n\n* `-s`: Specify the record (line) separator. Default: linefeed.\n* `-r`: Interpret the record separator as an\n  [extended regular expression](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04).\n  This allows using separators that may vary. Each separator is preserved\n  in the output as it is in the input.\n* `-b`: Assume the separator comes before each record in the input, and also\n  output the separator before each record. Cannot be combined with `-B`.\n* `-B`: Assume the separator comes after each record in the input, but output\n  the separator before each record. Cannot be combined with `-b`.\n* `-P`: Paragraph mode: output text last paragraph first. Input paragraphs\n  are separated from each other by at least two linefeeds. Cannot be combined\n  with any other option.\n\nDifferences between GNU `tac` and modernish `tac`:\n* The `-B` and `-P` options were added.\n* The `-r` option interprets the record separator as an extended regular\n  expression. This is an incompatibility with GNU `tac` unless expressions\n  are used that are valid as both basic and extended regular expressions.\n* In UTF-8 locales, multi-byte characters are recognised and reversed\n  correctly.\n\n#### `use sys/base/which` ####\nThe modernish `which` utility finds external programs and reports their\nabsolute paths, offering several unique options for reporting, formatting\nand robust processing. The default operation is similar to GNU `which`.\n\nUsage: `which` [ `-apqsnQ1f` ] [ `-P` *number* ] *program* [ *program* ... ]\n\nBy default, `which` finds the first available path to each given *program*.\nIf *program* is itself a path name (contains a slash), only that path's base\ndirectory is searched; if it is a simple command name, the current `$PATH`\nis searched. Any relative paths found are converted to absolute paths.\nSymbolic links are not followed. The first path found for each *program* is\nwritten to standard output (one per line), and a warning is written to\nstandard error for every *program* not found. The exit status is 0 (success)\nif all *program*s were found, 1 otherwise.\n\n`which` also leaves its output in the `REPLY` variable. This may be useful\nif you run `which` in the main shell environment. The `REPLY` value will\n*not* survive a command substitution\n[subshell](#user-content-insubshell)\nas in `ls_path=$(which ls)`.\n\nThe following options modify the default behaviour described above:\n\n* `-a`: List *a*ll *program*s that can be found in the directories searched,\n  instead of just the first one. This is useful for finding duplicate\n  commands that the shell would not normally find when searching its `$PATH`.\n* `-p`: Search in [`$DEFPATH`](#user-content-modernish-system-constants)\n  (the default standard utility `PATH` provided by the operating system)\n  instead of in the user's `$PATH`, which is vulnerable to manipulation.\n* `-q`: Be *q*uiet: suppress all warnings.\n* `-s`: *S*ilent operation: don't write output, only store it in the `REPLY`\n  variable. Suppress warnings except, if you run `which -s` in a subshell,\n  a warning that the `REPLY` variable will not survive the subshell.\n* `-n`: When writing to standard output, do *n*ot write a final *n*ewline.\n* `-Q`: Shell-*q*uote each unit of output. Separate by spaces instead\n  of newlines. This generates a one-line list of arguments in shell syntax,\n  guaranteed to be suitable for safe parsing by the shell, even if the\n  resulting pathnames should contain strange characters such as spaces or\n  newlines and other control characters.\n* `-1` (one): Output the results for at most *one* of the arguments in\n  descending order of preference: once a search succeeds, ignore\n  the rest. Suppress warnings except a subshell warning for `-s`.\n  This is useful for finding a command that can exist under\n  several names, for example:\n  `which -f -1 gnutar gtar tar`    \n  This option modifies which's exit status behaviour: `which -1`\n  returns successfully if at least one command was found.\n* `-f`: Throw a [*f*atal error](#user-content-reliable-emergency-halt)\n  in cases where `which` would otherwise return status 1 (non-success).\n* `-P`: Strip the indicated number of *p*athname elements from the output,\n  starting from the right.\n  `-P1`: strip `/program`;\n  `-P2`: strip `/*/program`,\n  etc. This is useful for determining the installation root directory for\n  an installed package.\n* `--help`: Show brief usage information.\n\n#### `use sys/base/yes` ####\n`yes` very quickly outputs infinite lines of text, each consisting of its\nspace-separated arguments, until terminated by a signal or by a failure to\nwrite output. If no argument is given, the default line is `y`. No options\nare supported.\n\nThis infinite-output command is useful for piping into commands that need an\nindefinite input data stream, or to automate a command requiring interactive\nconfirmation.\n\nModernish `yes` is like GNU `yes` in that it outputs all its arguments,\nwhereas BSD `yes` only outputs the first. It can output multiple gigabytes\nper second on modern systems.\n\n### `use sys/cmd` ###\n\nModules in this category contain functions for enhancing the invocation of\ncommands.\n\n#### `use sys/cmd/extern` ####\n`extern` is like `command` but always runs an external command, without\nhaving to know or determine its location. This provides an easy way to\nbypass a builtin, alias or function. It does the same `$PATH` search\nthe shell normally does when running an external command. For instance, to\nguarantee running external `printf` just do: `extern printf ...`\n\nUsage: `extern` [ `-p` ] [ `-v` ] [ `-u` *varname* ... ]\n[ *varname*`=`*value* ... ] *command* [ *argument* ... ]\n\n* `-p`: The *command*, as well as any commands it further invokes, are searched in\n  [`$DEFPATH`](#user-content-modernish-system-constants)\n  (the default standard utility `PATH` provided by the operating system)\n  instead of in the user's `$PATH`, which is vulnerable to manipulation.\n  * `extern -p` is much more reliable than the shell's builtin\n    [`command -p`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html#tag_20_22_04)\n    because: (a) many existing shell installations use a wrong search path for\n    `command -p`; (b) `command -p` does not export the default `PATH`, so\n    something like `command -p sudo cp foo /bin/bar` searches only `sudo` in\n    the secure default path and not `cp`.\n* `-v`: don't execute *command* but show the full path name of the command that\n  would have been executed. Any extra *argument*s are taken as more command\n  paths to show, one per line. `extern` exits with status 0 if all the commands\n  were found, 1 otherwise. This option can be combined with `-p`.\n* `-u`: Temporary export override. Unset the given variable in the\n  environment of the command executed, even if it is currently exported. Can\n  be specified multiple times.\n* *varname*`=`*value* assignment-arguments: These variables/values are\n  temporarily exported to the environment during the execution of the command.\n  * This is provided because assignments *preceding* `extern` cause unwanted,\n    shell-dependent side effects, as `extern` is a shell function. Be\n    sure to provide assignment-arguments *following* `extern` instead.\n  * Assignment-arguments after a `--` end-of-options delimiter are not parsed;\n    this allows *command*s containing a `=` sign to be executed.\n\n#### `use sys/cmd/harden` ####\nThe `harden` function allows implementing emergency halt on error\nfor any external commands and shell builtin utilities. It is\nmodernish's replacement for `set -e` a.k.a. `set -o errexit` (which is\n[fundamentally](https://lists.gnu.org/archive/html/bug-bash/2012-12/msg00093.html)\n[flawed](http://mywiki.wooledge.org/BashFAQ/105),\nnot supported and will break the library).\nIt depends on, and auto-loads, the `sys/cmd/extern` module.\n\n`harden` sets a shell function with the same name as the command hardened,\nso it can be used transparently. This function hardens the given command by\nchecking its exit status against values indicating error or system failure.\nExactly what exit statuses signify an error or failure depends on the\ncommand in question; this should be looked up in the\n[POSIX specification](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html)\n(under \"Utilities\") or in the command's `man` page or other documentation.\n\nIf the command fails, the function installed by `harden` calls `die`, so it\nwill reliably halt program execution, even if the failure occurred within a\n[subshell](#user-content-insubshell).\n\nUsage:\n\n`harden` [ `-f` *funcname* ] [ `-[cSpXtPE]` ] [ `-e` *testexpr* ]\n[ *var*`=`*value* ... ] [ `-u` *var* ... ] *command_name_or_path*\n[ *command_argument* ... ]\n\nThe `-f` option hardens the command as the shell function *funcname* instead\nof defaulting to *command_name_or_path* as the function name. (If the latter\nis a path, that's always an invalid function name, so the use of `-f` is\nmandatory.) If *command_name_or_path* is itself a shell function, that\nfunction is bypassed and the builtin or external command by that name is\nhardened instead. If no such command is found, `harden` dies with the message\nthat hardening shell functions is not supported. (Instead, you should invoke\n`die` directly from your shell function upon detecting a fatal error.)\n\nThe `-c` option causes *command_name_or_path* to be hardened and run\nimmediately instead of setting a shell function for later use. This option\nis meant for commands that run once; it is not efficient for repeated use.\nIt cannot be used together with the `-f` option.\n\nThe `-S` option allows specifying several possible names/paths for a\ncommand. It causes the *command_name_or_path* to be split by comma and\ninterpreted as multiple names or paths to search. The first name or path\nfound is used. Requires `-f`.\n\nThe `-e` option, which defaults to `\u003e0`, indicates the exit statuses\ncorresponding to a fatal error. It depends on the command what these are;\nconsult the POSIX spec and the manual pages.\nThe status test expression *testexpr*, argument\nto the `-e` option, is like a shell arithmetic\nexpression, with the binary operators `==` `!=` `\u003c=` `\u003e=` `\u003c` `\u003e` turned\ninto unary operators referring to the exit status of the command in\nquestion. Assignment operators are disallowed. Everything else is the same,\nincluding `\u0026\u0026` (logical and) and `||` (logical or) and parentheses.\nNote that the expression needs to be quoted as the characters used in it\nclash with shell grammar tokens.\n\nThe `-X` option causes `harden` to always search for and harden an external\ncommand, even if a built-in command by that name exists.\n\nThe `-E` option causes the hardening function to consider it a fatal error\nif the hardened command writes anything to the standard error stream. This\noption allows hardening commands (such as\n[`bc`](pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html#tag_20_09_14))\nwhere you can't rely on the exit status to detect an error. The text written\nto standard error is passed on as part of the error message printed by\n`die`. Note that:\n* Intercepting standard error necessitates that the command be executed from a\n  [subshell](#user-content-insubshell).\n  This means any builtins or shell functions hardened with `-E` cannot\n  influence the calling shell (e.g. `harden -E cd` renders `cd` ineffective).\n* `-E` does not disable exit status checks; by default, any exit status greater\n  than zero is still considered a fatal error as well. If your command does not\n  even reliably return a 0 status upon success, then you may want to add `-e\n  '\u003e125'`, limiting the exit status check to reserved values indicating errors\n  launching the command and signals caught.\n\nThe `-p` option causes `harden` to search for commands using the\nsystem default path (as obtained with `getconf PATH`) as opposed to the\ncurrent `$PATH`. This ensures that you're using a known-good external\ncommand that came with your operating system. By default, the system-default\nPATH search only applies to the command itself, and not to any commands that\nthe command may search for in turn. But if the `-p` option is specified at\nleast twice, the command is run in a subshell with `PATH` exported as the\ndefault path, which is equivalent to adding a `PATH=$DEFPATH` assignment\nargument (see [below](#user-content-important-note-on-variable-assignments)).\n\nExamples:\n\n```sh\nharden make                           # simple check for status \u003e 0\nharden -f tar '/usr/local/bin/gnutar' # id.; be sure to use this 'tar' version\nharden -e '\u003e 1' grep                  # for grep, status \u003e 1 means error\nharden -e '==1 || \u003e2' gzip            # 1 and \u003e2 are errors, but 2 isn't (see manual)\n```\n\n##### Important note on variable assignments #####\nAs far as the shell is concerned, hardened commands are shell functions and\nnot external or builtin commands. This essentially changes one behaviour of\nthe shell: variable assignments preceding the command will not be local to\nthe command as usual, but *will persist* after the command completes.\n(POSIX technically makes that behaviour\n[optional](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01)\nbut all current shells behave the same in POSIX mode.)\n\nFor example, this means that something like\n\n```sh\nharden -e '\u003e1' grep\n# [...]\nLC_ALL=C grep regex some_ascii_file.txt\n```\n\nshould never be done, because the meant-to-be-temporary `LC_ALL` locale\nassignment will persist and is likely to cause problems further on.\n\nTo solve this problem, `harden` supports adding these assignments as\npart of the hardening command, so instead of the above you do:\n\n```sh\nharden -e '\u003e1' LC_ALL=C grep\n# [...]\ngrep regex some_ascii_file.txt\n```\n\nWith the `-u` option, `harden` also supports unsetting variables for the\nduration of a command, e.g.:\n\n```sh\nharden -e '\u003e1' -u LC_ALL grep\n```\n\nThe `-u` option may be specified multiple times.\nIt causes the hardened command to be invoked from a\n[subshell](#user-content-insubshell)\nwith the specified variables unset.\n\n##### Hardening while allowing for broken pipes #####\nIf you're piping a command's output into another command that may close\nthe pipe before the first command is finished, you can use the `-P` option\nto allow for this:\n\n```sh\nharden -e '==1 || \u003e2' -P gzip\t\t# also tolerate gzip being killed by SIGPIPE\ngzip -dc file.txt.gz | head -n 10\t# show first 10 lines of decompressed file\n```\n\n`head` will close the pipe of `gzip` input after ten lines; the operating\nsystem kernel then kills `gzip` with the PIPE signal before it's finished,\ncausing a particular exit status that is greater than 128. This exit status\nwould normally make `harden` kill your entire program, which in the example\nabove is clearly not the desired behaviour. If the exit status caused by a\nbroken pipe were known, you could specifically allow for that exit status in\nthe status expression. The trouble is that this exit status varies depending\non the shell and the operating system. The `-p` option was made to solve\nthis problem: it automatically detects and whitelists the correct exit\nstatus corresponding to `SIGPIPE` termination on the current system.\n\nTolerating `SIGPIPE` is an option and not the default, because in many\ncontexts it may be entirely unexpected and a symptom of a severe error if a\ncommand is killed by a broken pipe. It is up to the programmer to decide\nwhich commands should expect `SIGPIPE` and which shouldn't.\n\n*Tip:* It could happen that the same command should expect `SIGPIPE` in one\ncontext but not another. You can create two hardened versions of the same\ncommand, one that tolerates `SIGPIPE` and one that doesn't. For example:\n\n```sh\nharden -f hardGrep -e '\u003e1' grep\t\t# hardGrep does not tolerate being aborted\nharden -f pipeGrep -e '\u003e1' -P grep\t# pipeGrep for use in pipes that may break\n```\n\n*Note:* If `SIGPIPE` was set to ignore by the process invoking the current\nshell, the `-p` option has no effect, because no process or subprocess of\nthe current shell can ever be killed by `SIGPIPE`. However, this may cause\nvarious other problems and you may want to refuse to let your program run\nunder that condition.\n[`thisshellhas WRN_NOSIGPIPE`](#user-content-warning-ids) can help\nyou easily detect that condition so your program can make a decision. See\nthe [`WRN_NOSIGPIPE` description](#user-content-warning-ids) for more information.\n\n##### Tracing the execution of hardened commands #####\nThe `-t` option will trace command output. Each execution of a command\nhardened with `-t` causes the command line to be output to standard\nerror, in the following format:\n\n    [functionname]\u003e commandline\n\nwhere `functionname` is the name of the shell function used to harden the\ncommand and `commandline` is the actual command executed. The\n`commandline` is properly shell-quoted in a format suitable for re-entry\ninto the shell; however, command lines longer than 512 bytes will be\ntruncated and the unquoted string ` (TRUNCATED)` will be appended to the\ntrace. If standard error is on a terminal that supports ANSI colours,\nthe tracing output will be colourised.\n\nThe `-t` option was added to `harden` because the commands that you harden\nare often the same ones you would be particularly interested in tracing. The\nadvantage of using `harden -t` over the shell's builtin tracing facility\n(`set -x` or `set -o xtrace`) is that the output is a *lot* less noisy,\nespecially when using a shell library such as modernish.\n\n*Note:* Internally, `-t` uses the shell file descriptor 9, redirecting it to\nstandard error (using `exec 9\u003e\u00262`). This allows tracing to continue to work\nnormally even for commands that redirect standard error to a file (which is\nanother enhancement over `set -x` on most shells). However, this does mean\n`harden -t` conflicts with any other use of the file descriptor 9 in your\nshell program.\n\nIf file descriptor 9 is already open before `harden` is called, `harden`\ndoes not attempt to override this. This means tracing may be redirected\nelsewhere by doing something like `exec 9\u003etrace.out` before calling\n`harden`. (Note that redirecting FD 9 on the `harden` command itself will\n*not* work as it won't survive the run of the command.)\n\n##### Simple tracing of commands #####\nSometimes you just want to trace the execution of some specific commands as\nin `harden -t` (see above) without actually hardening them against command\nerrors; you might prefer to do your own error handling. `trace` makes this\neasy. It is modernish's replacement or complement for `set -x` a.k.a. `set\n-o xtrace`.\nUnlike `harden -t`, it can also trace shell functions.\n\n**Usage 1:** `trace` [ `-f` *funcname* ] [ `-[cSpXE]` ]\n[ *var*`=`*value* ... ] [ `-u` *var* ... ] *command_name_or_path*\n[ *command_argument* ... ]\n\nFor non-function commands, `trace` acts as a shortcut for\n`harden -t -P -e '\u003e125 \u0026\u0026 !=255'` *command_name_or_path*.\nAny further options and arguments are passed on to `harden` as given. The\nresult is that the indicated command is automatically traced upon execution.\nA bonus is that you still get minimal hardening against fatal system errors.\nErrors in the traced command itself are ignored, but your program is\nimmediately halted with an informative error message if the traced command:\n\n- cannot be found (exit status 127);\n- was found but cannot be executed (exit status 126);\n- was killed by a signal other than `SIGPIPE` (exit status \u003e 128, except\n  the shell-specific exit status for `SIGPIPE`, and except 255 which is\n  used by some utilities, such as `ssh` and `rsync`, to return an error).\n\n*Note:* The caveat for command-local variable assignments for `harden` also\napplies to `trace`. See\n[Important note on variable assignments](#user-content-important-note-on-variable-assignments)\nabove.\n\n**Usage 2:** [ `#!` ] `trace -f` *funcname*\n\nIf no further arguments are given, `trace -f` will trace the shell\nfunction *funcname* without applying further hardening (except against\nnonexistence). `trace -f` can be used to trace the execution of modernish\nlibrary functions as well as your own script's functions. The trace output\nfor shell functions shows an extra `()` following the function name.\n\nInternally, this involves setting an alias under the function's name, so\nthe limitations of the shell's alias expansion mechanism apply: only\nfunction calls that the shell had not yet parsed before calling `trace -f`\nwill be traced. So you should use `trace -f` at the beginning of your\nscript, before defining your own functions. To facilitate this, `trace -f`\ndoes not check that the function *funcname* exists while setting up\ntracing, but only when attempting to execute the traced function.\n\nIn [portable-form](#user-content-portable-form)\nmodernish scripts, `trace -f` should be used as a hashbang command to be\ncompatible with alias expansion on all shells. Only the `trace -f` form\nmay be used that way. For example:\n\n```sh\n#! /usr/bin/env modernish\n#! use safe -k\n#! use sys/cmd/harden\n#! trace -f push\n#! trace -f pop\n...your program begins here...\n```\n\n#### `use sys/cmd/mapr` ####\n`mapr` (map records) is an alternative to `xargs` that shares features with the\n`mapfile` command in bash 4.x. It is fully integrated into your script's main\nshell environment, so it can call your shell functions as well as builtin and\nexternal utilities.\nIt depends on, and auto-loads, the `sys/cmd/procsubst` module.\n\nUsage: `mapr` [ `-d` *delimiter* | `-P` ] [ `-s` *count* ] [ `-n` *number* ]\n[ `-m` *length* ] [ `-c` *quantum* ] *callback*\n\n`mapr` reads delimited records from the standard input, invoking the specified\n*callback* command once or repeatedly as needed, with batches of input records\nas arguments. The *callback* may consist of multiple arguments. By default, an\ninput record is one line of text.\n\nOptions:\n\n* `-d` *delimiter*: Use the single character *delimiter* to delimit input records,\n  instead of the newline character. A `NUL` (0) character and multi-byte\n  characters are not supported.\n* `-P`: Paragraph mode. Input records are delimited by sequences consisting of\n  a newline plus one or more blank lines, and leading or trailing blank lines\n  will not result in empty records at the beginning or end of the input. Cannot\n  be used together with `-d`.\n* `-s` *count*: Skip and discard the first *count* records read.\n* `-n` *number*: Stop processing after passing a total of *number* records to\n  invocation(s) of *callback*. If `-n` is not supplied or *number* is 0, all\n  records are passed, except those skipped using `-s`.\n* `-m` *length*: Set the maximum argument length in bytes of each *callback*\n  command call, including the *callback* command argument(s) and the current\n  batch of up to *quantum* input records. The length of each argument is\n  increased by 1 to account for the terminating null byte. The default\n  maximum length depends on constraints set by the operating system for\n  invoking external commands. If *length* is 0, this limit is disabled.\n* `-c` *quantum*: Pass at most *quantum* arguments at a time to each call to\n  *callback*. If `-c` is not supplied or if *quantum* is 0, the number of\n  arguments per invocation is not limited except by `-m`; whichever limit is\n  reached first applies.\n\nArguments:\n\n* *callback*: Call the *callback* command with the collected arguments each\n  time *quantum* lines are read. The callback command may be a shell function or\n  any other kind of command, and is executed from the same shell environment\n  that invoked `mapr`. If the callback command exits or returns with status\n  255 or is interrupted by the `SIGPIPE` signal, `mapr` will not process any\n  further batches but immediately exit with the status of the callback\n  command. If it exits with another exit status 126 or greater, a\n  [fatal error](#user-content-reliable-emergency-halt)\n  is thrown. Otherwise, `mapr` exits with the status of the last-executed\n  callback command.\n* *argument*:  If there are extra arguments supplied on the mapr command line,\n  they will be added before the collected arguments on each invocation on the\n  callback command.\n\n##### Differences from `mapfile` #####\n`mapr` was inspired by the bash 4.x builtin command `mapfile` a.k.a.\n`readarray`, and uses similar options, but there are important differences.\n\n* `mapr` passes all the records as arguments to the callback command.\n* `mapr` does not support assigning records directly to an array. Instead,\n  all handling is done through the callback command (which could be a shell\n  function that assigns its arguments to an array.)\n* The callback command is specified directly instead of with a `-C` option,\n  and it may consist of several arguments (as with `xargs`).\n* The record separator itself is never included in the arguments passed\n  to the callback command (so there is no `-t` option to remove it).\n* `mapr` supports paragraph mode.\n* If the callback command exits with status 255, processing is aborted.\n\n##### Differences from `xargs` #####\n`mapr` shares important characteristics with\n[`xargs`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html)\nwhile avoiding its myriad pitfalls.\n\n* Instead of being an external utility, `mapr` is fully integrated into the\n  shell. The callback command can be a shell function or builtin, which can\n  directly modify the shell environment.\n* `mapr` is line-oriented by default, so it is safe to use for input\n  arguments that contain spaces or tabs.\n* `mapr` does not parse or modify the input arguments in any way, e.g. it\n  does not process and remove quotes from them like `xargs` does.\n* `mapr` supports paragraph mode.\n\n#### `use sys/cmd/procsubst` ####\nThis module provides a portable\n[process substitution](https://en.wikipedia.org/wiki/Process_substitution)\nconstruct, the advantage being that this is not limited to bash, ksh or zsh\nbut works on all POSIX shells capable of running modernish. It is not\npossible for modernish to introduce the original ksh syntax into other\nshells. Instead, this module provides a `%` command for use within a\n`$(`command substitution`)`.\n\nThe `%` command takes one simple command as its arguments, executes it in\nthe background, and writes a file name from which to read its output. So\nif `%` is used within a command substitution as intended, that file name\nis passed on to the invoking command as an argument.\n\nThe `%` command supports one option, `-o`. If that option is given, then it is\nexpected that, instead of reading input, the invoking command writes output to\nthe file name passed on to it, so that the command invoked by `% -o` can read\nthat data from its standard input.\n\n\u003ctable\u003e\n\u003ccaption\u003eExample syntax comparison:\u003c/caption\u003e\n\u003ctr\u003e\n\u003cth\u003eksh/bash/zsh\u003c/th\u003e\u003cth\u003emodernish\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\u003ccode\u003ediff -u \u003c(ls) \u003c(ls -a)\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003ccode\u003ediff -u $(% ls) $(% ls -a)\u003c/code\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\u003ccode\u003eIFS=' ' read -r user vsz args \u003c \u003c(ps -o 'user= vsz= args=' -p $$)\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003ccode\u003eIFS=' ' read -r user vsz args \u003c $(% ps -o 'user= vsz= args=' -p $$)\u003c/code\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\u003ccode\u003e{ some commands; } \u003e \u003e(tee stdout.log) 2\u003e \u003e(tee stderr.log)\u003c/code\u003e\n\u003cbr/\u003e\u003csmall\u003e(both `tee` commands write terminal output to standard output)\u003c/small\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\u003ccode\u003e{ some commands; } \u003e $(% -o tee stdout.log) 2\u003e $(% -o tee stderr.log)\u003c/code\u003e\n\u003cbr/\u003e\u003csmall\u003e(both `tee` commands write terminal output to standard error)\u003c/small\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nUnlike the bash/ksh/zsh version, modernish process substitution only works\nwith simple commands. This includes shell function calls, but not aliases or\nanything involving shell grammar or reserved words (such as redirections,\npipelines, loops, etc.). To use such complex commands, enclose them in a shell\nfunction and call that function from the process substitution.\n\nAlso note that anything that a command invoked by the `% -o` writes to its\nstandard output is redirected to standard error. The main shell environment's\nstandard output is not available because the command substitution subsumes it.\n\n#### `use sys/cmd/source` ####\nThe `source` command sources a dot script like the `.` command, but\nadditionally supports passing arguments to sourced scripts like you would\npass them to a function. It mostly mimics the behaviour of the `source`\ncommand built in to bash and zsh.\n\nIf a filename without a directory path is given, then, unlike the `.`\ncommand, `source` looks for the dot script in the current directory by\ndefault, as well as searching `$PATH`.\n\nIt is a fatal error to attempt to source a directory, a file with no read\npermission, or a nonexistent file.\n\n### `use sys/dir` ###\n\nFunctions for working with directories.\n\n#### `use sys/dir/countfiles` ####\n`countfiles`: Count the files in a directory using nothing but shell\nfunctionality, so without external commands. (It's amazing how many pitfalls\nthis has, so a library function is needed to do it robustly.)\n\nUsage: `countfiles` [ `-s` ] *directory* [ *globpattern* ... ]\n\nCount the number of files in a directory, storing the number in `REPLY`\nand (unless `-s` is given) printing it to standard output.\nIf any *globpattern*s are given, only count the files matching them.\n\n#### `use sys/dir/mkcd` ####\nThe `mkcd` function makes one or more directories, then, upon success,\nchange into the last-mentioned one. `mkcd` inherits `mkdir`'s usage, so\noptions depend on your system's `mkdir`; only the\n[POSIX options](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mkdir.html#tag_20_79_04)\nare guaranteed.\nWhen `mkcd` is run from a script, it uses `cd -P` to change the working\ndirectory, resolving any symlinks in the present working directory path.\n\n\n### `use sys/term` ###\n\nUtilities for working with the terminal.\n\n#### `use sys/term/putr` ####\nThis module provides commands to efficiently output a string repeatedly.\n\nUsage:\n\n* `putr` [ *number* | `-` ] *string*\n* `putrln` [ *number* | `-` ] *string*\n\nOutput the *string* *number* times. When using `putrln`, add a newline at\nthe end.\n\n\nIf a `-` is given instead of a *number*, then the total length of the output\nis the line length of the terminal divided by the length of the *string*,\nrounded down.\n\nNote that, unlike with `put` and `putln`, only a single *string*\nargument is accepted.\n\nExample: `putrln - '='` prints a full terminal line of equals signs.\n\n#### `use sys/term/readkey` ####\n`readkey`: read a single character from the keyboard without echoing back to\nthe terminal. Buffering is done so that multiple waiting characters are read\none at a time.\n\nUsage: `readkey` [ `-E` *ERE* ] [ `-t` *timeout* ] [ `-r` ] [ *varname* ]\n\n`-E`: Only accept characters that match the extended regular expression\n*ERE* (the type of RE used by `grep -E`/`egrep`). `readkey` will silently\nignore input not matching the ERE and wait for input matching it.\n\n`-t`: Specify a *timeout* in seconds (one significant digit after the\ndecimal point). After the timeout expires, no character is read and\n`readkey` returns status 1.\n\n`-r`: Raw mode. Disables INTR (Ctrl+C), QUIT, and SUSP (Ctrl+Z) processing\nas well as translation of carriage return (13) to linefeed (10).\n\nThe character read is stored into the variable referenced by *varname*,\nwhich defaults to `REPLY` if not specified.\n\nThis module depends on the trap stack to save and restore the terminal state\nif the program is stopped while reading a key, so it will automatically\n`use var/stack/trap` on initialisation.\n\n---\n\n## Appendix A: List of shell cap IDs ##\n\nThis appendix lists all the shell\n[capabilities](#user-content-capabilities),\n[quirks](#user-content-quirks), and\n[bugs](#user-content-bugs)\nthat modernish can detect in the current shell, so that modernish scripts\ncan easily query the results of these tests and decide what to do. Certain\n[problematic system conditions](#user-content-warning-ids)\nare also detected this way and listed here.\n\nThe all-caps IDs below are all usable with the\n[`thisshellhas`](#user-content-shell-capability-detection)\nfunction. This makes it easy for a cross-platform modernish script to\nbe aware of relevant conditions and decide what to do.\n\nEach detection test has its own little test script in the\n`lib/modernish/cap` directory. These tests are executed on demand, the\nfirst time the capability or bug in question is queried using\n`thisshellhas`. See `README.md` in that directory for further information.\nThe test scripts also document themselves in the comments.\n\n### Capabilities ###\n\nModernish currently identifies and supports the following non-standard\nshell capabilities:\n\n* `ADDASSIGN`: Add a string to a variable using additive assignment,\n  e.g. *VAR*`+=`*string*\n* `ANONFUNC`: zsh anonymous functions (basically the native zsh equivalent\n  of modernish's var/local module)\n* `ARITHCMD`: standalone arithmetic evaluation using a command like\n  `((`*expression*`))`.\n* `ARITHFOR`: ksh93/C-style arithmetic `for` loops of the form\n  `for ((`*exp1*`; `*exp2*`; `*exp3*`)) do `*commands*`; done`.\n* `ARITHPP`: support for the `++` and `--` unary operators in shell arithmetic.\n* `CESCQUOT`: Quoting with C-style escapes, like `$'\\n'` for newline.\n* `DBLBRACKET`: The ksh88-style `[[` double-bracket command `]]`,\n  implemented as a reserved word, integrated into the main shell grammar,\n  and with a different grammar applying within the double brackets.\n  (ksh93, mksh, bash, zsh, yash \u003e= 2.48)\n* `DBLBRACKETERE`: `DBLBRACKET` plus the `=~` binary operator to match a\n  string against an extended regular expression.\n* `DBLBRACKETV`: `DBLBRACKET` plus the `-v` unary operator to test if a\n  variable is set. Named variables only. (Testing positional parameters\n  (like `[[ -v 1 ]]`) does not work on bash or ksh93; check `$#` instead.)\n* `DOTARG`: Dot scripts support arguments.\n* `HERESTR`: Here-strings, an abbreviated kind of here-document.\n* `KSH88FUNC`: define ksh88-style shell functions with the `function` keyword,\n  supporting dynamically scoped local variables with the `typeset` builtin.\n  (mksh, bash, zsh, yash, et al)\n* `KSH93FUNC`: the same, but with static scoping for local variables. (ksh93 only)\n  See Q28 at the [ksh93 FAQ](http://kornshell.com/doc/faq.html) for an explanation\n  of the difference.\n* `KSHARRAY`: ksh93-style arrays. Supported on bash, zsh (under `emulate sh`),\n  mksh, and ksh93.\n* `LEPIPEMAIN`: execute last element of a pipe in the main shell, so that\n  things like *somecommand* `| read` *somevariable* work. (zsh, AT\u0026T ksh,\n  bash 4.2+)\n* `LINENO`: the `$LINENO` variable contains the current shell script line\n  number.\n* `LOCALVARS`: the `local` command creates dynamically scoped local variables\n  within functions defined using standard POSIX syntax.\n* `NONFORKSUBSH`: as a performance optimisation,\n  [subshells](#user-content-insubshell) are\n  implemented without forking a new process, so they share a PID with the main\n  shell. (AT\u0026T ksh93; it has [many bugs](https://github.com/att/ast/issues/480)\n  related to this, but there's a nice workaround: `ulimit -t unlimited` forces\n  a subshell to fork, making those bugs disappear! See also `BUG_FNSUBSH`.)\n* `PRINTFV`: The shell's `printf` builtin has the `-v` option to print to a variable,\n  which avoids forking a command substitution subshell.\n* `PROCREDIR`: the shell natively supports `\u003c(`process redirection`)`,\n  a special kind of redirection that connects standard input (or standard\n  output) to a background process running your command(s).\n  This exists on yash.\n  Note this is **not** combined with a redirection like `\u003c \u003c(`*command*`)`.\n  Contrast with bash/ksh/zsh's `PROCSUBST` where this `\u003c(`syntax`)`\n  substitutes a file name.\n* `PROCSUBST`: the shell natively supports `\u003c(`process substitution`)`,\n  a special kind of command substitution that substitutes a file name,\n  connecting it to a background process running your command(s).\n  This exists on ksh93 and zsh.\n  (Bash has it too, but its POSIX mode turns it off, so modernish can't use it.)\n  Note this is usually combined with a redirection, like `\u003c \u003c(`*command*`)`.\n  Contrast this with yash's `PROCREDIR` where the same `\u003c(`syntax`)`\n  is itself a redirection.\n* `PSREPLACE`: Search and replace strings in variables using special parameter\n  substitutions with a syntax vaguely resembling sed.\n* `RANDOM`: the `$RANDOM` pseudorandom generator.\n  Modernish seeds it if detected. The variable is then set it to read-only\n  whether the generator is detected or not, in order to block it from losing\n  its special properties by being unset or overwritten, and to stop it being\n  used if there is no generator. This is because some of modernish depends\n  on `RANDOM` either working properly or being unset.    \n  (The use case for non-readonly `RANDOM` is setting a known seed to get\n  reproducible pseudorandom sequences. To get that in a modernish script,\n  use `awk`'s `srand(yourseed)` and `int(rand()*32768)`.)\n* `ROFUNC`: Set functions to read-only with `readonly -f`. (bash, yash)\n* `TESTERE`: The regular `test`/`[` builtin command supports the `=~` binary\n  operator to match a string against an extended regular expression.\n* `TESTO`: The `test`/`[` builtin supports the `-o` unary operator to check if \n  a shell option is set.\n* `TRAPPRSUBSH`: The ability to obtain a list of the current shell's native\n  traps from a command substitution subshell, for example: `var=$(trap)`,\n  as long as no new traps have been set within that command substitution.\n  Note that the `var/stack/trap` module transparently reimplements this\n  feature on shells without this native capability.\n* `TRAPZERR`: This feature ID is detected if the `ERR` trap is an alias for\n  the `ZERR` trap. According to the zsh manual, this is the case for zsh on\n  most systems, i.e. those that don't have a `SIGERR` signal. (The\n  [trap stack](#user-content-the-trap-stack)\n  uses this feature test.)\n* `VARPREFIX`: Expansions of type `${!`*prefix*`@}` and `${!`*prefix*`*}` yield\n  all names of set variables beginning with `prefix` in the same way and with\n  the same quoting effects as `$@` and `$*`, respectively.\n  This includes the name *prefix* itself, unless the shell has `BUG_VARPREFIX`.\n  (bash; AT\u0026T ksh93)\n\n### Quirks ###\n\nModernish currently identifies and supports the following shell quirks:\n\n* `QRK_32BIT`: mksh: the shell only has 32-bit arithmetic. Since every modern\n  system these days supports 64-bit long integers even on 32-bit kernels, we\n  can now count this as a quirk.\n* `QRK_ANDORBG`: On zsh, the `\u0026` operator takes the last simple command\n  as the background job and not an entire AND-OR list (if any).\n  In other words, `a \u0026\u0026 b || c \u0026` is interpreted as\n  `a \u0026\u0026 b || { c \u0026 }` and not `{ a \u0026\u0026 b || c; } \u0026`.\n* `QRK_ARITHEMPT`: In yash, with POSIX mode turned off, a set but empty\n  variable yields an empty string when used in an arithmetic expression,\n  instead of 0. For example, `foo=''; echo $((foo))` outputs an empty line.\n* `QRK_ARITHWHSP`: In [yash](https://osdn.jp/ticket/browse.php?group_id=3863\u0026tid=36002)\n  and FreeBSD /bin/sh, trailing whitespace from variables is not trimmed in arithmetic\n  expansion, causing the shell to exit with an 'invalid number' error. POSIX is silent\n  on the issue. The modernish `isint` function (to determine if a string is a valid\n  integer number in shell syntax) is `QRK_ARITHWHSP` compatible, tolerating only\n  leading whitespace.\n* `QRK_BCDANGER`: `break` and `continue` can affect non-enclosing loops,\n  even across shell function barriers (zsh, Busybox ash; older versions\n  of bash, dash and yash). (This is especially dangerous when using\n  [var/local](#user-content-use-varlocal)\n  which internally uses a temporary shell function to try to protect against\n  breaking out of the block without restoring global parameters and settings.)\n* `QRK_EMPTPPFLD`: Unquoted `$@` and `$*` do not discard empty fields.\n  [POSIX says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02)\n  for both unquoted `$@` and unquoted `$*` that empty positional parameters\n  *may* be discarded from the expansion. AFAIK, just one shell (yash)\n  doesn't.\n* `QRK_EMPTPPWRD`: [POSIX says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02)\n  that empty `\"$@\"` generates zero fields but empty `''` or `\"\"` or\n  `\"$emptyvariable\"` generates one empty field. But it leaves unspecified\n  whether something like `\"$@$emptyvariable\"` generates zero fields or one\n  field. Zsh, pdksh/mksh and (d)ash generate one field, as seems logical.\n  But bash, AT\u0026T ksh and yash generate zero fields, which we consider a\n  quirk. (See also BUG_PP_01)\n* `QRK_EVALNOOPT`: `eval` does not parse options, not even `--`, which makes it\n  incompatible with other shells: on the one hand, (d)ash does not accept   \n  `eval -- \"$command\"` whereas on other shells this is necessary if the command\n  starts with a `-`, or the command would be interpreted as an option to `eval`.\n  A simple workaround is to prefix arbitrary commands with a space.\n  [Both situations are POSIX compliant](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_19_16),\n  but since they are incompatible without a workaround,the minority situation\n  is labeled here as a QuiRK.\n* `QRK_EXECFNBI`: In pdksh and zsh, `exec` looks up shell functions and\n  builtins before external commands, and if it finds one it does the\n  equivalent of running the function or builtin followed by `exit`. This\n  is probably a bug in POSIX terms; `exec` is supposed to launch a\n  program that overlays the current shell, implying the program launched by\n  `exec` is always external to the shell. However, since the\n  [POSIX language](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_20)\n  is rather\n  [vague and possibly incorrect](https://www.mail-archive.com/austin-group-l@opengroup.org/msg01437.html),\n  this is labeled as a shell quirk instead of a shell bug.\n* `QRK_FNRDREXIT`: On FreeBSD sh and NetBSD sh, an error in a redirection\n  attached to a function call causes the shell to exit. This affects\n  redirections of all functions, including modernish library functions\n  as well as functions set by [`harden`](#user-content-use-syscmdharden).\n* `QRK_GLOBDOTS`: Pathname expansion of `.*` matches the pseudonames `.` and\n  `..` so that, e.g., `cp -pr .* backup/` cannot be used to copy all your\n  hidden files. (bash \\\u003c 5.2, (d)ash, AT\u0026T ksh != 93u+m, yash)\n* `QRK_HDPARQUOT`: Double **quot**es within certain **par**ameter substitutions in\n  **h**ere-**d**ocuments aren't removed (FreeBSD sh; bosh). For instance, if\n  `var` is set, `${var+\"x\"}` in a here-document yields `\"x\"`, not `x`.\n  [POSIX considers it undefined](https://www.mail-archive.com/austin-group-l@opengroup.org/msg01626.html)\n  to use double quotes there, so they should be avoided for a script to be\n  fully POSIX compatible.\n  (Note this quirk does **not** apply for substitutions that remove patterns,\n  such as `${var#\"$x\"}` and `${var%\"$x\"}`; those are defined by POSIX\n  and double quotes are fine to use.)\n  (Note 2: single quotes produce widely varying behaviour and should never\n  be used within any form of parameter substitution in a here-document.)\n* `QRK_IFSFINAL`: in field splitting, a final non-whitespace `IFS` delimiter\n  character is counted as an empty field (yash \\\u003c 2.42, zsh, pdksh). This is a QRK\n  (quirk), not a BUG, because POSIX is ambiguous on this.\n* `QRK_LOCALINH`: On a shell with `LOCALVARS`, local variables, when declared\n  without assigning a value, inherit the state of their global namesake, if\n  any. (dash, FreeBSD sh)\n* `QRK_LOCALSET`: On a shell with `LOCALVARS`, local variables are immediately set\n  to the empty value upon being declared, instead of being initially without\n  a value. (zsh)\n* `QRK_LOCALSET2`: Like `QRK_LOCALSET`, but *only* if the variable by the\n  same name in the global/parent scope is unset. If the global variable is\n  set, then the local variable starts out unset. (bash 2 and 3)\n* `QRK_LOCALUNS`: On a shell with `LOCALVARS`, local variables lose their local\n  status when unset. Since the variable name reverts to global, this means that\n  *`unset` will not necessarily unset the variable!* (yash, pdksh/mksh. Note:\n  this is actually a behaviour of `typeset`, to which modernish aliases `local`\n  on these shells.)\n* `QRK_LOCALUNS2`: This is a more treacherous version of `QRK_LOCALUNS` that\n  is unique to bash. The `unset` command works as expected when used on a local\n  variable in the same scope that variable was declared in, **however**, it\n  makes local variables global again if they are unset in a subscope of that\n  local scope, such as a function called by the function where it is local.\n  (Note: since `QRK_LOCALUNS2` is a special case of `QRK_LOCALUNS`, modernish\n  will not detect both.)\n  On bash \\\u003e= 5.0, modernish eliminates this quirk upon initialisation\n  by setting `shopt -s localvar_unset`.\n* `QRK_OPTABBR`: Long-form shell option names can be abbreviated down to a\n  length where the abbreviation is not redundant with other long-form option\n  names. (ksh93, yash)\n* `QRK_OPTCASE`: Long-form shell option names are case-insensitive. (yash, zsh)\n* `QRK_OPTDASH`: Long-form shell option names ignore the `-`. (ksh93, yash)\n* `QRK_OPTNOPRFX`: Long-form shell option names use a dynamic `no` prefix for\n  all options (including POSIX ones). For instance, `glob` is the opposite\n  of `noglob`, and `nonotify` is the opposite of `notify`. (ksh93, yash, zsh)\n* `QRK_OPTULINE`: Long-form shell option names ignore the `_`. (yash, zsh)\n* `QRK_PPIPEMAIN`: On zsh \\\u003c= 5.5.1, in all elements of a pipeline, parameter\n  expansions are evaluated in the current environment (with any changes they\n  make surviving the pipeline), though the commands themselves of every\n  element but the last are executed in a subshell. For instance, given unset\n  or empty `v`, in the pipeline `cmd1 ${v:=foo} | cmd2`, the assignment to\n  `v` survives, though `cmd1` itself is executed in a subshell.\n* `QRK_SPCBIXP`: Variable assignments directly preceding\n  [special builtin commands](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14)\n  are exported, and persist as exported. (bash; yash)\n* `QRK_UNSETF`: If 'unset' is invoked without any option flag (-v or -f), and\n  no variable by the given name exists but a function does, the shell unsets\n  the function. (bash)\n\n### Bugs ###\n\nModernish currently identifies and supports the following shell bugs:\n\n* `BUG_ALIASCSHD`: A spurious syntax error occurs if a here-document\n  containing a command substitution is used within two aliases that define a\n  block. The syntax error reporting a missing `}` occurs because the alias\n  terminating the block is not correctly expanded. This bug affects\n  [`var/local`](#user-content-use-varlocal) and\n  [`var/loop`](#user-content-use-varloop)\n  as they define blocks this way. Workaround: make a shell function that\n  handles the here-document and call that shell function from the block/loop\n  instead. Bug found on: dash \\\u003c= 0.5.10.2; Busybox ash \\\u003c= 1.31.1.\n* `BUG_ALIASPOSX`: Running any command \"foo\" in POSIX mode like\n  `POSIXLY_CORRECT=y foo` will globally disable alias expansion on a\n  non-interactive shell (killing modernish), unless POSIX mode is globally\n  enabled. Bug found on bash 4.2 through 5.0.\n  **Note:** on bash versions with this bug, modernish automatically enables\n  POSIX mode to avoid triggering it. A side effect is that process substitution\n  (`PROCSUBST`) isn't available.\n* `BUG_ARITHINIT`: Using unset or empty variables (dash \u003c= 0.5.9.1 on macOS)\n  or unset variables (yash \u003c= 2.44) in arithmetic expressions causes the\n  shell to exit, instead of taking them as a value of zero.\n* `BUG_ARITHLNNO`: The shell supports `$LINENO`, but the variable is\n   considered unset in arithmetic contexts, like `$(( LINENO \u003e 0 ))`.\n   This makes it error out under `set -u` and default to zero otherwise.\n   Workaround: use shell expansion like `$(( $LINENO \u003e 0 ))`. (FreeBSD sh)\n* `BUG_ARITHNAN`: The case-insensitive special floating point constants\n   `Inf` and `NaN` are recognised in arithmetic evaluation, overriding any\n   variables with the names `Inf`, `NaN`, `INF`, `nan`, etc. (AT\u0026T ksh93;\n   zsh 5.6 - 5.8)\n* `BUG_ARITHSPLT`: Unquoted `$((`arithmetic expressions`))` are not\n  subject to field splitting as expected. (zsh, mksh\u003c=R49)\n* `BUG_ASGNCC01`: if `IFS` contains a `$CC01` (`^A`) character, unquoted expansions in\n  shell assignments discard that character (if present). Found on: bash 4.0-4.3\n* `BUG_ASGNLOCAL`: If you have a function-local variable (see `LOCALVARS`)\n  with the same name as a global variable, and within the function you run a\n  shell builtin command preceded by a temporary variable assignment, then\n  the global variable is unset. (zsh \\\u003c= 5.7.1)\n* `BUG_BRACQUOT`: shell quoting within bracket patterns has no effect (zsh \u003c 5.3;\n  ksh93) This bug means the `-` retains it special meaning of 'character\n  range', and an initial `!` (and, on some shells, `^`) retains the meaning of\n  negation, even in quoted strings within bracket patterns, including quoted\n  variables.\n* `BUG_CASEEMPT`: An empty `case` list on a single line, as in `case x in esac`,\n  is a syntax error. (AT\u0026T ksh93)\n* `BUG_CASELIT`: If a `case` pattern doesn't match as a pattern, it's tried\n  again as a literal string, even if the pattern isn't quoted. This can\n  result in false positives when a pattern doesn't match itself, like with\n  bracket patterns. This contravenes POSIX and breaks use cases such as\n  input validation. (AT\u0026T ksh93) Note: modernish `match` works around this.\n* `BUG_CASEPAREN`: `case` patterns without an opening parenthesis\n  (i.e. with only an unbalanced closing parenthesis) are misparsed\n  as a syntax error within command substitutions of the form `$( )`.\n  Workaround: include the opening parenthesis. Found on: bash 3.2\n* `BUG_CASESTAT`: The `case` conditional construct prematurely clobbers the\n  exit status `$?`. (found in zsh \\\u003c 5.3, Busybox ash \\\u003c= 1.25.0, dash \\\u003c\n  0.5.9.1)\n* `BUG_CDNOLOGIC`: The `cd` built-in command lacks the POSIX-specified `-L`\n  option and does not support logical traversal; it always acts as if the `-P`\n  (physical traversal) option was passed. This also renders the `-L` option\n  to modernish [`chdir`](#user-content-chdir) ineffective. (NetBSD sh)\n* `BUG_CDPCANON`: `cd -P` (and hence also modernish\n  [`chdir`](#user-content-chdir)) does not correctly canonicalise/normalise a\n  directory path that starts with three or more slashses; it reduces these to\n  two initial slashes instead of one in `$PWD`. (zsh \\\u003c= 5.7.1)\n* `BUG_CMDEXEC`: using `command exec` (to open a file descriptor, using\n  `command` to avoid exiting the shell on failure) within a function causes\n  bash \\\u003c= 4.0 to fail to restore the global positional parameters when\n  leaving that function. It also renders bash \\\u003c=4.0 prone to hanging.\n* `BUG_CMDEXPAN`: if the `command` command results from an expansion, it acts\n  like `command -v`, showing the path of the command instead of executing it.\n  For example: `v=command; \"$v\" ls` or `set -- command ls; \"$@\"` don't work.\n  (AT\u0026T ksh93)\n* `BUG_CMDOPTEXP`: the `command` builtin does not recognise options if they\n  result from expansions. For instance, you cannot conditionally store `-p`\n  in a variable like `defaultpath` and then do `command $defaultpath\n  someCommand`. (found in zsh \\\u003c 5.3)\n* `BUG_CMDPV`: `command -pv` does not find builtins ({pd,m}ksh), does not\n  accept the -p and -v options together (zsh \\\u003c 5.3) or ignores the `-p`\n  option altogether (bash 3.2); in any case, it's not usable to find commands\n  in the default system PATH.\n* `BUG_CMDSETPP`: using `command set --` has no effect; it does not set the\n  positional parameters. For compat, use `set` without `command`. (mksh \\\u003c= R57)\n* `BUG_CMDSPASGN`: preceding a\n  [special builtin](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14)\n  with `command` does not stop preceding invocation-local variable\n  assignments from becoming global. (AT\u0026T ksh93)\n* `BUG_CMDSPEXIT`: preceding a\n  [special builtin](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14)\n  (other than `eval`, `exec`, `return` or `exit`)\n  with `command` does not always stop\n  it from exiting the shell if the builtin encounters error.\n  (bash \\\u003c= 4.0; zsh \\\u003c= 5.2; mksh; ksh93)\n* `BUG_CSNHDBKSL`: Backslashes within non-expanding here-documents within\n  command substitutions are incorrectly expanded to perform newline joining,\n  as opposed to left intact. (bash \\\u003c= 4.4)\n* `BUG_CSUBBTQUOT`: A spurious syntax erorr is thrown when using double\n  quotes within a backtick-style command substitution that is itself within\n  double quotes. (AT\u0026T ksh93 \\\u003c 93u+m 2022-05-20)\n* `BUG_CSUBLNCONT`: Backslash line continuation is not processed correctly\n  within modern-form `$(`command substitutions`)`.\n  (AT\u0026T ksh93 \\\u003c 93u+m 2022-05-21)\n* `BUG_CSUBRMLF`: A bug affecting the stripping of final linefeeds from\n  command substitutions. If a command substitution does not produce any\n  output to substitute **and** is concatenated in a string or here-document,\n  then the shell removes any concurrent linefeeds occurring directly before\n  the command substitution in that string or here-document.\n  (dash \\\u003c= 0.5.10.2, Busybox ash, FreeBSD sh)\n* `BUG_CSUBSTDO`: If standard output (file descriptor 1) is closed before\n  entering a command substitution, and any other file descriptors are\n  redirected within the command substitution, commands such as `echo` or\n  `putln` will not work within the command substitution, acting as if standard\n  output is still closed (AT\u0026T ksh93 \\\u003c= AJM 93u+ 2012-08-01). Workaround: see\n  [`cap/BUG_CSUBSTDO.t`](https://github.com/modernish/modernish/blob/master/lib/modernish/cap/BUG_CSUBSTDO.t).\n* `BUG_DEVTTY`: the shell can't redirect output to `/dev/tty` if\n  `set -C`/`set -o noclobber` (part of [safe mode](#user-content-use-safe))\n  is active. Workaround: use `\u003e| /dev/tty` instead of `\u003e /dev/tty`.\n  Bug found on: bash on certain systems (at least QNX and Interix).\n* `BUG_DOLRCSUB`: parsing problem where, inside a command substitution of\n  the form `$(...)`, the sequence `$$'...'` is treated as `$'...'` (i.e. as\n  a use of CESCQUOT), and `$$\"...\"` as `$\"...\"` (bash-specific translatable\n  string). (Found in bash up to 4.4)\n* `BUG_DQGLOB`: *glob*bing is not properly deactivated within\n  *d*ouble-*q*uoted strings. Within double quotes, a `*` or `?` immediately\n  following a backslash is interpreted as a globbing character. This applies\n  to both pathname expansion and pattern matching in `case`. Found in: dash.\n  (The bug is not triggered when using modernish\n  [`match`](#user-content-string-tests).)\n* `BUG_EXPORTUNS`: Setting the export flag on an otherwise unset variable\n  causes a set and empty environment variable to be exported, though the\n  variable continues to be considered unset within the current shell.\n  (FreeBSD sh \\\u003c 13.0)\n* `BUG_FNSUBSH`: Function definitions within subshells (including command\n  substitutions) are ignored if a function by the same name exists in the\n  main shell, so the wrong function is executed. `unset -f` is also silently\n  ignored. ksh93 (all current versions as of November 2018) has this bug.\n  It only applies to non-forked subshells. See `NONFORKSUBSH`.\n* `BUG_FORLOCAL`: a `for` loop in a function makes the iteration variable\n  local to the function, so it won't survive the execution of the function.\n  Found on: yash. This is intentional and documented behaviour on yash in\n  non-POSIX mode, but in POSIX terms it's a bug, so we mark it as such.\n* `BUG_GETOPTSMA`: The `getopts` builtin leaves a `:` instead of a `?` in\n  the specified option variable if a given option that requires an argument\n  lacks an argument, and the option string does not start with a `:`. (zsh)\n* `BUG_HDOCBKSL`: Line continuation using *b*ac*ksl*ashes in expanding\n  *h*ere-*doc*uments is handled incorrectly. (zsh up to 5.4.2)\n* `BUG_HDOCMASK`: Here-documents (and here-strings, see `HERESTRING`) use\n  temporary files. This fails if the current `umask` setting disallows the\n  user to read, so the here-document can't read from the shell's temporary\n  file. Workaround: ensure user-readable `umask` when using here-documents.\n  (bash, mksh, zsh)\n* `BUG_IFSCC01PP`: If `IFS` contains a `$CC01` (`^A`) control character, the\n  expansion `\"$@\"` (even quoted) is gravely corrupted. *Since many modernish\n  functions use this to loop through the positional parameters, this breaks\n  the library.* (Found in bash \\\u003c 4.4)\n* `BUG_IFSGLOBC`: In glob pattern matching (such as in `case` and `[[`), if a\n  wildcard character is part of `IFS`, it is matched literally instead of as a\n  matching character. This applies to glob characters `*`, `?`, `[` and `]`.\n  *Since nearly all modernish functions use `case` for argument validation and\n  other purposes, nearly every modernish function breaks on shells with this\n  bug if `IFS` contains any of these three characters!*\n  (Found in bash \\\u003c 4.4)\n* `BUG_IFSGLOBP`: In pathname expansion (filename globbing), if a\n  wildcard character is part of `IFS`, it is matched literally instead of as a\n  matching character. This applies to glob characters `*`, `?`, `[` and `]`.\n  (Bug found in bash, all versions up to at least 4.4)\n* `BUG_IFSGLOBS`: in glob pattern matching (as in `case` or parameter\n  substitution with `#` and `%`), if `IFS` starts with `?` or `*` and the\n  `\"$*\"` parameter expansion inserts any `IFS` separator characters, those\n  characters are erroneously interpreted as wildcards when quoted \"$*\" is\n  used as the glob pattern. (AT\u0026T ksh93)\n* `BUG_IFSISSET`: AT\u0026T ksh93 (2011/2012 versions): `${IFS+s}` always yields `s`\n  even if `IFS` is unset. This applies to `IFS` only.\n* `BUG_ISSETLOOP`: AT\u0026T ksh93: Expansions like `${var+set}`\n  remain static when used within a `for`, `while` or\n  `until` loop; the expansions don't change along with the state of the\n  variable, so they cannot be used to check whether a variable is set\n  within a loop if the state of that variable may change\n  in the course of the loop.\n* `BUG_KBGPID`: AT\u0026T ksh93: If a single command ending in `\u0026` (i.e. a background\n  job) is enclosed in a `{` braces`; }` block with an I/O redirection, the `$!`\n  special parameter is not set to the background job's PID.\n* `BUG_KUNSETIFS`: AT\u0026T ksh93: Unsetting `IFS` fails to activate default field\n  splitting if the following conditions are met: 1. `IFS` is set and empty\n  (i.e. split is disabled) in the main shell, and at least one expansion has\n  been processed with that setting; 2. The code is currently executing in a\n  non-forked subshell (see [`NONFORKSUBSH`](#user-content-capabilities)).\n* `BUG_LNNONEG`: `$LINENO` becomes wildly inaccurate, even negative, when\n  dotting/sourcing scripts. Bug found on: dash with LINENO support compiled in.\n* `BUG_LOOPRET1`: If a `return` command is given with a status argument within\n  the set of conditional commands in a `while` or `until` loop (i.e., between\n  `while`/`until` and `do`), the status argument is ignored and the function\n  returns with status 0 instead of the specified status.\n  Found on: dash \\\u003c= 0.5.8; zsh \\\u003c= 5.2\n* `BUG_LOOPRET2`: If a `return` command is given without a status argument\n  within the set of conditional commands in a `while` or `until` loop (i.e.,\n  between `while`/`until` and `do`), the exit status passed down from the\n  previous command is ignored and the function returns with status 0 instead.\n  Found on: dash \\\u003c= 0.5.10.2; AT\u0026T ksh93; zsh \\\u003c= 5.2\n* `BUG_LOOPRET3`: If a `return` command is given within the set of conditional\n  commands in a `while` or `until` loop (i.e., between `while`/`until` and\n  `do`), *and* the return status (either the status argument to `return` or the\n  exit status passed down from the previous command by `return` without a\n  status argument) is non-zero, *and* the conditional command list itself yields\n  false (for `while`) or true (for `until`), *and* the whole construct is\n  executed in a dot script sourced from another script, then too many levels of\n  loop are broken out of, causing **program flow corruption** or premature exit.\n  Found on: zsh \\\u003c= 5.7.1\n* `BUG_MULTIBIFS`: We're on a UTF-8 locale and the shell supports UTF-8\n  characters in general (i.e. we don't have `WRN_MULTIBYTE`) – however, using\n  multi-byte characters as `IFS` field delimiters still doesn't work. For\n  example, `\"$*\"` joins positional parameters on the first byte of `IFS`\n  instead of the first character. (ksh93, mksh, FreeBSD sh, Busybox ash)\n* `BUG_NOCHCLASS`: POSIX-mandated character `[:`classes`:]` within bracket\n  `[`expressions`]` are not supported in glob patterns. (mksh)\n* `BUG_NOEXPRO`: Cannot export read-only variables. (zsh \u003c= 5.7.1 in sh mode)\n* `BUG_OPTNOLOG`: on dash, setting `-o nolog` causes `$-` to wreak havoc:\n  trying to expand `$-` silently aborts parsing of an entire argument,\n  so e.g. `\"one,$-,two\"` yields `\"one,\"`. (Same applies to `-o debug`.)\n* `BUG_PP_01`: [POSIX says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02)\n  that empty `\"$@\"` generates zero fields but empty `''` or `\"\"` or\n  `\"$emptyvariable\"` generates one empty field. This means concatenating\n  `\"$@\"` with one or more other, separately quoted, empty strings (like\n  `\"$@\"\"$emptyvariable\"`) should still produce one empty field. But on\n  bash 3.x, this erroneously produces zero fields. (See also QRK_EMPTPPWRD)\n* `BUG_PP_02`: Like `BUG_PP_01`, but with unquoted `$@` and only\n  with `\"$emptyvariable\"$@`, not `$@\"$emptyvariable\"`.\n  (mksh \\\u003c= R50f; FreeBSD sh \\\u003c= 10.3)\n* `BUG_PP_03`: When `IFS` is unset or empty (zsh 5.3.x) or empty (mksh \\\u003c= R50),\n  assigning `var=$*` only assigns the first field, failing to join and\n  discarding the rest of the fields. Workaround: `var=\"$*\"`\n  (POSIX leaves `var=$@`, etc. undefined, so we don't test for those.)\n* `BUG_PP_03A`: When `IFS` is unset, assignments like `var=$*`\n  incorrectly remove leading and trailing spaces (but not tabs or\n  newlines) from the result. Workaround: quote the expansion. Found on:\n  bash 4.3 and 4.4.\n* `BUG_PP_03B`: When `IFS` is unset, assignments like `var=${var+$*}`,\n  etc. incorrectly remove leading and trailing spaces (but not tabs or\n  newlines) from the result. Workaround: quote the expansion. Found on:\n  bash 4.3 and 4.4.\n* `BUG_PP_03C`: When `IFS` is unset, assigning `var=${var-$*}` only assigns\n  the first field, failing to join and discarding the rest of the fields.\n  (zsh 5.3, 5.3.1) Workaround: `var=${var-\"$*\"}`\n* `BUG_PP_04A`: Like BUG_PP_03A, but for conditional assignments within\n  parameter substitutions, as in `: ${var=$*}` or `: ${var:=$*}`.\n  Workaround: quote either `$*` within the expansion or the expansion\n  itself. (bash \\\u003c= 4.4)\n* `BUG_PP_04E`: When assigning the positional parameters ($*) to a variable\n  using a conditional assignment within a parameter substitution, e.g.\n  `: ${var:=$*}`, the fields are always joined and separated by spaces,\n  except if `IFS` is set and empty. Workaround as in BUG_PP_04A.\n  (bash 4.3)\n* `BUG_PP_04_S`: When `IFS` is null (empty), the result of a substitution\n  like `${var=$*}` is incorrectly field-split on spaces.\n  The assignment itself succeeds normally.\n  Found on: bash 4.2, 4.3\n* `BUG_PP_05`: [POSIX says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02)\n  that empty `$@` and `$*` generate zero fields, but with null `IFS`, empty\n  unquoted `$@` and `$*` yield one empty field. Found on: dash 0.5.9\n  and 0.5.9.1; Busybox ash.\n* `BUG_PP_06A`: [POSIX says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02)\n  that unquoted `$@` and `$*` initially generate as many fields as there are\n  positional parameters, and then (because `$@` or `$*` is unquoted) each field is\n  split further according to `IFS`. With this bug, the latter step is not\n  done if `IFS` is unset (i.e. default split). Found on: zsh \\\u003c 5.4\n* `BUG_PP_07`: unquoted `$*` and `$@` (including in substitutions like\n  `${1+$@}` or `${var-$*}`) do not perform default field splitting if\n  `IFS` is unset. Found on: zsh (up to 5.3.1) in sh mode\n* `BUG_PP_07A`: When `IFS` is unset, unquoted `$*` undergoes word splitting\n  as if `IFS=' '`, and not the expected `IFS=\" ${CCt}${CCn}\"`.\n  Found on: bash 4.4\n* `BUG_PP_08`: When `IFS` is empty, unquoted `$@` and `$*` do not generate\n  one field for each positional parameter as expected, but instead join\n  them into a single field without a separator. Found on: yash \\\u003c 2.44\n  and dash \\\u003c 0.5.9 and Busybox ash \\\u003c 1.27.0\n* `BUG_PP_08B`: When `IFS` is empty, unquoted `$*` within a substitution (e.g.\n  `${1+$*}` or `${var-$*}`) does not generate one field for each positional\n  parameter as expected, but instead joins them into a single field without\n  a separator. Found on: bash 3 and 4\n* `BUG_PP_09`: When `IFS` is non-empty but does not contain a space,\n  unquoted `$*` within a substitution (e.g. `${1+$*}` or `${var-$*}`) does\n  not generate one field for each positional parameter as expected,\n  but instead joins them into a single field separated by spaces\n  (even though, as said, `IFS` does not contain a space).\n  Found on: bash 4.3\n* `BUG_PP_10`: When `IFS` is null (empty), assigning `var=$*` removes any\n  `$CC01` (^A) and `$CC7F` (DEL) characters. (bash 3, 4)\n* `BUG_PP_10A`: When `IFS` is non-empty, assigning `var=$*` prefixes each\n  `$CC01` (^A) and `$CC7F` (DEL) character with a `$CC01` character. (bash 4.4)\n* `BUG_PP_1ARG`: When `IFS` is empty on bash \u003c= 4.3 (i.e. field\n  splitting is off), `${1+\"$@\"}` or `\"${1+$@}\"` is counted as a single\n  argument instead of each positional parameter as separate arguments.\n  This also applies to prepending text only if there are positional\n  parameters with something like `\"${1+foobar $@}\"`.\n* `BUG_PP_MDIGIT`: Multiple-digit positional parameters don't require expansion\n  braces, so e.g. `$10` = `${10}` (dash; Busybox ash). This is classed as a bug\n  because it causes a straight-up incompatibility with POSIX scripts. POSIX\n  [says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02):\n  \"The parameter name or symbol can be enclosed in braces, which are\n  optional except for positional parameters with more than one digit [...]\".\n* `BUG_PP_MDLEN`: For `${#x}` expansions where x \u003e= 10, only the first digit of\n  the positional parameter number is considered, e.g. `${#10}`, `${#12}`,\n  `${#123}` are all parsed as if they are `${#1}`. Then, string parsing is\n  aborted so that further characters or expansions, if any, are lost.\n  Bug found in: dash 0.5.11 - 0.5.11.4 (fixed in dash 0.5.11.5)\n* `BUG_PSUBASNCC`: in an assignment parameter substitution of the form\n  `${foo=value}`, if the characters `$CC01` (^A) or `$CC7F` (DEL) are in the\n  value, all their occurrences are stripped from the expansion (although the\n  assignment itself is done correctly). If the expansion is quoted, only\n  `$CC01` is stripped. This bug is independent of the state of `IFS`, except if\n  `IFS` is null, the assignment in `${foo=$*}` (unquoted) is buggy too: it\n  strips `$CC01` from the assigned value. (Found on bash 4.2, 4.3, 4.4)\n* `BUG_PSUBBKSL1`: A backslash-escaped `}` character within a quoted parameter\n  substitution is not unescaped. (bash 3.2, dash \\\u003c= 0.5.9.1, Busybox 1.27 ash)\n* `BUG_PSUBEMIFS`: if `IFS` is empty (no split, as in safe mode), then if a\n  parameter substitution of the forms `${foo-$*}`, `${foo+$*}`, `${foo:-$*}` or\n  `${foo:+$*}` occurs in a command argument, the characters `$CC01` (^A) or\n  `$CC7F` (DEL) are stripped from the expanded argument. (Found on: bash 4.4)\n* `BUG_PSUBEMPT`: Expansions of the form `${V-}` and `${V:-}` are not\n  subject to normal shell empty removal if that parameter is unset, causing\n  unexpected empty arguments to commands. Workaround: `${V+$V}` and\n  `${V:+$V}` work as expected. (Found on FreeBSD 10.3 sh)\n* `BUG_PSUBIFSNW`: When field-splitting unquoted parameter substitutions like\n  `${var#foo}`, `${var##foo}`, `${var%foo}` or `${var%%foo}` on non-whitespace\n  `IFS`, if there is an initial empty field, a spurious extra initial empty\n  field is generated. (mksh)\n* `BUG_PSUBNEWLN`: Due to a bug in the parser, parameter substitutions\n  spread over more than one line cause a syntax error.\n  Workaround: instead of a literal newline, use [`$CCn`](#user-content-control-character-whitespace-and-shell-safe-character-constants).\n  (found in dash \\\u003c= 0.5.9.1 and Busybox ash \\\u003c= 1.28.1)\n* `BUG_PSUBSQUOT`: in pattern matching parameter substitutions\n  (`${param#pattern}`, `${param%pattern}`, `${param##pattern}` and\n  `${param%%pattern}`), if the whole parameter substitution is quoted with\n  double quotes, then single quotes in the *pattern* are not parsed. POSIX\n  [says](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02)\n  they are to keep their special meaning, so that glob characters may\n  be quoted. For example: `x=foobar; echo \"${x#'foo'}\"` should yield `bar`\n  but with this bug yields `foobar`. (dash \\\u003c= 0.5.9.1; Busybox 1.27 ash)\n* `BUG_PSUBSQHD`: Like BUG_PSUBSQUOT, but included a here-document instead of\n  quoted with double quotes. (dash \\\u003c= 0.5.9.1; mksh)\n* `BUG_PUTIOERR`: Shell builtins that output strings (`echo`, `printf`, ksh/zsh\n  `print`), and thus also modernish `put` and `putln`, do not check for I/O\n  errors on output. This means a script cannot check for them, and a script\n  process in a pipe can get stuck in an infinite loop if `SIGPIPE` is ignored.\n* `BUG_READWHSP`: If there is more than one field to read, `read` does not\n   trim trailing `IFS` whitespace. (dash 0.5.7, 0.5.8)\n* `BUG_REDIRIO`: the I/O redirection operator `\u003c\u003e` (open a file descriptor\n  for both read and write) defaults to opening standard output (i.e. is\n  short for `1\u003c\u003e`) instead of defaulting to opening standard input (`0\u003c\u003e`) as\n  [POSIX specifies](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_07).\n  (AT\u0026T ksh93)\n* `BUG_REDIRPOS`: Buggy behaviour occurs if a *redir*ection is *pos*itioned\n  in between to variable assignments in the same command. On zsh 5.0.x, a\n  parse error is thrown. On zsh 5.1 to 5.4.2, anything following the\n  redirection (other assignments or command arguments) is silently ignored.\n* `BUG_SCLOSEDFD`: bash \\\u003c 5.0 and dash fail to establish a block-local scope\n  for a file descriptor that is added to the end of the block as a redirection\n  that closes that file descriptor (e.g. `} 8\u003c\u0026-` or `done 7\u003e\u0026-`). If that FD\n  is already closed outside the block, the FD remains global, so you can't\n  locally `exec` it. So with this bug, it is not straightforward to make a\n  block-local FD appear initially closed within a block. Workaround: first open\n  the FD, then close it – for example: `done 7\u003e/dev/null 7\u003e\u0026-` will establish\n  a local scope for FD 7 for the preceding `do`...`done` block while still\n  making FD 7 appear initially closed within the block.\n* `BUG_SETOUTVAR`: The `set` builtin (with no arguments) only prints native\n  function-local variables when called from a shell function. (yash \\\u003c= 2.46)\n* `BUG_SHIFTERR0`: The `shift` builtin silently returns a successful exit\n  status (0) when attempting to shift a number greater than the current\n  amount of positional parameters. (Busybox ash \\\u003c= 1.28.4)\n* `BUG_SPCBILOC`: Variable assignments preceding\n  [special builtins](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14)\n  create a partially function-local variable if a variable by the same name\n  already exists in the global scope. (bash \\\u003c 5.0 in POSIX mode)\n* `BUG_TESTERR1A`: `test`/`[` exits with a non-error `false` status\n  (1) if an invalid argument is given to an operator. (AT\u0026T ksh93)\n* `BUG_TESTILNUM`: On dash (up to 0.5.8), giving an illegal number to `test -t`\n  or `[ -t` causes some kind of corruption so the next `test`/`[` invocation\n  fails with an \"unexpected operator\" error even if it's legit.\n* `BUG_TESTONEG`: The `test`/`[` builtin supports a `-o` unary operator to\n  check if a shell option is set, but it ignores the `no` prefix on shell\n  option names, so something like `[ -o noclobber ]` gives a false positive.\n  Bug found on yash up to 2.43. (The `TESTO` feature test implicitly checks\n  against this bug and won't detect the feature if the bug is found.)\n* `BUG_TRAPEMPT`: The `trap` builtin does not quote empty traps in its\n  output, rendering the output unsuitable for shell re-input. For instance,\n  `trap '' INT; trap` outputs \"`trap --  INT`\" instead of \"`trap -- '' INT`\".\n  (found in mksh \\\u003c= R56c)\n* `BUG_TRAPEXIT`: the shell's `trap` builtin does not know the EXIT trap by\n  name, but only by number (0). Using the name throws a \"bad trap\" error. Found in\n  [klibc 2.0.4 dash](https://git.kernel.org/pub/scm/libs/klibc/klibc.git/tree/usr/dash).\n* `BUG_TRAPFNEXI`: When a function issues a signal whose trap exits the\n  shell, the shell is not exited immediately, but only on return from the\n  function. (zsh)\n* `BUG_TRAPRETIR`: Using `return` within `eval` triggers infinite recursion if\n  both a RETURN trap and the `functrace` shell option are active. This bug in\n  bash-only functionality triggers a crash when using modernish, so to avoid\n  this, modernish automatically disables the `functrace` shell option if a\n  `RETURN` trap is set or pushed and this bug is detected. (bash 4.3, 4.4)\n* `BUG_TRAPSUB0`: Subshells in traps fail to pass down a nonzero exit status of\n  the last command they execute, under certain conditions or consistently,\n  depending on the shell. (bash \\\u003c= 4.0; dash 0.5.9 - 0.5.10.2; yash \\\u003c= 2.47)\n* `BUG_TRAPUNSRE`: When a trap *uns*ets itself and then *re*sends its own signal,\n  the execution of the trap action (including functions called by it) is\n  not interrupted by the now-untrapped signal; instead, the process\n  terminates after completing the entire trap routine. (bash \\\u003c= 4.2; zsh)\n* `BUG_UNSETUNXP`: If an unset variable is given the export flag using the\n  `export` command, a subsequent `unset` command does not remove that export\n  flag again. Workaround: assign to the variable first, then unset it to\n  unexport it. (Found on AT\u0026T ksh JM-93u-2011-02-08; Busybox 1.27.0 ash)\n* `BUG_VARPREFIX`: On a shell with the `VARPREFIX` feature, expansions of type\n  `${!`*prefix*`@}` and `${!`*prefix*`*}` do not find the variable name\n  *prefix* itself. (AT\u0026T ksh93)\n* `BUG_ZSHNAMES`: A series of lowerase names, normally okay for script use\n  as per POSIX convention, is reserved for special use. Unsetting these\n  names is impossible in most cases, and changing them may corrupt important\n  shell or system settings. This may conflict with\n  [simple-form](#user-content-simple-form) modernish scripts.\n  This bug is detected on zsh when it was not initially invoked in emulation\n  mode, and emulation mode was enabled using `emulate sh` post invocation\n  instead (which does not disable these conflicting parameters).\n  As of zsh 5.6, the list of variable names affected is: `aliases` `argv`\n  `builtins` `cdpath` `commands` `dirstack` `dis_aliases` `dis_builtins`\n  `dis_functions` `dis_functions_source` `dis_galiases` `dis_patchars`\n  `dis_reswords` `dis_saliases` `fignore` `fpath` `funcfiletrace`\n  `funcsourcetrace` `funcstack` `functions` `functions_source` `functrace`\n  `galiases` `histchars` `history` `historywords` `jobdirs` `jobstates`\n  `jobtexts` `keymaps` `mailpath` `manpath` `module_path` `modules` `nameddirs`\n  `options` `parameters` `patchars` `path` `pipestatus` `prompt` `psvar`\n  `reswords` `saliases` `signals` `status` `termcap` `terminfo` `userdirs`\n  `usergroups` `watch` `widgets` `zsh_eval_context` `zsh_scheduled_events`\n* `BUG_ZSHNAMES2`: Two lowercase variable names `histchars` and `signals`,\n  normally okay for script use as per POSIX convention, are reserved for\n  special use on zsh, *even* if zsh is initialised in sh mode (via a `sh`\n  symlink or using the `--emulate sh` option at startup).\n  Bug found on: zsh \u003c= 5.7.1. The bug is only detected if `BUG_ZSHNAMES` is\n  not detected, because this bug's effects are included in that one's.\n\n### Warning IDs ###\n\nWarning IDs do not identify any characteristic of the shell, but instead\nwarn about a potentially problematic system condition that was detected at\ninitialisation time.\n\n* `WRN_EREMBYTE`: The current system locale setting supports Unicode UTF-8\n  multi-byte/variable-length characters, but the utility used by\n  [`str ematch`](#user-content-string-tests)\n  to match extended regular expressions (EREs) does not support them\n  and treats all characters as single bytes. This means multi-byte characters\n  will be matched as multiple characters, and character `[:`classes`:]`\n  within bracket expressions will only match ASCII characters.\n* `WRN_MULTIBYTE`: The current system locale setting supports Unicode UTF-8\n  multi-byte/variable-length characters, but the current shell does not\n  support them and treats all characters as single bytes. This means\n  counting or processing multi-byte characters with the current shell will\n  produce incorrect results. Scripts that need compatibility with this\n  system condition should check `if thisshellhas WRN_MULTIBYTE` and resort\n  to a workaround that uses external utilities where necessary.\n* `WRN_NOSIGPIPE`: Modernish has detected that the process that launched\n  the current program has set `SIGPIPE` to ignore, an irreversible condition\n  that is in turn inherited by any process started by the current shell, and\n  their subprocesses, and so on. The system constant\n  [`$SIGPIPESTATUS`](#user-content-modernish-system-constants)\n  is set to the special value 99999 and neither the current shell nor any\n  process it spawns is now capable of receiving `SIGPIPE`. The\n  [`-P` option to `harden`](#hardening-while-allowing-for-broken-pipes)\n  is also rendered ineffective.\n  Depending on how a given command `foo` is implemented, it is now possible\n  that a pipeline such as `foo | head -n 10` never ends; if `foo` doesn't\n  check for I/O errors, the only way it would ever stop trying to write\n  lines is by receiving `SIGPIPE` as `head` terminates.\n  Programs that use commands in this fashion should check `if thisshellhas\n  WRN_NOSIGPIPE` and either employ workarounds or refuse to run if so.\n\n\n## Appendix B: Regression test suite ##\n\nModernish comes with a suite of regression tests to detect bugs in modernish\nitself, which can be run using `modernish --test` after installation. By\ndefault, it will run all the tests verbosely but without tracing the command\nexecution. The `install.sh` installer will run `modernish --test -eqq` on the\nselected shell before installation.\n\nA few options are available to specify after `--test`:\n\n* `-h`: show help.\n* `-e`: disable or reduce expensive (i.e. slow or memory-hogging) tests.\n* `-q`: quieter operation; report expected fails [known shell bugs]\n  and unexpected fails [bugs in modernish]). Add `-q` again for\n  quietest operation (report unexpected fails only).\n* `-s`: entirely silent operation.\n* `-t`: run only specific test sets or tests. Test sets are those listed\n  in the full default output of `modernish --test`. This option requires\n  an option-argument in the following format:    \n  *testset1*`:`*num1*`,`*num2*`,`…`/`*testset2*`:`*num1*`,`*num2*`,`…`/`…    \n  The colon followed by numbers is optional; if omitted, the entire set\n  will be run, otherwise the given numbered tests will be run in the given\n  order. Example: `modernish --test -t match:2,4,7/arith/shellquote:1` runs\n  test 2, 4 and 7 from the `match` set, the entire `arith` set, and only\n  test 1 from the `shellquote` set.\n  A *testset* can also be given as the incomplete beginning of a name or as\n  a shell glob pattern. In that case, all matching sets will be run.\n* `-x`: trace each test using the shell's `xtrace` facility. Each trace is\n  stored in a separate file in a specially created temporary directory. By\n  default, the trace is deleted if a test does not produce an unexpected\n  fail. Add `-x` again to keep expected fails as well, and again to\n  keep all traces regardless of result. If any traces were saved,\n  modernish will tell you the location of the temporary directory at the\n  end, otherwise it will silently remove the directory again.\n* `-E`: don't run any tests, but output a command to open the tests that would\n  have been run in your editor. The editor from the `VISUAL` or `EDITOR`\n  environment variable is used, with `vi` as a default. This option should be\n  used together with `-t` to specify tests. All other options are ignored.\n* `-F`: takes an argument with the name or path to a `find` utility to\n  prefer when testing [`LOOP find`](#user-content-the-find-loop).\n  [More info here](#user-content-picking-a-find-utility).\n\nThese short options can be combined so, for example,\n`--test -qxx` is the same as `--test -q -x -x`.\n\n### Difference between capability detection and regression tests ###\n\nNote the difference between these regression tests and the cap tests listed in\n[Appendix A](#user-content-appendix-a-list-of-shell-cap-ids). The latter are\ntests for whatever shell is executing modernish: they detect capabilities\n(features, quirks, bugs) of the current shell. They are meant to be run via\n[`thisshellhas`](#user-content-shell-capability-detection) and are designed to\nbe taken advantage of in scripts. On the other hand, these tests run by\n`modernish --test` are regression tests for modernish itself. It does not\nmake sense to use these in a script.\n\nNew/unknown shell bugs can still cause modernish regression tests to fail,\nof course. That's why some of the regression tests also check for\nconsistency with the results of the capability detection tests: if there is a\nshell bug in a widespread release version that modernish doesn't know about\nyet, this in turn is considered to be a bug in modernish, because one of its\ngoals is to know about all the shell bugs in all released shell versions\ncurrently seeing significant use.\n\n### Testing modernish on all your shells ###\n\nThe `testshells.sh` program in `share/doc/modernish/examples` can be used to\nrun the regression test suite on all the shells installed on your system.\nYou could put it as `testshells` in some convenient location in your\n`$PATH`, and then simply run:\n\n    testshells modernish --test\n\n(adding any further options you like – for instance, you might like to add\n`-q` to avoid very long terminal output). On first run, `testshells` will\ngenerate a list of shells it can find on your system and it will give you a\nchance to edit it before proceeding.\n\n\n## Appendix C: Supported locales ##\n\nmodernish, like most shells, fully supports two system locales: POSIX\n(a.k.a. C, a.k.a. ASCII) and Unicode's UTF-8. It will work in other locales,\nbut things like converting to upper/lower case, and matching single\ncharacters in patterns, are not guaranteed.\n\n*Caveat:* some shells or operating systems have bugs that prevent (or lack\nfeatures required for) full locale support. If portability is a concern,\ncheck for `thisshellhas WRN_MULTIBYTE` or `thisshellhas BUG_NOCHCLASS`\nwhere needed. See [Appendix A](#user-content-appendix-a-list-of-shell-cap-ids).\n\nScripts/programs should *not* change the locale (`LC_*` or `LANG`) after\ninitialising modernish. Doing this might break various functions, as\nmodernish sets specific versions depending on your OS, shell and locale.\n(Temporarily changing the locale is fine as long as you don't use\nmodernish features that depend on it – for example, setting a specific\nlocale just for an external command. However, if you use `harden`, see\nthe [important note](#user-content-important-note-on-variable-assignments)\nin its documentation!)\n\n\n## Appendix D: Supported shells ##\n\nModernish builds on the\n[POSIX 2018 Edition](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html)\nstandard, so it should run on any sufficiently POSIX-compliant shell and\noperating system. It uses both\n[bug/feature detection](#user-content-shell-capability-detection)\nand\n[regression testing](#user-content-appendix-b-regression-test-suite)\nto determine whether it can run on any particular shell, so it does not\nblock or support particular shell versions as such. However, modernish has\nbeen confirmed to run correctly on the following shells:\n\n-   [bash](https://www.gnu.org/software/bash/) 3.2 or higher\n-   [Busybox](https://busybox.net/) ash 1.20.0 or higher, excluding 1.28.x\n    (also possibly excluding anything older than 1.27.x on UTF-8 locales,\n    depending on your operating system)\n-   [dash](http://gondor.apana.org.au/~herbert/dash/) (Debian sh)\n    0.5.7 or higher, *excluding* 0.5.10, 0.5.10.1, 0.5.11-0.5.11.4\n-   [FreeBSD](https://www.freebsd.org/) sh 11.0 or higher\n-   [gwsh](https://github.com/hvdijk/gwsh)\n-   [ksh](http://github.com/ksh93/ksh) 93u+ 2012-08-01, 93u+m\n-   [mksh](http://www.mirbsd.org/mksh.htm) version R55 or higher\n-   [NetBSD](https://www.netbsd.org/) sh 9.0 or higher\n-   [yash](http://yash.osdn.jp/) 2.40 or higher (2.44+ for POSIX mode)\n-   [zsh](http://www.zsh.org/) 5.3 or higher\n\nCurrently known *not* to run modernish due to excessive bugs:\n\n-   bosh ([Schily](http://schilytools.sourceforge.net/) Bourne shell)\n-   [ksh](http://www.kornshell.com/) A 2020.0.0\n-   pdksh, including [NetBSD](https://www.netbsd.org/) ksh and\n    [OpenBSD](https://www.openbsd.org/) ksh\n\n\n## Appendix E: zsh: integration with native scripts ##\n\nThis appendix is specific to [zsh](http://zsh.sourceforge.net/).\n\nWhile modernish duplicates some functionality already available natively\non zsh, it still has plenty to add. However, writing a normal\n[simple-form](#user-content-simple-form) modernish script turns\n`emulate sh` on for the entire script, so you lose important aspects\nof the zsh language.\n\nBut there is another way – modernish functionality may be integrated\nwith native zsh scripts using 'sticky emulation', as follows:\n\n```sh\nemulate -R sh -c '. modernish'\n```\n\nThis causes modernish functions to run in sh mode while your script will still\nrun in native zsh mode with all its advantages. The following notes apply:\n\n* Using the [safe mode](#user-content-use-safe) is *not* recommended, as zsh\n  does not apply split/glob to variable expansions by default, and the\n  modernish safe mode would defeat the `${~var}` and `${=var}` flags that apply\n  these on a case by case basis. This does mean that:\n    * The `--split` and `--glob` operators to constructs such as\n      [`LOOP find`](#user-content-the-find-loop)\n      are not available. Use zsh expansion flags instead.\n    * Quoting literal glob patterns to commands like `find` remains necessary.\n* Using [`LOCAL`](#user-content-use-varlocal) is not recommended.\n  [Anonymous functions](http://zsh.sourceforge.net/Doc/Release/Functions.html#Anonymous-Functions)\n  are the native zsh equivalent.\n* Native zsh loops should be preferred over modernish loops, except where\n  modernish adds functionality not available in zsh (such as `LOOP find` or\n  [user-programmed loops](#user-content-creating-your-own-loop)).\n\nSee `man zshbuiltins` under `emulate`, option `-c`, for more information.\n\n\n## Appendix F: Bundling modernish with your script ##\n\nThe modernish installer `install.sh` can bundle one or more scripts with a\nstripped-down version of the modernish library. This allows the bundled scripts\nto run with a known version of modernish, whether or not modernish is installed\non the user's system. Like modernish itself, bundling is cross-platform and\nportable (or as portable as your script is).\n\nBundled scripts are not modified. Instead, for each script, a wrapper script is\ninstalled under the same name in the installation root directory. This wrapper\nautomatically looks for a [suitable](#user-content-appendix-d-supported-shells)\nPOSIX-compliant shell that passes the modernish battery of fatal bug tests,\nthen sets up the environment to run the real script with modernish on that\nshell. Your modernish script can be run through the supplied wrapper script\nfrom any directory location on any POSIX-compliant operating system, as long as\nall files remain in the same location relative to each other.\n\nBundling is always a non-interactive installer operation, with options\nspecified on the command line. The installer usage for bundling is as follows:\n\n`install.sh` `-B` `-D` *rootdir* [ `-d` *subdir* ]\n[ `-s` *shell* ] *scriptfile* [ *scriptfile* ... ]\n\nThe `-B` option enables bundling mode. The option does not itself take an\noption-argument. Instead, any number of *scriptfile*s to bundle can be given\nas arguments following all other options. All scripts are bundled with a\nsingle copy of modernish. The bundling operation does not deal with any\nauxiliary files the scripts may require (other than modernish modules); any\nsuch need to be added manually after bundling is complete.\n\nThe `-D` option specifies the path to the bundled installation's root\ndirectory, where wrapper scripts are installed. This option is mandatory.\nIf the directory doesn't exist, it is created.\n\nThe `-d` option specifies the subdirectory of the `-D` root directory where the\nbundled scripts and modernish are installed. It can contain slashes to install\nthe bundle at a deeper directory level. The default subdirectory is `bndl`.\nThe option-argument can be empty or `/`, in which case the bundle is installed\ndirectly into the installation root directory.\n\nThe `-s` option specifies a preferred shell for the bundled scripts. A shell\nname or a full path to a shell can be given. Wrapper scripts try the full path\nfirst (if any), then try to find a shell with its basename, and then try to\nfind a shell with that basename minus any version number (e.g. `bash` instead\nof `bash-5.0` or `ksh` instead of `ksh93`). If all that doesn't produce a shell\nthat passes fatal bugs tests, it continues with the normal shell search.\n\nThis means the script won't fail to launch if the preferred shell can't be\nfound. Instead, it is up to the script itself to refuse to run if required\nshell-specific conditions are not met. Script should use the\n[`thisshellhas`](#user-content-shell-capability-detection)\nfunction to check for any nonstandard\n[capabilities](#user-content-capabilities)\nrequired, or any\n[bugs](#user-content-bugs)\nor\n[quirks](#user-content-quirks)\nthat the script is incompatible with (or indeed requires!).\n\nBundling is supported for both\n[portable-form](#user-content-portable-form)\nand\n[simple-form](#user-content-simple-form)\nmodernish scripts. The installer automatically adapts the wrapper scripts to\nthe form used. For simple-form scripts, the directory containing the bundled\nmodernish core library (by default, `.../bndl/bin/modernish`) is prefixed to\n`$PATH` so that `. modernish` works. Since simple-form scripts are often more\nshell-specific, you may want to specify a preferred shell with the `-s` option.\n\nTo save space, the bundled copy of the modernish library is reduced such that\nall comments are stripped from the code,\n[interactive use](#user-content-interactive-use)\nis not supported,\nthe [regression test suite](#user-content-appendix-b-regression-test-suite)\nis not included,\n[`thisshellhas`](#user-content-shell-capability-detection)\ndoes not have the `--cache` and `--show` operators,\nand the\n[`cap/*.t` capability detection scripts](#user-content-appendix-a-list-of-shell-cap-ids)\nare \"statically linked\" (directly included) into bin/modernish instead of\nshipped as separate files.\nA `README.modernish` file is added with a short explanation, the licence,\nand a link for people to get the complete version of modernish. Please do\nnot remove this when distributing bundled scripts.\n\n---\n\n`EOF`\n","funding_links":[],"categories":["Shell","bash","Shell Script Development","Libraries"],"sub_categories":["Reusable Things"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodernish%2Fmodernish","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodernish%2Fmodernish","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodernish%2Fmodernish/lists"}