{"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","funding_links":[],"categories":["Shell","bash","Shell Script Development","Libraries"],"sub_categories":["Reusable Things"],"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#### `shellquotepara","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"}