Modernish: a shell library that adds safe expansion and a find loop
Modernish is a library for writing robust, portable, readable, and powerful programs for POSIX-based shells and utilities.
At a glance
- What is it?
- Modernish is a library written entirely in the shell language that gives POSIX-like shells safer expansion, new loop constructs, a find loop and a safe mode, and it can be bundled with a script or installed system wide. Its front page is a manual index, so the depth is in the sections rather than in the summary.
- Who is it for?
- Modernish fits a team that ships shell scripts to many hosts, where quoting bugs and the absence of useful loop constructs cause real damage, and it fits embedded or locked-down systems where new binaries cannot be introduced.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 22 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A library written in the shell language itself
Modernish presents itself as a library for writing portable and readable programs for POSIX-based shells and utilities, and the project description is more precise still: it is a library for shell script programming providing features like safer variable and command expansion, new language constructs for loop iteration, and much more.
The defining constraint is that there is no compiled code to install, because modernish is written entirely in the shell language. The README states the reason this matters: it can be deployed in embedded or multi-user systems in which new binary executables may not be introduced for security reasons.
That constraint shapes the whole design. The library cannot assume a modern shell, so it has to detect what the running shell can do. The manual has a chapter on shell capability detection, and the project topics name the shells the effort targets, among them ash, bash, dash, ksh, ksh93, mksh, yash and zsh. The stated aim is that your script works on nearly any shell on any Unix-like OS, and the new constructs are mixed with shell syntax so a programmer can take the best of both rather than write in a foreign language.
One more design choice deserves attention. The library can also be bundled: the installer can include a reduced copy of the library with your scripts, so they run portably with a known version of modernish and without requiring a prior installation on the target machine.
Simple form and portable form are two different contracts
The manual organises modernish programs into two basic forms, and the choice between them is the first decision a user makes.
The simple form is the ordinary way of writing a script: the library is expected to be present in the environment, and the program loads it from there. The portable form exists for the case where the script must run on a machine you do not control, and it carries what it needs with it.
The bundling mechanism is what makes the portable form work, and the README describes it as bundling a reduced copy of the library with your scripts, so that they run portably with a known version of modernish and without requiring prior installation. The word reduced matters. What travels with the script is the part of the library that script uses, a reduced copy of the library rather than the whole of modernish.
This is a genuine engineering trade-off and worth weighing before you commit. The simple form keeps deployment trivial, since the script is small and the library is managed once on the host. The portable form removes the dependency on the host entirely, at the cost of a larger script and a duplicated copy of library code in every artefact that uses it. For a script deployed to one controlled fleet, the simple form is usually the saner choice. For a script distributed to arbitrary machines, the portable form is the only one that behaves predictably.
The safe mode is the feature the manual argues hardest for
The `use safe` module has its own chapter, and that chapter is subdivided further into why the safe mode exists, how it works, important notes for safe mode, and extra options for the safe mode. Four sub-sections for one module tells you how much weight the design puts on it.
The reason to care is the set of problems the README opens with. It asks whether you are sick of quoting hell and split and glob pitfalls, tired of brittle shell scripts going haywire and causing damage, mystified by line noise commands like `[`, `[[` and `((`, or simply find that scripting basic things is too hard. Safe mode is the answer to the first three of those, and the emphasis on the scripts going haywire and causing damage is not decorative. Shell quoting failures do not throw an exception, they do the wrong thing quietly.
The same documentation index shows the supporting cast of low-level utilities, and they are named for legibility rather than brevity: legibility aliases `not`, `so` and `forever`, an enhanced `exit`, `chdir`, `insubshell`, `isset` and `setstatus`. A separate chapter covers a reliable emergency halt, which tells you the library treats failure handling as a first-class concern rather than an afterthought.
What you should take from this section is a reading order. The safe mode chapter is where the design rationale is, so read it before you decide whether to enable it, and read the notes for safe mode, since a mode that changes expansion semantics will change how existing code behaves.
The find loop replaces the find utility with a shell loop
One of the README's rhetorical questions is whether you ever wished that `find` were a built-in shell loop. The manual answers it, because the `use var/loop` module includes a find loop with its own chapter.
That chapter is subdivided into available options, available find-expression operands, picking a `find` utility, compatibility mode for obsolete `find` utilities, and find loop usage examples. The structure says something about the design. It is not a reimplementation of find in the abstract; it takes find expressions and applies them, and the chapter even discusses which find utility to pick and how to cope with obsolete ones.
That is the honest engineering position. Different find implementations differ in behaviour and in which expressions they support, so a portable script has to make a choice. Rather than pretending the differences do not exist, the module exposes the choice and provides a compatibility mode for obsolete find utilities, which is what a script that must run on a decade of systems actually needs.
The `use var/loop` module is broader than find. It also covers a simple repeat loop, a BASIC-style arithmetic for loop, a C-style arithmetic for loop, an enumerative for and select loop with safe split and glob, and a section on creating your own loop. The split and glob point is the connection back to the quoting complaints in the opening of the README: iterating over a list of items is where shell scripts traditionally lose their contents to whitespace, and having a construct that does it safely is the feature that pays for the library in the first place.
var/local and var/arith remove the need for (( ))
The arithmetic and variable modules are where the library attacks the line noise complaint directly, since `((` is not POSIX and therefore cannot appear in a script that has to run everywhere.
`use var/arith` provides integer number arithmetic tests and operations, with a chapter for the arithmetic command `let`, plus sections on arithmetic operator shortcuts and arithmetic comparison shortcuts. The design replaces the double-parenthesis syntax with ordinary command invocation and named shortcuts, which means the construct can be read by a reader who does not know the shell-specific extension.
`use var/local` handles scoping, and the manual gives it an explicit notes section, which is fair because local variables are exactly the place where shell implementations disagree. It works together with `use var/assign` and `use var/readf` for assignment and for reading input into variables.
The quoting side is handled by `use var/shellquote`, with two documented functions, `shellquote` and `shellquoteparams`. The second one is the interesting name, since a plural version implies handling a parameter list rather than a single string, which is where the classic word splitting bug lives.
The testing surface is broad as well. Beyond the arithmetic tests there are unary string tests, binary string matching tests, a multi-matching option, file type tests, file comparison tests, file status tests, I/O tests and file permission tests. A script that needs to distinguish a directory from a symlink, or check writability, has named constructs for that rather than a chain of `test` invocations.
Shell options and traps are managed as a stack
Two pieces of shell state are the usual source of subtle bugs in long scripts: the shell options that change behaviour, and the trap handlers that run on exit or signal. Modernish treats both as stacks, and the manual has a chapter on the stack covering the shell options stack and the trap stack.
The trap stack is the more interesting of the two, because traps accumulate. A script that installs a cleanup handler, then calls a function that installs another, then returns, has to restore the previous handler or the cleanup runs at the wrong moment. A stack discipline gives that a defined order, and the manual follows through with `use var/stack`, `use var/stack/extra` and `use var/stack/trap`, the last with its own section on trap stack compatibility considerations.
That last heading is the honest part. Trap handling is one of the areas where shells differ, so a portable library has to acknowledge the compatibility question rather than paper over it. You should read that section before you rely on nested trap behaviour across several shells.
The reliable emergency halt belongs to the same family of concerns. A library that can restore state and clean up properly can also guarantee that a fatal error path leaves the system in a known state, which is what that chapter is about.
Where Modernish is the wrong tool
There are cases where a shell library is the wrong answer, and the project itself sets the limits.
The most important is scope. Modernish makes shell programming better; it does not turn shell into a different language. A script that needs data structures, real error handling at scale, or maintainable logic across thousands of lines is still better off in a language with those features, and the library will not change that. The README's own framing is that modernish programs are shell programs, with the new constructs mixed with shell syntax.
The second is the cost of a large library in a small script. There is a whole chapter of low-level utilities and roughly a dozen modules, each with its own options. A script that needs one safe expansion does not need a module system, and pulling in a large library for a small job adds a dependency to every host you deploy to, plus the learning time for the constructs you will never use.
The third is the version story, and it deserves attention because the release history is unusual. There is a single tagged release listed, v0.16.5, published on 2020-06-27, described as a stable release bringing many improvements to the 0.16.x line. The last push was on 2026-10-01, so development on master continues, but the project ships a stable point from 2020 and an ever moving development branch. If you need a version you can reproduce, that means either pinning the 2020 release or pinning a specific commit, and the README suggests you can do either, by downloading the latest release or checking out the very latest development code from the master branch.
On licensing, the project is under the ISC licence, which is short and permissive. The repository root contains install.sh and uninstall.sh, a lib/ directory, a bin/ directory, a share/ directory that holds the documentation and examples, and an EXAMPLES.md file with further examples.
Editorial conclusion
Modernish fits a team that ships shell scripts to many hosts, where quoting bugs and the absence of useful loop constructs cause real damage, and it fits embedded or locked-down systems where new binaries cannot be introduced. It does not fit a codebase that has already standardised on a different shell extension scheme, and it does not fit work where the script must run under a shell the library does not recognise, because the capability detection step decides at runtime what is available. Before adopting it, read the safe mode chapter and the find loop chapter in the manual, decide between the simple form and the portable form for each script, and pin the library version you depend on rather than tracking master.
Frequently asked questions
What is Modernish and which shells does it support?
Modernish is a library for shell script programming written entirely in the shell language, so there is no compiled code to install. It aims to be portable across POSIX-like shells, and the project topics name ash, bash, dash, ksh, ksh93, mksh, yash and zsh. The manual includes a chapter on shell capability detection.
What does the safe mode in Modernish change?
The safe mode is enabled through the `use safe` module and has its own chapter in the manual, split into why the safe mode exists, how it works, important notes for safe mode and extra options. The project presents it as the answer to quoting problems, split and glob pitfalls, and to brittle scripts that go haywire and cause damage.
Can Modernish scripts run without installing the library first?
Yes. The installer can bundle a reduced copy of the library with your scripts, so they run portably with a known version of modernish and without requiring prior installation. This is the portable form of a modernish program, as opposed to the simple form where the library is expected to be present on the host.
How does Modernish handle arithmetic without the (( )) syntax?
The `use var/arith` module provides integer arithmetic tests and operations through the arithmetic command `let`, along with operator and comparison shortcuts, and `use var/local` provides scoping with `use var/assign` and `use var/readf` for assignment and input. The README frames this as a response to the line noise of shell-specific syntax.
Which release of Modernish should I use?
The listed stable release is v0.16.5 from 2020-06-27, described as a stable release bringing many improvements to the 0.16.x line, and the last push to the repository was on 2026-10-01. The README suggests either downloading the latest release or checking out the very latest development code from the master branch, so pin whichever you depend on.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/modernish-modernish)Was this useful?