├── .editorconfig ├── .hooks ├── hook-wrapper ├── pre-commit ├── pre-commit.d │ └── new-file ├── pre-push └── pre-push.d │ └── unit-tests ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── TODO.md ├── doc ├── __index__.md ├── atexit.md ├── die.md ├── error.md ├── getarg.md ├── hascmd.md ├── hasfunc.md ├── import.md ├── main.md ├── optarg.md ├── pathcmd.md └── realpath.md ├── examples ├── README.md └── remote_command.sh ├── lib ├── array │ ├── __init__.shlib │ ├── append.shlib │ ├── head.shlib │ ├── index.shlib │ ├── insert.shlib │ ├── join.shlib │ ├── length.shlib │ ├── pop.shlib │ ├── shift.shlib │ └── tail.shlib ├── deprecated │ ├── Packages.md │ ├── Testing.md │ ├── run-tests.sh │ ├── shlib-doc.sh │ ├── shlib-project │ └── shlib-wrapper ├── experimental │ ├── .shlib_notest │ ├── __index__.md │ ├── expect │ │ ├── __index__.md │ │ ├── __init__.shlib │ │ ├── match.md │ │ ├── open.md │ │ ├── popen.md │ │ ├── read.md │ │ ├── regexp.md │ │ ├── timeout.md │ │ └── write.md │ ├── map.shlib │ ├── readlink.shlib │ ├── repeat1 │ │ └── __init__.shlib │ ├── repeat2 │ │ └── __init__.shlib │ └── trap.shlib ├── hostinfo │ ├── __init__.shlib │ └── config.guess ├── math │ ├── __index__.md │ ├── atan2.md │ ├── atan2.shlib │ ├── atan2_test.shlib │ ├── calc.md │ ├── calc.shlib │ ├── calc_test.shlib │ ├── cmp.md │ ├── cmp.shlib │ ├── cmp_test.shlib │ ├── cos.md │ ├── cos.shlib │ ├── cos_test.shlib │ ├── exp.shlib │ ├── exp_test.shlib │ ├── int.md │ ├── int.shlib │ ├── int_test.shlib │ ├── log.md │ ├── log.shlib │ ├── rand.md │ ├── rand.shlib │ ├── seq.md │ ├── seq.shlib │ ├── sin.md │ ├── sin.shlib │ ├── sqrt.md │ ├── sqrt.shlib │ ├── sum.md │ └── sum.shlib ├── remote │ ├── __index__.md │ ├── __init__.shlib │ ├── arch.md │ ├── close.md │ ├── hascmd.md │ ├── open.md │ ├── runcmd.md │ └── vendor.md ├── string │ ├── __index__.md │ ├── compare.md │ ├── compare.shlib │ ├── escape.md │ ├── escape.shlib │ ├── index.md │ ├── index.shlib │ ├── match.md │ ├── match.shlib │ ├── regexp.md │ ├── regexp.shlib │ ├── sub.md │ ├── sub.shlib │ ├── tolower.md │ ├── tolower.shlib │ ├── toupper.md │ └── toupper.shlib └── system │ ├── __index__.md │ ├── __init__.shlib │ ├── mkfifo.md │ ├── mkfifo.shlib │ ├── mktemp.md │ ├── mktemp.shlib │ ├── mktempdir.md │ └── mktempdir.shlib ├── libexec └── shlib-install ├── shlib └── shlib.install /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /.hooks/hook-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ## 3 | # Simple git hook wapper for wrapping all hook types. This allows one to 4 | # easily add a call to ".hooks/" to a users custom .git/hook/ so 5 | # that we can enforce our hooks along side a users custom hooks. This also 6 | # allows us to support multiple hook scripts, as opposed to just one. 7 | # 8 | # To make use of this framework simply add the following line to an existing 9 | # git-hook (such as .git/hooks/pre-commit): 10 | # if test -x ".hooks/${0##*/}"; then ".hooks/${0##*/}" "$@"; fi 11 | 12 | PROGNAME="${0##*/}" 13 | test -d ".hooks/${PROGNAME}.d" || exit 0 14 | ls -C1 .hooks/${PROGNAME}.d/|while read SCRIPT; do 15 | ".hooks/${PROGNAME}.d/${SCRIPT}" "${@}" 16 | done 17 | 18 | echo "All ${PROGNAME} scripts passed!" 19 | 20 | # vim: filetype=sh 21 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | hook-wrapper -------------------------------------------------------------------------------- /.hooks/pre-commit.d/new-file: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Rules for adding new files 3 | #set -x 4 | : "$0" "$@" 5 | die() { echo "error: $*" >&2; exit 1; } 6 | 7 | exec 1>&2 8 | 9 | git diff --cached --diff-filter A --name-only|while read filename; do 10 | # Filename must have valid characters 11 | if test "$(git config --bool hooks.allownonascii)" != 'true'; then 12 | if ! test -z "$(printf '%s' "${filename}"|LC_ALL=C tr -d '[ -~]')"; then 13 | die 'attempt to add a non-ASCII filename.' 14 | fi 15 | fi 16 | 17 | # New libraries and interfaces must come with documentation and unit tests. 18 | case "${filename}" in 19 | (*/__init__.shlib) 20 | if ! test -f "${filename%__init__.shlib}__index__.md"; then 21 | die "missing documentation for '${filename%/__init__.shlib}'" 22 | fi 23 | 24 | if ! test -f "${filename%__init__.shlib}__index__.shit"; then 25 | die "missing tests for '${filename%/__init__.shlib}'" 26 | fi 27 | ;; 28 | 29 | (*.shlib) 30 | if ! test -f "${filename%.shlib}.md"; then 31 | die "missing documentation for '${filename}'" 32 | fi 33 | if ! test -f "${filename%.shlib}.shit"; then 34 | die "missing tests for '${filename}'" 35 | fi 36 | ;; 37 | esac 38 | done 39 | -------------------------------------------------------------------------------- /.hooks/pre-push: -------------------------------------------------------------------------------- 1 | hook-wrapper -------------------------------------------------------------------------------- /.hooks/pre-push.d/unit-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run the shlib unit tests 4 | exec ./shlib-interface-test 5 | 6 | # vim: filetype=sh 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 # Use the ref you want to point at 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: check-merge-conflict 7 | - id: check-shebang-scripts-are-executable 8 | - id: end-of-file-fixer 9 | - id: mixed-line-ending 10 | - repo: https://github.com/shellcheck-py/shellcheck-py 11 | rev: v0.10.0.1 12 | hooks: 13 | - id: shellcheck 14 | - repo: https://github.com/koalaman/shellcheck-precommit 15 | rev: v0.10.0 16 | hooks: 17 | - id: shellcheck 18 | args: ['-e', 'SC1090,SC2034'] 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing ## 2 | 3 | Welcome to the SHLIB Project. Here you will find some basic information on 4 | how to contribute to the project. 5 | 6 | ## Reporting Bugs ## 7 | 8 | Simply open an issue on [GitHub](../issues). 9 | 10 | ## Submitting Changes 11 | 12 | All changes should: 13 | - Use [Conventional Commits](https://www.conventionalcommits.org) 14 | - Should pass local [pre-commit][] tests. 15 | - Should be submitted as a pull-request to the repository. 16 | 17 | ## Testing 18 | 19 | Local testing is handled by [pre-commit][], which will also be used to 20 | automatically test any pull-requests in the repositories CI/CD pipeline. 21 | 22 | See the [installation instructions](https://pre-commit.com/#install) for 23 | [pre-commit][] for more details. 24 | 25 | ## Roadmap 26 | 27 | Please review the [roadmap](Roadmap.md) to see where we are in the project 28 | and how you might be able to help. 29 | 30 | ### Coding Style ### 31 | 32 | The coding-style in [shlib][shlib] borrows heavily from the coding-style 33 | defined by [git][git]. 34 | 35 | * Use tabs for indentation (not 8 spaces, but tabs). 36 | 37 | * case arms are indented the same depth as the `case` and `esac` lines. 38 | 39 | ```sh 40 | case "${variable}" in 41 | pattern1) 42 | do_this;; 43 | pattern2) 44 | do_that;; 45 | esac 46 | ``` 47 | 48 | * Use `$( ... )` for command substitution. Do not use *\` ... \`*. 49 | 50 | * Feel free to use `$(( ... ))` for arithmetic expansion. 51 | 52 | * Do not use process substition `<( )` or `>( )`. 53 | 54 | * Use 'test' over `[ ... ]`: Historically `[` and `]` are filesystem symlinks 55 | to the `test` CLI command. While most modern shell's handle `test` as a 56 | builtin, most users don't understand that `[` is not part of shell's defined 57 | syntax. 58 | 59 | #### Rethink your understanding of `test` #### 60 | 61 | * Do not use `-a` or `-o` with `test`, use `&&` or `||` instead. 62 | 63 | Parameter expansion on both left and right side is performed regardless of 64 | the result of the left-side during `-o` and `-a`: 65 | 66 | ```sh 67 | test "$(cmd1)" = 'success' -o "$(cmd2)" = 'success' 68 | ``` 69 | 70 | * The `-a` condition can become painfully confused depending on the data, 71 | for example: `test -n "${a}" -a "${a}" = "${b}"` can break if `a='='`. 72 | Using `&&` will have no such problem: 73 | 74 | ```sh 75 | test -n "${a}" && test "${a}" = "${b}" 76 | ``` 77 | 78 | #### Do not confuse what constitutes a Basic Regular Expression #### 79 | 80 | * `?` and `+` are not part of the BRE definition, GNU just made them 81 | accessible when in BRE. 82 | 83 | * Do not use `?`, this is an ERE, use `\{0,1\}` instead. 84 | 85 | * Do not use '+', this is an ERE, use `\{1,\}` instead. 86 | 87 | * Do not use `grep -E` unless you know the current flavor of grep supports 88 | it (it isn't portable). 89 | 90 | 91 | #### Avoid shell-ism's outside of shell-specific code. ##### 92 | 93 | ##### Accepted Parameter Substitutions ##### 94 | 95 | * `${parameter-word}` and its `[-=?+]` siblings, and their colon'ed "unset or null" form. 96 | 97 | * `${parameter#word}` and its `[#%]` siblings and their doubled "longest matching" form. 98 | 99 | * `${#parameter}` for strlen substitution. 100 | 101 | ##### Unacceptable shell-ism's ##### 102 | 103 | Do not use any of these w/out knowledge of the current shell-flavour (i.e. 104 | hide it inside of an if, or some other source-file to be sourced in). 105 | 106 | * Double-bracket conditions: `if [[ ... ]]`, this is entirely unportable. 107 | 108 | * Sub-string expansion: `${parameter:offset:length}` 109 | 110 | * Shell-Arrays: `var=(one two three)` 111 | 112 | * Pattern replacement: `${parameter/pattern/string}` 113 | 114 | [//]: # (References) 115 | 116 | [GitHub]: https://github.com 117 | [pre-commit]: https://pre-commit.com 118 | [shlib]: http://github.com/major0/shlib "shlib" 119 | [git]: http://gitscm.com/ 120 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | WORKDIR /app 4 | 5 | COPY * ./ 6 | 7 | ENTRYPOINT ["./shlib"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2025 Mark Ferrell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shlib 2 | ===== 3 | 4 | One of the great drawbacks to the POSIX shell environment is that, like the C 5 | programming language, it has very little in the way of builtin high-level 6 | functionality. To do many things often requires resorting to esoteric hacks 7 | which wrap other tools. This has lead to a number of variations of the POSIX 8 | shell ([Korne Shell][ksh], [Z Shell][zsh], [Bash][bash], etc..) which implement 9 | various high-level features in their own proprietary way. [shlib][] aims 10 | to supply many (all?) of these high-level features using only pure POSIX shell 11 | syntax, while optimizing the core of any interface via shell-specific 12 | enhancements under the covers (making afore mentioned esoteric hacks totally 13 | portable and useful for all). Further more, [shlib][] is self-optimizing 14 | at *load-time*, meaning that it does not spend time checking the shell 15 | environment every time an interface is called. The end result is fast and 16 | portable shell scripts which, hopefully, work right regardless of which flavor 17 | of `/bin/sh` is interpretting them. 18 | 19 | ## Philosphy ## 20 | 21 | [shlib][] is written around the goal of providing a portable **dumping 22 | ground** for various shell tricks and hacks produced throughout the ages. Such 23 | things as floating point arithematic, string manipulation, and even just overly 24 | common routines used by programmers on a day-to-day basis. With that in mind, 25 | [shlib][] is a framework which presents a portable interface to the developer, but 26 | which may be implemented via any number of methods under-the-covers at load 27 | time. 28 | 29 | ## What shlib is not ## 30 | 31 | [shlib][] is not in and of itself a scripting language, or a replacement to 32 | existing shells. While various libaries w/in [shlib][] may implement an 33 | interface using external utilities are shell-specific features, the interfaces 34 | provided conform to the [Shell Command Language](http://pubs.opengroup.org/onlinepubs/007904975/utilities/xcu_chap02.html) 35 | as defined by the [OpenGroup](http://www.opengroup.org/). 36 | 37 | ## load-time optimization ## 38 | 39 | Due to the nature of POSIX shell, libraries can perform tests _when_ they are 40 | imported, as opposed to doing a test every time an interface is called. This 41 | allows [shlib][] to implement a single interface via a variety of methods, and 42 | select the method which best fits the local platform. 43 | 44 | For example: 45 | 46 | ```sh 47 | #!/usr/bin/env shlib 48 | 49 | if shlib.hascmd seq; then 50 | __math_seq() { command seq "${@}"; } 51 | else 52 | # no seq cmd available then attempt to load the bash version 53 | # which uses a c-for style itterator, else use one written in 54 | # pure POSIX shell. 55 | if ! . seq.bash > /dev/null 2>&1; then 56 | . seq.sh 57 | fi 58 | fi 59 | alias math.seq='__math_seq ' 60 | 61 | shlib.main() { math.seq "${@}"; } 62 | ``` 63 | 64 | From here a program only need to `import math.seq` to gain access to a 65 | `math.seq()` function which will call the native `seq` command if available, 66 | otherwise it will use one implemented in POSIX shell. 67 | 68 | Usage 69 | ===== 70 | 71 | [shlib][] tries hard to be as non-intrussive as possible to software 72 | developers. To this end there are a number of ways to integrate [shlib][] 73 | into ones software. 74 | 75 | Command Line 76 | ------------ 77 | 78 | [shlib][] can be envoked from the command-line in much the same way as 79 | traditional `/bin/sh`. 80 | 81 | Examples: 82 | 83 | 1. Run an shlib: `shlib program.shlib` 84 | 2. Run a shell script: `shlib program.sh` 85 | 3. Shell-like `-c` syntax: `shlib -I math -c 'math.seq 0 9'` 86 | 87 | See `shlib --help` for various command-line options which change the behavior 88 | of [shlib][]. 89 | 90 | hash/bang 91 | --------- 92 | 93 | You can make [shlib][] the interpretter for an existing shell script. 94 | 95 | #!/usr/bin/env shlib 96 | 97 | (_warning: not all OS's support scripts being used as the interpretter for a 98 | file in this way_). 99 | 100 | Source in shlib 101 | --------------- 102 | 103 | When all else fails, you can simply source in the [shlib][] top-level 104 | script into your existing `/bin/sh` scripts. 105 | 106 | ```sh 107 | . /path/to/shlib 108 | ``` 109 | 110 | See Also 111 | ======== 112 | 113 | * [Examples](examples/) 114 | * [Development](CONTRIB.md) 115 | * [API Reference](doc/__index__.md) 116 | * [Future features](TODO.md) 117 | * [License](LICENSE) 118 | * [Unix Shell Objects](https://www.biblio.com/9780764570049), ISBN-13: 978-0764570049, ISBN-10: 0764570048 119 | * [A new object-oriented programming lanuage: sh](https://dl.acm.org/doi/10.5555/1267257.1267258) 120 | * [SHOOP: SHell Object Oriented Programming](https://sourceforge.net/projects/shoop/) 121 | 122 | [shlib]: http://github.com/major0/shlib "shlib" 123 | [ksh]: http://www.kornshell.com/ "Korne Shell" 124 | [bash]: http://www.gnu.org/software/bash/ "Borne Again Shell" 125 | [zsh]: http://www.zsh.org/ "Z Shell" 126 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # dir 2 | Interface for walking and acting upon directory structures. 3 | - walk 4 | - isempty 5 | 6 | # lock 7 | Portable locks for enforcing exclusion. Likely depends on shlib.mktemp and the 8 | dir.* interaces. 9 | - init 10 | - lock 11 | - unlock 12 | - destroy 13 | 14 | db 15 | Simple directory structured database supporting key=value pairs. Will likely 16 | depend on dir.* and the lock.* interfaces. 17 | 18 | # url 19 | Interface for the manipulation of URLs 20 | - escape 21 | - each 22 | - fetch 23 | 24 | # archive 25 | Universal archive wrapper to various archive commands. 26 | - create 27 | - add 28 | - extract 29 | - find 30 | 31 | # fd 32 | File descriptor management. Shell does not really do any FD management for us, 33 | unlike the underlying OS. So we need to track all out FDs manualy and figure 34 | out what is or isn't in use. 35 | - alloc 36 | - free 37 | - dup 38 | 39 | # log (depends on fd.*) 40 | Sane logger interface which allows the opening of multilpe log locations and 41 | the writing to multiple log locations based on a single log message. This will 42 | depend on the fd.* interface. 43 | - create