{"id":44778189,"url":"https://github.com/groboclown/precision-shell","last_synced_at":"2026-02-16T07:14:04.323Z","repository":{"id":47386893,"uuid":"486994239","full_name":"groboclown/precision-shell","owner":"groboclown","description":"The Exactly What You Need Shell","archived":false,"fork":false,"pushed_at":"2025-06-28T03:49:51.000Z","size":871,"stargazers_count":2,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-28T04:29:53.538Z","etag":null,"topics":["c","container","docker","shell"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/groboclown.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","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,"zenodo":null}},"created_at":"2022-04-29T14:04:36.000Z","updated_at":"2025-06-26T21:39:24.000Z","dependencies_parsed_at":"2025-06-28T04:26:23.981Z","dependency_job_id":"95adb761-3c39-4429-8075-9a16fa732efd","html_url":"https://github.com/groboclown/precision-shell","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/groboclown/precision-shell","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groboclown%2Fprecision-shell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groboclown%2Fprecision-shell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groboclown%2Fprecision-shell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groboclown%2Fprecision-shell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/groboclown","download_url":"https://codeload.github.com/groboclown/precision-shell/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groboclown%2Fprecision-shell/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29502933,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-16T05:57:17.024Z","status":"ssl_error","status_checked_at":"2026-02-16T05:56:49.929Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["c","container","docker","shell"],"created_at":"2026-02-16T07:14:01.464Z","updated_at":"2026-02-16T07:14:04.304Z","avatar_url":"https://github.com/groboclown.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Precision Shell (presh)\n\n[![Build](https://github.com/groboclown/precision-shell/actions/workflows/build.yml/badge.svg)](https://github.com/groboclown/precision-shell/actions/workflows/build.yml)\n\n\n## Custom Built Shell With Only What You Need\n\nSometimes, you don't need or want everything that a shell or Linux environment can do.  You just have a few things that you need to do.  Adding more adds bloat to your Linux environment and broadens the attack surface.  **Precision Shell** lets you compile the shell to contain only what you need.  It works great as a default shell environment for a Docker or Podman container for when you need just that extra bit of runtime setup, especially when running with a [distro-less container](https://github.com/GoogleContainerTools/distroless/).\n\n`presh` offers a [few commands and shell syntax](#what-it-does), and gives you the flexibility to select which ones to compile, which can make the executable smaller and provide extra security by not enabling commands that don't need to be run.  It's not POSIX conformant, and doesn't try to be.  Its syntax is [eclectic](#command-parsing) and doesn't hold your hand.\n\nThe tool has two goals - provide just enough commands for what you need to do, and [make it small](#compiled-size).\n\n\n## What It Does\n\nThe shell supports these commands:\n\n* Output\n  * [echo](#echo) - send text to `stdout`.\n  * [dup-r, dup-w, dup-a](#dup-r-dup-w-dup-a) - duplicates a file (or file descriptor) to a specific file descriptor for the remaining commands in this execution.\n  * [cat-fd](#cat-fd) - write the contents of a file to a file descriptor.\n  * [env-cat-fd](#env-cat-fd) - write the contents of a file to a file descriptor, performing environment variable parsing on the source file.\n  * [write-fd](#write-fd) - write the arguments to a file descriptor, either stdout or stdin, or those opened through the `dup-*` commands.\n  * [ls](#ls) - list contents of a directory.\n  * [ls-l](#ls-l) - list contents of a directory with content attributes.\n  * [ls-t](#ls-t) - list contents and content type of a directory\n  * [file-stat](#file-stat) - describe a file\n* Files\n  * [fmode](#fmode) - set the octal file mode for other commands that work with files.\n  * [rm](#rm) - remove files.\n  * [rmdir](#rmdir) - remove empty directories.\n  * [mv](#mv) - move a file from one name to another.\n  * [mkdir](#mkdir) - create an empty directory.\n  * [chmod](#chmod) - change file permissions.\n  * [chown](#chown) - change user and group owner for files.\n  * [ln-s](#ln-s) - create a symbolic link.\n  * [ln-h](#ln-h) - create a hard link.\n  * [touch](#touch) - \u003cstrike\u003eUpdate the access and modification times of each file to the current time, or,\u003c/strike\u003e if a file does not exist, it is created empty.\n  * [trunc](#trunc) - Sets the file length to 0, and if the file does not exist, creates it.\n  * [mknod](#mknod) - create a FIFO or UNIX socket node.\n  * [mkdev](#mkdev) - create a device OS node.\n* Command Execution\n  * [cd](#cd) - change current working directory, for relative file locations and executed command working directory.\n  * [export](#export) - export an environment variable + value into the running process and to-be-run child processes.\n  * [exec](#exec) - switch execution to a new process.\n  * [su-exec](#su-exec) - switch execution to a new process as another user and group ID.\n  * [spawn](#spawn) - launch a new process in the background.\n  * [su-spawn](#su-spawn) - launch a new process in the background as another user and group ID.\n  * [wait-pid](#wait-pid) - wait for a process started by `spawn` to end.\n  * [kill-pid](#kill-pid) - send a signal to a process.\n  * [start-timer](#start-timer) - start the global timer.  Queried with other commands.\n  * [elapsed-time](#elapsed-time) - prints number of seconds since the [start-timer](#start-timer) command was called.\n  * [export-elapsed-time](#export-elapsed-time) - records the number of seconds since the [start-timer](#start-timer) command was called into an environment variable.\n  * [elapsed-time-under](#elapsed-time-under) - passes with an error code of 0 if the time since the [start-timer](#start-timer) command was called is under the given number of seconds.\n  * [signal .. wait](#signal-wait) - wait for an OS signal before continuing.\n* Control Flow\n  * [if-else](#if-else-command) - run a command conditionally based on the error result of another.\n  * [subcmd](#subcmd) - run an argument as a complete precision shell command.\n  * [not](#not) - run an argument as a complete precision shell command, and invert the error code (non-zero becomes zero, zero becomes 1).\n  * [exit](#exit) - exits the command (or sub-command) with an exit code.\n  * [sleep](#sleep) - wait for a number of seconds.\n  * [for-each](#for-each) - loop over sub-arguments, setting an environment variable with the value and running a sub-command.\n  * [while-error](#while-error) - run a sub-command until it ends without error.\n  * [while-no-error](#while-no-error) - run a sub-command until it ends with an error.\n  * [is-eq](#is-eq) - compare 2 or more strings for equality.\n* Network\n  * [test-connect](#test-connect) - test whether a host is listening on a given port number.\n  * [export-host-lookup](#export-host-lookup) - exports to an environment variable the IP address of a hostname.\n  * [expect-http-get-response](#expect-http-get-response) - generates an error if an HTTP GET request to a host, port, path returns a status code that doesn't match an expected value.\n* Usability\n  * [pwd](#pwd) - display current working directory, or store it in an environment variable.\n  * [version](#version) - prints the current version (cannot be disabled).\n  * [noop](#noop) - do nothing.  The comment command.\n\nIt also supports:\n* [Embedded Quoting](#command-parsing) by using the `[` and `]` symbols to allow easy deep quotes, like `exec [/usr/bin/echo [from native echo]]`\n* [Chaining commands](#chaining-commands) together with `\u0026\u0026` and `;` (cannot be disabled).\n* [Standard script argument flag](#standard-script-flag) - if passed with the arguments `-c \"commands\"`, then the shell will parse the commands argument into individual commands (cannot be disabled).\n* [Script files](#script-files) - as an argument if used with `-f script-file-name`.\n* [Commands from stdin](#passing-commands-from-stdin) - With the `-` argument, commands are parsed from stdin.  **Without the streaming input flag, the shell will not read from stdin.**\n* [Environment variable parsing](#environment-variables) - the input values can be replaced with environment variables, when specified in the form `${VALUE}` (the standard shell form of `$VALUE` is not supported).\n\n\n## What It Doesn't Do\n\n* Report detailed error messages.\n* Change file timestamps.\n* Provide splat pattern replacements.\n* Tell you how to use it.  That's what this document is for.\n\n\n## Why Would I Need It?\n\nThe tool was built for container (Docker and Podman) images that use minimal OS resources.  Golang projects commonly build a single file, the executable, placed into a `FROM scratch` image.  In these environments, `presh` can be easily added to provide some minimal file manipulation.\n\nAdditionally, you can build the tool with exactly the commands you need to run.  This limits the attack surface, making your install just that much safer.\n\n\n## How Do I Get It?\n\nBecause of the goals for the shell, it does not distribute a compiled version.  You're expected to build it from source yourself.\n\nYou can build it directly and set the included commands:\n\n```bash\nmake \"COMMAND_FLAGS=-DUSE_CMD_CHMOD -DUSE_STREAMING_INPUT\"\n```\n\nFlags are split into command flags and usage flags.  Each [command](#help) in this document also includes the flag to enable it in the compiled executable.  A complete list of flags is in the [`Makefile.command-flags`](Makefile.command-flags) file.\n\nYou can use some built-in command flag groups:\n\n* `make INCLUDE_ALL_COMMANDS=1` - build with all supported commands.\n* `make INCLUDE_STANDARD_COMMANDS=1` - include some simple file operations, streaming input, sleep, and signal handling.\n* `make INCLUDE_MINIMAL_COMMANDS=1` - include symbolic links, mkdir, echo, chown, and chmod.\n\nThe commands that cannot be directly controlled for inclusion are:\n\n* `version` - this is always included.\n* `fmode` - this is automatically included for `mkdir`, `touch`, `trunc`, and the `dup` commands.\n\nTo run the build, you'll need basic C compiler, linker, and make.  If you have Python 3 installed, you can generate the command source files (they are bundled if you don't have it).\n\nThe most common setup is to build it inside a Docker container for use in another container.  See the [`Dockerfile`](Dockerfile) for how this is done.\n\n\n## Using It With Docker\n\nYou can build it outside the container and bundle it in your container through a simple copy:\n\n```Dockerfile\nFROM super-skinny-image:11.12\n\nCOPY presh /bin/sh\n\n# Because /bin/sh is now the shell, run commands will\n#   run through it by executing \"/bin/sh -c (arguments)\"\nRUN echo Startup \\\n    \u0026\u0026 rm /www/404.html /www/501.html \\\n    \u0026\u0026 rmdir /tmp \\\n    echo Complete\n```\n\nYou can also build it in a multi-stage Dockerfile.  See [sample.Dockerfile](sample.Dockerfile) for an example of using it with Docker and a `FROM scratch`, to show that no OS setup is necessary to run the shell.\n\n\n## Help\n\nAll commands and special build modes are described here.  To add in a compile flag, run the build like: `make COMMAND_FLAGS=\"-DUSE_CMD_ECHO -DUSE_CMD_RM\"`\n\n### cat-fd\n\n**Compile flag**: `-DUSE_CMD_CAT_FD`\n\n**Usage**: `cat-fd (fd) [file 1 [file 2...]]`\n\nThe first argument is the file descriptor to send the output to (1 == stdout, 2 == stderr, and others can be used by use of the dup commands).\n\nFor each additional argument, in order, the command reads its contents and sends it, as-is, to the file descriptor.  If the command encounters a problem reading or accessing the file, the command will generate an error for that argument, but will keep going.\n\nThis can be used with the [`dup-w`](#dup-r-dup-w-dup-a) and [`dup-a`](#dup-r-dup-w-dup-a) commands to perform a file copy operation.\n\n**Example 1:**\n\nCopy a file, and report that it succeeded.\n\n```bash\n$ echo \"file contents\" \u003e a.txt\n$ presh -c \"\\\n  echo [Copying a.txt to b.txt] \\\n  \u0026\u0026 dup-w 2 b.txt \\\n  \u0026\u0026 cat-fd 2 a.txt \\\n  \u0026\u0026 dup-w 2 \u00262 \\\n  \u0026\u0026 echo [Completed copy]\"\n```\n\n### cd\n\n**Compile flag**: `-DUSE_CMD_CD`\n\n**Usage**: `cd (directory)`\n\nChanges the current directory.  Useful for relative paths when running file commands, or when launching an executable to run inside a specific directory.\n\n**Example 1:**\n\n```bash\n$ presh -c \"\\\n  cd /usr ; spawn [/usr/bin/pwd] ; sleep 1 ;\n  cd /lib ; spawn [/usr/bin/pwd]\"\n/usr\n/lib\n```\n\n### chmod\n\n**Compile flag**: `-DUSE_CMD_CHMOD`\n\n**Usage**: `chmod (octal mode) [file1 [file2 ...]]`\n\nChanges the file permissions for each file or directory.  The mode must be passed as an octal number.\n\nExample:\n\n```bash\n$ touch a.txt\n$ chmod 600 a.txt\n$ ls -l a.txt\n-rw------- 1 user user   0 Jan 19 09:51 a.txt\n$ presh -c \"chmod 770 a.txt\"\n$ ls -l a.txt\n-rwxrwx--- 1 user user   0 Jan 19 09:51 a.txt\n```\n\nThis command will fail if the mode value is out of range or not a number.\n\n### chown\n\n**Compile flag**: `-DUSE_CMD_MKDIR`\n\n**Usage**: `chown (uid) (gid) [file1 [file2 ...]]`\n\nChanges the owner and group ID for each file, directory, or symlink argument.\n\n### dup-r, dup-w, dup-a\n\n**Compile flag**: `-DUSE_CMD_DUP_W` and `-DUSE_CMD_DUP_R` and `-DUSE_CMD_DUP_A`\n\n**Usage**: `dup-w (fd) (file)`\n\n**Usage**: `dup-r (fd) (file)`\n\n**Usage**: `dup-a (fd) (file)`\n\nOpens the given file in either write + truncate mode (`dup-w`), write + append mode (`dup-a`), or read mode (`dup-r`), then duplicates that file descriptor to the \"fd\" argument.  This duplication remains in effect for the remainder of the execution, or until another `dup` command runs for the same file descriptor.\n\nThis is the equivalent of running a command like `echo \u003e out.txt` to redirect stdout, stderr, or stdin to a file.\n\nYou can also use the special files \"\u00261\" and \"\u00262\" to redirect stdout or stderr, respectively, to the file descriptor.  See examples for interesting usages.\n\nIf this creates a file, the file will have the file permissions set in [`fmode`](#fmode).\n\nIn order to redirect output from one command to input of another command, you must use a combination of [`mknod`](#mknod) to create a FIFO queue and [`spawn`](#spawn) to launch the first command in the background.\n\n**Example 1:**\n\nRun `echo` with output sent to the file `here.txt`, appending existing text.\n\n```bash\necho -n \"foo\" \u003e here.txt\npresh -c \"dup-a 1 here.txt \u0026\u0026 echo [ bar text] \u0026\u0026 echo more text \u0026\u0026 dup-w 1 \u00261 \u0026\u0026 echo [complete]\"\n```\n\nAnd the file `here.txt` will contain:\n\n```\nfoo bar text\nmore\ntext\n```\n\nThe final `dup-w 1 \u00261` restores stdout to the original stdout, allowing the `echo [complete]` to be seen on the console, and not written to `here.txt`.\n\n**Example 2:**\n\nRun `echo` with output sent to stderr\n\n```bash\n# These are equivalent\npresh -c \"dup-w 1 \u00262 \u0026\u0026 echo hello\"\nbash -c \"echo hello 1\u003e\u00262\"\n```\n\n**Example 3:**\n\nRun `find` command with stdout redirected to file `out.txt` and stderr redirected to file `err.txt`\n\n```bash\n# These are equivalent\npresh -c \"dup-w 1 out.txt \u0026\u0026 dup-w 2 err.txt \u0026\u0026 exec /usr/sbin/find /var -type f\"\nbash -c \"find /var -type f \u003eout.txt 2\u003eerr.txt\"\n```\n\n**Example 4:**\n\nGenerate some text to a file named `contents.txt`, then and sort it into another file named `sorted.txt`.\n\n```bash\npresh -c \"dup-w 1 contents.txt \\\n    \u0026\u0026 echo foo bar baz \\\n    \u0026\u0026 dup-w 1 sorted.txt \\\n    \u0026\u0026 dup-r 0 contents.txt \\\n    \u0026\u0026 exec /usr/sbin/sort\"\n```\n\n**Example 5:**\n\nPerform file descriptor redirect, then restore it when complete.\n\n```bash\npresh -c \"dup-w 1 contents.txt \\\n    \u0026\u0026 echo foo bar baz \\\n    \u0026\u0026 dup-w 1 \u00261 \\\n    \u0026\u0026 echo [Wrote to contents.txt]\"\n```\n\n### echo\n\n**Compile flag**: `-DUSE_CMD_ECHO`\n\n**Usage**: `echo [str1 [str2 ...]]`\n\nSends to `stdout` each argument, one per line.  To have a multi-word statement on a single line, it must be passed as a single argument; see [Command Parsing](#command-parsing) for details.\n\n### elapsed-time\n\n**Compile flag**: `-DUSE_CMD_ELAPSED_TIME`\n\n**Usage**: `elapsed-time`\n\nSends to stdout the number of seconds since the [`start-timer`](#start-timer) command was called, or, if `start-timer` was never called, then time since the epoch.\n\nBy including this command, it implies the inclusion of the `start-timer` command.\n\n**Example:**\n\n```bash\npresh -c \"start-timer ; sleep 2 ; elapsed-time ; sleep 2 ; elapsed-time\"\n2\n4\n```\n\n### elapsed-time-under\n\n**Compile flag**: `-DUSE_CMD_ELAPSED_TIME_UNDER`\n\n**Usage**: `elapsed-time-under (seconds)`\n\nIf the number of seconds since the last call to the [start-timer](#start-timer) command is less than the argument's value, then this generates an error code of 0.  If not, then it generates an error code of 1.\n\n**Example:**\n\n```bash\n#! /usr/bin/presh -f\n\nstart-timer\nwhile-no-error [elapsed-time-under 30] [export-elapsed-time DURATION ; touch ${DURATION}.txt ; sleep 1]\n```\n\nThis example creates 1 file every second for 30 seconds.\n\n### env-cat-fd\n\n**Compile flag**: `-DUSE_CMD_ENV_CAT_FD`\n\n**Usage**: `env-cat-fd (fd) [file 1 [file 2...]]`\n\nThis is similar to the Unix `envsubst` command.  It works just like [`cat-fd`](#cat-fd), but performs environment variable replacement on each input file.\n\n### exec\n\n**Compile flag**: `-DUSE_CMD_EXEC`\n\n**Usage**: `exec (quoted command to run)`\n\nParses the first argument using the [presh quoting rules](#command-parsing) and transfers execution to the command.  The first value extracted is the full path to the executable to run, and the rest of the values are passed as arguments.  If the executable exists and is executable, then presh will not run any more commands.\n\nCommands must be given in the full path; it doesn't look at any environment variable like `PATH`, even with the [environment variable parsing](#environment-variables) enabled.\n\nIf the command file does not exist or is not executable, then the next argument will be tried to run.  If none of them can be run, then the `exec` command generates an error.  Note that if the called command can run but generates an error, presh doesn't have control at that point and will not run anything else.\n\nBecause of how `exec` works, trailing the command with [`\u0026\u0026`](#chaining-commands) will never cause the following command to run.  This is because the only way `exec` will allow `presh` to run the next command is if it encountered an error attempting to launch the new process.\n\n**Example 1:**\n\nReport the date and time to a file, where the date time tool may be located in several locations, not known ahead of time.  This uses [`dup-w`](#dup-r-dup-w-dup-a) to cause the stdout from the executed command to be sent to the `/etc/time.txt` file, and if no date program is found, the message \"unknown date\" is written instead.\n\n```bash\npresh -c \"dup-w 1 /etc/time.txt \u0026\u0026 exec [/bin/date] [/usr/bin/date] [/sbin/date] ; echo [unknown date]\"\n```\n\n**Example 2:**\n\nRuns the native copy command to copy files with spaces in their names.  This shows how the embedded quoting makes it much easier to read.\n\n```bash\npresh -c \"exec [/usr/bin/cp [source file.txt] [target file.txt]]\"\n```\n\n### exit\n\n**Compile flag**: `-DUSE_CMD_EXIT`\n\n**Usage**: `exit (exit code)`\n\nQuits the execution of the current command context with an exit code, and no other commands are parsed.  If the exit is within a sub-command, then the sub command exits.\n\n**Example:**\n\n```bash\npresh -c \"\\\n  subcmd [exit 1 ; echo [should not run 1]] \u0026\u0026 \\\n  echo [should not run 2] ; \\\n  echo [should run]\"\n```\n\nThe \"should not run 1\" line will not be reported, because the `exit 1` in the sub-command will stop all further command processing inside that sub-command.  The \"should not run 2\" will not run either, because it runs after the \"\u0026\u0026\", which will prevent the next command from running because the sub-command exited with a non-zero return code (1, from the exit argument).  Finally, the \"should run\" line is printed because the \";\" ignores the global error state.\n\n### export\n\n**Compile flag**: `-DUSE_CMD_EXPORT`\n\n**Usage**: `export (ENV_NAME) (env value)`\n\nExport an environment variable + value into the running process and to-be-run child processes.\n\n### export-elapsed-time\n\n**Compile flag**: `-DUSE_CMD_EXPORT_ELAPSED_TIME`\n\n**Usage**: `export-elapsed-time [env-name [env-name ...]]`\n\nStores in environment variables named in the arguments the number of seconds since the [`start-timer`](#start-timer) command was called, or, if `start-timer` was never called, then time since the epoch.\n\nBy including this command, it implies the inclusion of the `start-timer` command.\n\n**Example:**\n\n```bash\npresh -c \"start-timer ; sleep 5 ; export-elapsed-time TIME ; echo \\${TIME}\"\n5\n```\n\n### export-host-lookup\n\n**Compile flag**: `-DUSE_EXPORT_HOST_LOOKUP`\n\n**Usage**: `export-host-lookup (hostname) (ENV_NAME)`\n\nExports to an environment variable the IP address of a hostname.\n\nNote: does not work with glibc, because this uses a function that glibc can only run when not compiled statically.  To avoid a compile error, pass `NO_GETADDRINFO=1` when running `make`.\n\n### expect-http-get-response\n\n**Compile flag**: `-DUSE_CMD_EXPECT_HTTP_GET_RESPONSE`\n\n**Usage**: `expect-http-get-response (hostname) (port) (path) (response-code)`\n\nPerforms a simple HTTP GET request to the given hostname and port, in the form:\n\n```\nGET (path) HTTP/1.1\n```\n\nIf the response code from the server does not match the expected response code, then the command generates an error.\n\n### file-stat\n\n**Compile flag**: `-DUSE_CMD_FILE_STAT`\n\n**Usage**: `file-stat some/file.txt`\n\n*NOTE: this is an experimental feature.  Output is expected to evolve.*\n\nList out information about the given file.\n\n### fmode\n\n**Compile flag**: *included automatically if the dependent commands are included; see below*\n\n**Usage**: `fmode (octal mode)`\n\nChanges an internal default file mode value to the given octal mode.  If never set, the default file mode value is `0644`.\n\nExample:\n\n```bash\n$ presh -c \"fmode 765 ; touch a.txt\"\n$ ls -l a.txt\n-rwxrw-r-x 1 user user   0 Jan 19 09:51 a.txt \n```\n\nThis can include the higher bits, too, so that some commands that support it (and if the executing user has permissions to run it) can set the sticky bits.\n\nThis command is added if commands that have an implicit file mode are added through compile flags ([`mkdir`](#mkdir), [`touch`](#touch), [`trunc`](#trunc), [`mknod`](#mknod), [`mkdev`](#mkdev), and any [`dup`](#dup-r-dup-w-dup-a) commands.\n\n### for-each\n\n**Compile flag**: `-DUSE_CMD_FOR_EACH`\n\n**Usage**: `for-each (ENV_NAME) (value-list) (sub-command)`\n\nFor each sub-argument in the value-list, set the environment variable name in the first argument to that sub-argument, and run the sub-command.  For the sub-command to be able to use the sub-argument, it will need to escape the environment variable.\n\n**Example 1:**\n\nHere, the `$` is escaped for the original shell that runs presh, then the $ is escaped for presh by doubling it (`$$`).\n\n```bash\n$ presh -c \"for-each TEXT [first second [sub text] 3 4] [echo \\$\\${TEXT}]\"\nfirst\nsecond\nsub text\n3\n4\n```\n\n### if-else\n\n**Compile flag**: `-DUSE_CMD_IF_ELSE`\n\n**Usage**: `if-else (conditional cmd) (if successful) [if failure]`\n\nRuns the first argument as a full presh command, as though it was run through [`subcmd`](#subcmd).  If the exit code is zero, then the second argument is run as a full presh command).  If the first argument fails, then the third argument runs, or is skipped if it isn't given.\n\n**Example 1:**\n\nTest to make sure that [`chmod`](#chmod) correctly makes things not-writable.\n\n```bash\n$ presh -c \"\\\n  touch /tmp/file \u0026\u0026 \\\n  chmod 000 /tmp/file \u0026\u0026 \\\n  if-else [touch /tmp/file] \\\n      [echo [chmod is dumb]] \\\n      [echo [chmod works]]\"\nERROR touch: /tmp/file\nchmod works\n```\n\n**Example 2:**\n\nBecause precision shell [does not support `||` chaining](#chaining-commands), this can be simulated by using the conditional operation with the [`noop`](#noop) command.\n\n```bash\npresh -c \"\\\n  dup 2 /dev/null ;\n  if-else [touch [/usr/bin/check-config my-config.rc]] \\\n      noop \\\n      [exec /usr/bin/generate-default-config my-config.rc]\n  \"\n```\n\n### is-eq\n\n**Compile flag**: `-USE_CMD_IS_EQUAL`\n\n**Usage**: `is-eq (arg1) (arg2) ...`\n\nCompares the first argument against the remaining arguments for strict equality.  This compares *textual* equality, case sensitive.  If the any of the arguments do not equal, then the command generates an error exit code.\n\n**Example 1:**\n\nIf all the arguments strictly equal each other, then this exits with a \"0\" (no error) exit code.\n\n```bash\npresh -c \"is-eq [1] [1] [1] \u0026\u0026 echo [yes]\"\nyes\n```\n\n```bash\npresh -c \"is-eq [1] [1] [2] [3]\"\nERROR is-eq: 2\nERROR is-eq: 2\n```\n\n**Example 2:**\n\nIf any of the arguments differ, even by case or whitespace, the command fails.  In the following commands, none of them match.\n\n```bash\npresh -c '\n  is-eq [1] [ 1] ; echo [A: ${?}] ;\n  is-eq [1] [1 ] ; echo [B: ${?}] ;\n  is-eq [1] [01] ; echo [C: ${?}]\n'\n```\n\n### kill-pid\n\n**Compile flag**: `-DUSE_CMD_KILL_PID`\n\n**Usage**: `kill-pid (signal) [pid 1 [pid 2 ...]]`\n\nSends the signal number in the first argument to the processes in the following arguments.\n\n### ln-h\n\n**Compile flag**: `-DUSE_CMD_LN_H`\n\n**Usage**: `ln-h (src file) (dest file)`\n\nCreates a hard link named dest file, pointing to src file.\n\n### ln-s\n\n**Compile flag**: `-DUSE_CMD_LN_S`\n\n**Usage**: `ln-s (src file) (dest file)`\n\nCreates a symbolic link named dest file, pointing to src file.\n\n### ls\n\n**Compile flag**: `-DUSE_CMD_LS`\n\n**Usage**: `ls (directory1 (directory2 ...))`\n\nOutputs, one per line, each entry within the given directory to stdout.  It does not use splat patterns (e.g. `*.txt`), and only accepts directory names.\n\nThe output does not include the directory name for the input.  This means if multiple directory arguments are passed, you won't be able to tell which directory the files belong to.\n\nLimited use as a diagnostic tool when inspecting an image.  Most securely constructed images should never include this command.\n\n**Example:**\n\n```bash\n$ presh -c \"mkdir x ; mkdir y ; touch x/a.txt ; touch y/b.txt ; ls x y\"\na.txt\nb.txt\n```\n\n### ls-l\n\n**Compile flag**: `-DUSE_CMD_LS_L`\n\n**Usage**: `ls-l (directory1 (directory2 ...))`\n\nGenerates an output similar to the output for the standard Unix `ls -lA` command.  Output is sent to stdout.  Non-directory arguments generate errors.\n\nLimited use as a diagnostic tool when inspecting an image.  Most securely constructed images should never include this command.\n\nThe output format has these columns:\n\n1. File attributes.  Each character indicates a different attribute.\n    1. File type.\n        * `-` regular file\n        * `d` directory\n        * `l` symbolic link\n        * `b` block-type device\n        * `c` character-type device\n        * `s` UNIX domain socket\n        * `p` FIFO pipe\n        * `?` other file type\n    2. Sticky flag.  `t` means the userid is \"sticky\", `s` means the groupid is \"sticky\", and `-` means no sticky flag.\n    3. Owning user read access.  `r` for allowed, `-` for not.\n    4. Owning user write access. `w` for allowed, `-` for not.\n    5. Owning user execute access.  `x` for allowed, `-` for not.\n    6. Owning group read access.  `r` for allowed, `-` for not.\n    7. Owning group write access. `w` for allowed, `-` for not.\n    8. Owning group execute access.  `x` for allowed, `-` for not.\n    6. Other read access.  `r` for allowed, `-` for not.\n    7. Other write access. `w` for allowed, `-` for not.\n    8. Other execute access.  `x` for allowed, `-` for not.\n2. Number of hard links to this file.\n3. Owning user ID (numeric, not name)\n4. Owning group ID (numeric, not name)\n5. Device major number\n6. Device minor number\n7. File size, in bytes\n8. File name, including directory.\n\nThe command does not report modified or created times.\n\n**Example 1:**\n\n```bash\n$ presh -c \"mkdir x ; mkdir y ; touch x/a.txt ; mkdir x/o.d ; mknod p y/b.fifo ; ls-l x y\"\nd-rwxr-xr-x 2 1000 1000 0 0 4096 x/o.d\n--rw-r--r-- 1 1000 1000 0 0 0 x/a.txt\np-rw-r--r-- 1 1000 1000 0 0 0 y/b.fifo\n```\n\n**Example 2:**\n\nThe `ls-l` command does not work on files; only directory paths.\n\n```bash\n$ presh -c \"ls-l /dev/null\"\nERROR ls-l: /dev/null\n$ presh -c \"ls-l /dev\"\n...\nc-rw-rw-rw- 1 0 0 1 3 0 /dev/null\n...\n```\n\n### ls-t\n\n**Compile flag**: `-DUSE_CMD_LS_T`\n\n**Usage**: `ls-t (directory1 (directory2 ...))`\n\nSimilar to [`ls`](#ls), but each line starts with a file-type letter:\n\n* `b` - block-type device\n* `c` - character-type device\n* `f` - normal file\n* `d` - directory\n* `p` - FIFO pipe\n* `l` - symbolic link\n* `s` - UNIX domain socket\n* `o` - other\n\nLimited use as a diagnostic tool when inspecting an image.  Most securely constructed images should never include this command.\n\n**Example:**\n\n```bash\n$ presh -c \"mkdir x ; touch x/a.txt ; mkdir x/o.d ; mknod p x/b.fifo ; ls-t x\"\nf a.txt\nd o.d\np b.fifo\n```\n\n### mkdev\n\n**Compile flag**: `-DUSE_CMD_MKDEV`\n\n**Usage**: `mkdev (major version) (minor version) (node type) [file1 [file2 ...]]`\n\nThe node type can be one of these:\n\n* `b` - block device\n* `c` and `u` - character device\n\nThe major and minor version reflect the OS kernel specific device number.\n\nFor example, to create the standard `/dev/null` device, you would run:\n\n```bash\npresh mkdev c 1 3 /dev/null\n```\n\nThe created file will have the file permissions set in [`fmode`](#fmode).\n\nThe execution of this command requires running with root level privileges, or the tool will report an error.\n\n### mkdir\n\n**Compile flag**: `-DUSE_CMD_MKDIR`\n\n**Usage**: `mkdir [file1 [file2 ...]]`\n\nCreates the listed directories.  The parent directory must exist, or it will generate an error.  If any creation fails, then the command fails with the number of failed directories.\n\nEach directory is created with the global file mode (see [`fmode`](#fmode)), with the user, group, and other executable flags also set.  To change this to something else, a [`chmod`](#chmod) must be run.\n\n### mknod\n\n**Compile flag**: `-DUSE_CMD_MKNOD`\n\n**Usage**: `mknod (node type) [file1 [file2 ...]]`\n\nCreates a special or ordinary file of the given type.  The node type is OS specific, but in general, the values supported are:\n\n* `p` - pipe (FIFO queue)\n* `s` - UNIX domain socket\n\nThe created file will have the file permissions set in [`fmode`](#fmode).\n\n### mv\n\n**Compile flag**: `-DUSE_CMD_MV`\n\n**Usage**: `mv (src file) (target file)`\n\nRenames the file referenced by the first argument to a new name referenced by the second argument.  If the operation fails, or if there is no second argument, then the command fails with an exit code of 1.\n\n### noop\n\n**Compile flag**: `-DUSE_CMD_NOOP`\n\n**Usage**: `noop [arg1 [arg2 ...]]`\n\n**Usage**: `# [Some comment text.  Put inside quoting to protect against \u0026\u0026 and ;]`\n\n**Usage**: `#! /shebang/format/presh -f`\n\nDoes nothing and ignores all arguments after it.\n\n**WARNING**  This does not work like you'd expect a normal comment to work.  This is a command, which means that ';' and '\u0026\u0026' will terminate it, so it's best to quote the arguments.  Also, it will be interpreted as a command for commands that take sub-commands as arguments.\n\nThe noop can also be used to mask a file start shebang (`#!`) marker.  To work with presh, the precise format will need a space after the shebang mark, and include the `-f` argument to have the script be interpreted as a file.  This mode requires the [stream input flag](#script-files).\n\n**Example 1:**\n\nA script file.\n\n```bash\n#! /usr/bin/presh -f\n\necho [This is a script file.]\n```\n\n**Example 2:**\n\nCareful with characters; the no-op is a command.\n\n```bash\n$ presh -c \" \\\n  echo [Text 1] ; \\\n  # echo [Text 2] ; echo [Text 3] ; \\\n  # [echo [Text 4] ; echo [Text 5]] ; \\\n  echo [Text 6]\"\nText 1\nText 3\nText 6\n```\n\n**Example 3:**\n\nCareful with location; the no-op is a command.\n\n```bash\n$ presh -c \"\\\n  touch a-file.txt \u0026\u0026 \\\n  chmod 000 a-file.txt \u0026\u0026\n  if-else [touch a-file.txt] \\\n    # [echo Worked] \\\n    [echo [chmod does not work]] \\\n    [echo [chmod works]]\"\nchmod does not work\nERROR if-else: echo [chmod works]\n```\n\nIn this situation, the `# [echo Worked]` line is interpreted as a no-op operation, so the failure line is `[echo [chmod does not work]]` and, because there's no termination for the `if-else` command, it generates an error for the extra parameter `[echo [chmod works]]`.\n\n### not\n\n**Compile flag**: `-DUSE_CMD_NOT`\n\n**Usage**: `not [command]`\n\nSimilar to the [subcmd](#subcmd), this runs the argument as its own, complete precision shell command.  When the command exits, the `not` command will invert the exit code, such that an exit code of `0` becomes `1`, and any non-zero exit code becomes `0`.\n\n### pwd\n\n**Compile flag**: `-DUSE_CMD_PWD`\n\n**Usage**: `pwd -`\n\n**Usage**: `pwd (env variable name)`\n\nWhen used with the argument `-`, the current working directory is written to stdout on its own line.  Otherwise, the environment variable name argument has the current working directory path stored in its value.\n\nMultiple arguments can be given, where the same rules apply for each argument.\n\n### rm\n\n**Compile flag**: `-DUSE_CMD_RM`\n\n**Usage**: `rm [file1 [file2 ...]]`\n\nRemoves each file passed as an argument.  The command will attempt to remove each file, and if any of them fail, then the whole command fails with an exit code equal to the sum of the error codes.\n\n### rmdir\n\n**Compile flag**: `-DUSE_CMD_RMDIR`\n\n**Usage**: `rmdir [dir1 [dir2 ...]]`\n\nRemoves each empty directory passed as an argument.  If a directory is not empty, the command will fail.  The command will attempt to remove each directory, and if any of them fail, then the whole command fails with an exit code equal to the sum of the number of failed files.\n\n### signal-wait\n\n**Compile flag**: `-DUSE_CMD_SIGNAL`\n\n**Usage**: `signal [*ENV] [signal1 [signal2]] [wait]`\n\nTraps OS signal numbers passed as arguments.  If `wait` is given, then it waits for any listed signal to occur before continuing.  If no signal is given (just `signal wait`), then it waits for a standard OS interruption, which will kill the whole process.  If the command includes a `*ENV` (where `ENV` is some environment name) argument, and presh has [environment variable parsing](#environment-variables) enabled, then the shell stores trapped OS signal numbers in the environment variable.\n\nOf note, once a signal is added to the list, it is registered for standard OS ignoring.  This applies to the current instruction, future `signal` instruction, and even some signals with child processes.  Only by adding the statement `wait` will the processing wait for the signal to be raised, and even then only for the signals requested in the current command.  This can be used for interesting applications, such as:\n\n```bash\npresh -c \"signal 2 ; signal 15 wait\"\n```\n\nThis will cause the shell to ignore SIGINT (2, usually sent by a ctrl-c input), and wait for SIGTERM (15).\n\n*Note that `dietlibc` [does not support ignoring signals not waited on, and will exit with an error if the to-be-ignored signals are received](https://github.com/groboclown/precision-shell/issues/2).*\n\n**Example 1:**\n\nRun a process that doesn't listen for OS signals, and instead have the shell take that ownership.\n\n```bash\n#! /usr/bin/presh -f\n\n# [ Spawn the process ]\nspawn [/usr/sbin/my-server] SERVER_PID \u0026\u0026 subcmd [\n    # [ Run this in a subcmd so that fail/pass of each of these instructions ]\n    # [   only runs when the spawn started successfully. ]\n\n    signal 1 2 9 15 17 wait\n\n    # [ Force the child to die, in case the signal wasn't a SIGCHLD (17). ]\n    kill-pid 15 ${SERVER_PID}\n\n    # [ Capture the exit code of the spawned server. ]\n    wait-pid ${SERVER_PID} *EXIT\n\n    # [ Exit the script with the spawned server's exit code. ]\n    exit ${EXIT}\n]\n```\n\n**Example 2:**\n\nRun a process that might generate an unexpected error.\n\n```bash\n#! /usr/bin/presh -f\n\nFIXME this example needs an implementation.\n\n```\n\n### sleep\n\n**Compile flag**: `-DUSE_CMD_SLEEP`\n\n**Usage**: `sleep [seconds [seconds ...]]`\n\nSleeps for the number of seconds in the argument.  If no arguments are given, or if an argument is not a positive integer, then it does nothing (no error).  If multiple, positive integers are given, then it sleeps for the sum of them.\n\n### spawn\n\n**Compile flag**: `-DUSE_CMD_SPAWN`\n\n**Usage**: `spawn (quoted command) [env for pid]`\n\nLaunch a new process in the background.  If the second argument is given, then the launched PID is stored in that value and exported to the environment variables.\n\n**Example 1:**\n\nLaunch one command in the background and switch to a different command in the foreground.\n\n```bash\npresh -c \"\\\n  spawn [/usr/bin/httpd -c /etc/local.config] \u0026\u0026 \\\n  exec [/usr/bin/tail -F /var/log/httpd/error.log]\"\n```\n\n**Example 2:**\n\nWith [environment variable parsing](#environment-variables) enabled, commands can spawn, wait for spawned processes to finish, and kill them.\n\n```bash\npresh -c \"\\\n    spawn [/usr/bin/sleep 10000] FOREVER \u0026\u0026 \\\n    spawn [/usr/bin/sleep 10] TEN \u0026\u0026 \\\n    spawn [/usr/bin/sleep 3] THREE \u0026\u0026\\\n    echo [Spawned \\${FOREVER} then \\${TEN} then \\${THREE}] ; \\\n    wait-pid \u003eFIRST ; \\\n    echo [\\${FIRST} just completed.] ;\n    wait-pid \u003eSECOND ; \\\n    echo [\\${SECOND} just completed.] ;\n    kill-pid 15 \\${FOREVER}\"\n```\n\n### start-timer\n\n**Compile flag**: `-DUSE_CMD_START_TIMER` (but is also included if [elapsed-time](#elapsed-time) or [export-elapsed-time](#export-elapsed-time) is included)\n\n**Usage**: `start-timer`\n\nSets the current time in the global timer.  If never set, the global timer is set to the epoch.  This can be queried with the [elapsed-time](#elapsed-time) and [export-elapsed-time](#export-elapsed-time) commands.\n\n### su-exec\n\n**Compile flag**: `-DUSE_CMD_SU_EXEC`\n\n**Usage**: `su-exec (UID) (GID) (command as an argument)`\n\nSwitches the current user to user id UID (not the user name) and group id GID (not the group name), then runs the third argument.  Unlike [`exec`](#exec), this will not keep running arguments if the first one fails to find a command but instead immediately exit the process.\n\n### su-spawn\n\n**Compile flag**: `-DUSE_CMD_SU_SPAWN`\n\n**Usage**: `su-spawn (UID) (GID) (command as an argument) [env for pid]`\n\nSwitches the current user to user id UID (not the user name) and group id GID (not the group name), and launches the third argument as a new process in the background.  If the fourth argument is given, then the launched PID is stored in that value and exported to the environment variables.\n\nTake note that, if the command execution fails to run, or the user ID cannot be changed, then the only way for presh to identify the failure is by inspecting the exit code through [`wait-pid`](#wait-pid).\n\n### subcmd\n\n**Compile flag**: `-DUSE_CMD_SUBCMD`\n\n**Usage**: `subcmd [command as an argument [command ...]]`\n\nRuns each argument as a whole command.  Useful when the sub-command must run only if the previous command succeeded to work around the [`\u0026\u0026`](#chaining-commands) limitation.\n\n**Example:**\n\n```bash\npresh -c \"\\\n  touch /tmp/file \u0026\u0026 \\\n  subcmd [\\\n    ln-s /tmp/file /tmp/foo \u0026\u0026 \\\n    ln-s /tmp/file /tmp/bar \\\n  ] ; \\\n  echo done\"\n```\n\n### test-connect\n\n**Compile flag:**: `-DUSE_CMD_TEST_CONNECT`\n\n**Usage**: `test-connect (hostname) (port) (timeout)`\n\nAttempts to open a socket connection to the server on the given port or service name.  If the server does not accept the connection within the given timeout seconds, then the command generates an error.\n\n**Example 1:**\n\nConnect to an IPv4 dot-notation host on port 80, with a maximum of 5 seconds waiting for a connection.\n\n```bash\npresh -c \"\\\n  test-connect 127.0.0.1 80 5\n  \"\n```\n\n**Example 2:**\n\nConnect to an IPv6 colon-notation host on port 80, with a maximum of 5 seconds waiting for a connection.\n\n```bash\npresh -c \"test-connect ::0 80 5\"\n```\n\n**Example 3:**\n\nConnect to the server \"google.com\" using its registered service name \"http\", with a maximum of 2 seconds waiting for a connection.\n\n```bash\npresh -c \"test-connect google.com http 2 \u0026\u0026 echo [We have Internet connectivity]\"\nWe have Internet connectivity\n```\n\n### touch\n\n**Compile flag**: `-DUSE_CMD_TOUCH`\n\n**Usage**: `touch [file1 [file2 ...]]`\n\nFor each argument, if it does not exist, it is created.  If the argument exists and is not a file, then the command fails.  **Warning:** Unlike the standard `touch` command, this will not update the modified time of the file.\n\nIf this creates a file, the file will have the file permissions set in [`fmode`](#fmode).\n\n### trunc\n\n**Compile flag**: `-DUSE_CMD_TRUNC`\n\n**Usage**: `trunc [file1 [file2 ...]]`\n\nFor each argument, sets the file length to 0 if the file exists, otherwise creates the file.  This is nearly identical to [touch](#touch), with the addition of setting file lengths to 0.\n\nIf this creates a file, the file will have the file permissions set in [`fmode`](#fmode).\n\n### wait-pid\n\n**Compile flag**: `-DUSE_CMD_WAIT_PID`\n\n**Usage**: `wait-pid (pid number) [*(env name)]`\n\n**Usage**: `wait-pid \u003e(env name)`\n\n**Usage**: `wait-pid \u003e(env name) [*(env name)]`\n\nIf passed with a numeric argument, it waits for the PID with that number to finish running.  If passed with an argument that starts with `\u003e`, then the command waits for the next child to finish, and stores its PID in the environment variable with the name after the `\u003e`.\n\nMultiple PID or `\u003eENV` arguments can be given, and the command will wait for each one to complete.  If a follow up argument in the form `*(ENV NAME)` is given, then the exit code from the process is stored in the given environment variable.\n\n**Example 1:**\n\nwait-pid + spawn + exit can be used together to create the equivalent of a normal shell's command execution.\n\nIn this example, because the environment variables REQUEST_PID and REQUEST_EXIT might have a chance of being already set, the variable replacement is escaped with an extra `$` in order to prevent the outer string parsing from replacing the value early.\n\n```bash\npresh -c \"\\\n  if-then \\\n    [spawn [/usr/bin/process-request] REQUEST_PID] \\\n    [ \\\n      wait-pid \\$\\${REQUEST_PID} *REQUEST_EXIT \u0026\u0026 \\\n      exit \\$\\${REQUEST_EXIT}\n    ] \\\n    [exit 1] \\\n  \"\n```\n\n### while-error\n\n**Compile flag**: `-DUSE_CMD_WHILE_ERROR`\n\n**Usage**: `while-error (error-sub-cmd) (loop-sub-cmd)`\n\nExecutes the error sub command (first argument), and if it generates an error, runs the loop sub command (second argument) then repeats.  If the error sub-command executes and does not generate an error, then the statement ends.\n\nBoth arguments are required.  If the loop block isn't necessary, then you can use [`noop`](#noop) as the second argument.\n\n**Example 1:**\n\n```bash\npresh -c \"\\\n  while-error \\\n      [mv /tmp/config.txt config.txt] \\\n      [sleep 5 ; exec [download-config /tmp/config.txt]]\"\n```\n\nThis will run `mv /tmp/config.txt config.txt`, and if it fails (in this case, because /tmp/config.txt does not exist), then it waits for 5 seconds then executes the native program `download-config /tmp/config.txt`.  This would be useful for a program that needs to download a file, but the file may not be available.\n\n### while-no-error\n\n**Compile flag**: `-DUSE_CMD_WHILE_NO_ERROR`\n\n**Usage**: `while-no-error (sub-cmd) (loop-sub-cmd)`\n\nIdentical to [`while-error`](#while-error), except that it stops looping when the first argument's sub command generates an error.\n\n### write-fd\n\n**Compile flag**: `-DUSE_CMD_WRITE_FD`\n\n**Usage**: `write-fd (fd) [text] [text]`\n\nWrites to the file descriptor the precise text given.  Each argument is written as is without spaces between them, and no newline is inserted except where explicitly added to the text.\n\n**Example 1:**\n\nWith the following script:\n\n```bash\n#! presh -f\n\ndup 12 new-file.txt\nwrite-fd 12 [text1] [text2] [text3\\ntext4]\n```\n\nThis will generate the file `new-file.txt` with the contents:\n\n```\ntext1text2text3\ntext4\n```\n\n### version\n\n**Compile flag**: *always present*\n\n**Usage**: `version`\n\nPrints the version information to stdout.  Any additional arguments generates an error.\n\n\n\n\n### Chaining Commands\n\nLike most shells, you can chain commands together with `\u0026\u0026` and `;`.  `\u0026\u0026` stops the execution if the previous command failed and allows another command after it; and `;` resets the error count to 0 and allows another command to follow it.  If a new line is encountered, that acts like a `;`.  The common shell `||` chain (run if non-zero error code) is not supported; instead, you will need to take advantage of the [if-else](#if-else) command.\n\n```bash\n$ ls\npresh\n$ ./presh -c \"echo abc \u0026\u0026 rmdir does-not-exist ; echo dce\"\nabc\nERROR rmdir: does-not-exist\ndce\n$ ./presh -c \"echo abc \u0026\u0026 rmdir does-not-exist \u0026\u0026 echo dce\"\nabc\nERROR rmdir: does-not-exist\nFAIL \u0026\u0026\n$ ./presh -c \"echo abc \\\necho def\"\nabc\ndef\n```\n\nA `;` character erases the previous command's error state, which makes `\u0026\u0026` only sensitive to the previous command's error.\n\n```bash\n$ ./presh -c \"touch a.txt \\\n  ; rm a.txt \\\n  ; # [ This next rm command should fail ] \\\n  ; rm a.txt \\\n  ; echo Continuing \\\n  \u0026\u0026 echo Ending\"\nERROR rm: a.txt\nContinuing\nEnding\n```\n\nOn top of this, if you terminate the commands with a `;`, then, even if the command ended with an error, the full `presh` execution will not ([`exit`](#exit), though, is immune to this).\n\n```bash\n$ ./presh -c \"is-eq 1 2\" \u0026\u0026 echo passed || echo failed\nERROR is-eq: 2\nfailed\n$ ./presh -c \"is-eq 1 2 ;\" \u0026\u0026 echo passed || echo failed\nERROR is-eq: 2\npassed\n```\n\nA [future feature](https://github.com/groboclown/precision-shell/issues/14) may allow changing the newline behavior via a compile flag.\n\n### Environment Variables\n\n**Compile flag**: `-DUSE_ENVIRONMENT_INPUT`\n\nWith environment variable parsing enabled, argument parsing will replace text in the form `${VALUE}` with the environment variable contained in the `${}` marks.  Currently, these are not expanded into multiple arguments, but instead will be considered part of a single argument.  Because the [`echo`](#echo) command outputs one argument per line, this shows the behavior clearly:\n\n```bash\n$ echo \"echo \\${ABC} \\${ABC}\" \u003e script.txt\n$ cat script.txt\necho ${ABC} ${ABC}\n$ export ABC=\"a b c\"\n$ ./presh -f script.txt\na b c\na b c\n```\n\nTo escape an environment variable to not parse it or delay its parsing, use a double `$` character.  See [`wait-pid`](#wait-pid) for an example of where this is necessary.  In general, doubling-up the `$` to escape an environment variable is necessary in sub-commands when the environment variable is set inside other sub-commands.\n\n```bash\n$ presh \"echo \\$\\${ABC}\"\n${ABC}\n```\n\nAlong with the normal environment variables, it also adds the shell-like `${?}` environment reference to allow `;` separated commands to capture the error code of the previous command.  Note that this adds the environment variable `?`; it's not just a shell variable.\n\n### Standard Script Flag\n\nThe tool also supports invoking it with the arguments `-c \"commands\"` to simulate running the second argument as a script.\n\n### Command Parsing\n\nThe parsing is kept simple, and follows these rules:\n\n* A space, tab (`\\t`), and linefeed (`\\r`) separates arguments.\n* The parser will interpret newlines (`\\n`) like a semi-colon (`;`), which is the ignore-error command separator.  A [future feature](https://github.com/groboclown/precision-shell/issues/14) may allow changing the newline behavior via a compile flag.\n* Arguments are quoted with `[` and `]` pairs, and can be embedded in each other, so that a command like `echo [a [b]]` will output \"a [b]\" without needing to escape the `[` and `]` marks.\n* Characters can be escaped by adding a backslash (`\\`) character.  `\\n` turns into a newline, `\\r` into a linefeed, `\\t` into a tab, and anything else is the character itself (for example, inserting a backslash is the standard `\\\\`).\n\n### Script Files\n\n**Compile flag**: `-DUSE_STREAMING_INPUT`\n\nIf you use the input-enabled build, then you can use these additional forms of the command:\n\n* `presh -f script-file.txt`\n  runs the commands contained in the file `script-file.txt`\n* `presh -`\n  reads commands from stdin.\n\n### Better Error Reporting\n\n**Compile flag**: `-DREQUIRE_FULL_CMD`\n\nSome commands, like [`ln-s`](#ln-s), require an exact number of arguments.  Unless this flag is set, using the command with just one argument will not cause a failure.  With this flag, `presh` will generate an error.\n\n### Passing Commands from stdin\n\n**Compile flag**: `-DUSE_STREAMING_INPUT`\n\nIf you use the input-enabled build, then you can pass the argument `-` to have the tool read commands from stdin.\n\n\n# Compiled Size\n\nLast build size:\n\n* Do-nothing build:\n  * [glibc (Ubuntu)](#build-glibc.Dockerfile): 706,568 bytes\n  * [glibc (Arch)](#build-glibc-arch.Dockerfile): 698,808 bytes\n  * [clang/musl (Alpine)](#build-clang.Dockerfile): 25,816 bytes\n  * [musl (Alpine)](#build-musl.Dockerfile): 25,792 bytes\n  * [dietlibc (Alpine)](#build-dietlibc.Dockerfile): 13,200 bytes\n* Minimal build:\n  * glibc (Ubuntu): 710,664 bytes\n  * glibc (Arch): 698,808 bytes\n  * clang/musl (Alpine): 25,816 bytes\n  * musl (Alpine): 25,792 bytes\n  * dietlibc (Alpine): 17,296 bytes\n* Standard build:\n  * glibc (Ubuntu): 714,760 bytes\n  * glibc (Arch): 702,904 bytes\n  * clang/musl (Alpine): 29,912 bytes\n  * musl (Alpine): 29,888 bytes\n  * dietlibc (Alpine): 17,296 bytes\n* Non-Network build:\n  * glibc (Ubuntu): 731,144 bytes\n  * glibc (Arch): 727,480 bytes\n  * clang/musl (Alpine): 50,400 bytes\n  * musl (Alpine): 46,280 bytes\n  * dietlibc (Alpine): 29,592 bytes\n* Full build:\n  * glibc (Ubuntu): 972,872 bytes\n  * glibc (Arch): 944,632 bytes\n  * clang/musl (Alpine): 87,264 bytes\n  * musl (Alpine): 87,240 bytes\n  * musl with self-extracting executable (Alpine): 75,808 bytes\n  * dietlibc (Alpine): 46,072 bytes\n\n*dietlibc [requires](https://www.fefe.de/dietlibc/FAQ.txt) that you either not distribute the compiled executable, or release the executable under GPL v2.*\n\nThese file sizes are *statically compiled*, so they don't have any external dependencies other than the Linux OS.  Due to its nature, glibc cannot run some networking functionality (specifically hostname lookups) when statically compiled.  Some documentation indicates that, by statically compiling the executable with GPL shared libraries, that causes the final executable to become itself covered under GPL.  Please follow best practices when distributing executables.\n\nThese were compiled within Docker containers, which are supplied in the code.  For each stdlib library (glibc, musl, dietlibc), the Linux distribution used to compile it is listed.  This is because the Arch Linux compile size is different than the Ubuntu compile size for the same library.  Your milage may differ depending on the distribution and compiler and other minor differences you use.\n\ndietlibc exhibits slightly different behavior than the other libraries, specifically around the [`signal`](#signal-wait) command.  Please see the command documentation for a description of the differences.  If you select a different library for your compilation, please ensure that the provided test suite passes.\n\n\n# Contributing\n\nTo contribute to the project, submit a PR or open a bug.  All code submitted must be licensed under the MIT license.\n\nContributing new commands requires (this list assumes that the change is for a single command contained in one file, but it can be multiple commands in a single file, or multiple files, each with their own command):\n\n1. Read through the [source readme file](src/README.md).  This describes how to add commands, though it's lacking on the details that go into a full change.\n2. Add cmd_mount.h.in in the src tree and include it in source control changes.\n3. Add the cmd_mount.h file to the src/Makefile header list and include the Makefile in source control changes.\n4. Generate the .h file by running make and add that to source control.\n5. Include the new USE_CMD_* in the flag list in the Makefile.command-flags file, both in the list of flags, and in the INCLUDE_ALL_COMMANDS list.\n6. Add new test scripts in the `tests` directory.  The [test readme file](tests/README.md) offers a brief overview of what goes into a test script.\n7. Add documentation in the root [README.md](README.md) file, both in the initial command listing, and the detailed description.\n\nSee the [CONTRIBUTING.md](CONTRIBUTING.md) file for additional details.\n\n## Reporting Security Issues\n\nIf you think you discovered an issue that allows for a remote attack on computer running Precision Shell, please open a bug report with enough information to describe what it is, then a representative with the project will reach out to find out more information on a private channel if it's determined to be severe enough to fix without reporting too much information.\n\n\n# License\n\nPrecision Shell is licensed under the [MIT license](LICENSE).\n\nDepending on how you compile the executable, the executable may be under a different license.  For example, compiling with the `dietlibc` library causes the compiled executable to be under GPL 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgroboclown%2Fprecision-shell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgroboclown%2Fprecision-shell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgroboclown%2Fprecision-shell/lists"}