# pure sh bible A collection of pure POSIX `sh` alternatives to external processes **NOTE**: If anything seen here is **not** POSIX `sh`, open an issue. This is a living document that is open to change and improvement. ## Table of Contents * [STRINGS](#strings) * [Strip pattern from start of string](#strip-pattern-from-start-of-string) * [Strip pattern from end of string](#strip-pattern-from-end-of-string) * [Trim all white-space from string and truncate spaces](#trim-all-white-space-from-string-and-truncate-spaces) * [Check if string contains a sub-string](#check-if-string-contains-a-sub-string) * [Check if string starts with sub-string](#check-if-string-starts-with-sub-string) * [Check if string ends with sub-string](#check-if-string-ends-with-sub-string) * [FILES](#files) * [Get the first N lines of a file](#get-the-first-n-lines-of-a-file) * [Get the number of lines in a file](#get-the-number-of-lines-in-a-file) * [Count files or directories in directory](#count-files-or-directories-in-directory) * [Create an empty file](#create-an-empty-file) * [FILE PATHS](#file-paths) * [Get the directory name of a file path](#get-the-directory-name-of-a-file-path) * [Get the base-name of a file path](#get-the-base-name-of-a-file-path) * [ESCAPE SEQUENCES](#escape-sequences) * [Text Colors](#text-colors) * [Text Attributes](#text-attributes) * [Cursor Movement](#cursor-movement) * [Erasing Text](#erasing-text) * [PARAMETER EXPANSION](#parameter-expansion) * [Replacement](#replacement) * [Length](#length) * [Default Value](#default-value) * [CONDITIONAL EXPRESSIONS](#conditional-expressions) * [File Conditionals](#file-conditionals) * [Variable Conditionals](#variable-conditionals) * [Variable Comparisons](#variable-comparisons) * [ARITHMETIC OPERATORS](#arithmetic-operators) * [Assignment](#assignment) * [Arithmetic](#arithmetic) * [Bitwise](#bitwise) * [Logical](#logical) * [Miscellaneous](#miscellaneous) # STRINGS ## Strip pattern from start of string **Example Function:** ```sh lstrip() { # Usage: lstrip "string" "pattern" printf '%s\n' "${1##$2}" } ``` **Example Usage:** ```shell $ lstrip "The Quick Brown Fox" "The " Quick Brown Fox ``` ## Strip pattern from end of string **Example Function:** ```sh rstrip() { # Usage: rstrip "string" "pattern" printf '%s\n' "${1%%$2}" } ``` **Example Usage:** ```shell $ rstrip "The Quick Brown Fox" " Fox" The Quick Brown ``` ## Trim all white-space from string and truncate spaces This is an alternative to `sed`, `awk`, `perl` and other tools. The function below works by abusing word splitting to create a new string without leading/trailing white-space and with truncated spaces. **Example Function:** ```sh # shellcheck disable=SC2086,SC2048 trim_all() { # Usage: trim_all " example string " set -f set -- $* printf '%s\n' "$*" set +f } ``` **Example Usage:** ```shell $ trim_all " Hello, World " Hello, World $ name=" John Black is my name. " $ trim_all "$name" John Black is my name. ``` ## Check if string contains a sub-string **Using a case statement:** ```shell case $var in *sub_string*) # Do stuff ;; *sub_string2*) # Do more stuff ;; *) # Else ;; esac ``` ## Check if string starts with sub-string **Using a case statement:** ```shell case $var in sub_string*) # Do stuff ;; sub_string2*) # Do more stuff ;; *) # Else ;; esac ``` ## Check if string ends with sub-string **Using a case statement:** ```shell case $var in *sub_string) # Do stuff ;; *sub_string2) # Do more stuff ;; *) # Else ;; esac ``` # FILES ## Get the first N lines of a file Alternative to the `head` command. **Example Function:** ```sh head() { # Usage: head "n" "file" while read -r line; do [ "$i" = "$1" ] && break printf '%s\n' "$line" i=$((i+1)) done < "$2" } ``` **Example Usage:** ```shell $ head 2 ~/.bashrc # Prompt PS1='➜ ' $ head 1 ~/.bashrc # Prompt ``` ## Get the number of lines in a file Alternative to `wc -l`. **Example Function:** ```sh lines() { # Usage: lines "file" while read -r _; do lines=$((lines+1)) done < "$1" printf '%s\n' "$lines" } ``` **Example Usage:** ```shell $ lines ~/.bashrc 48 ``` ## Count files or directories in directory This works by passing the output of the glob to the function and then counting the number of arguments. **Example Function:** ```sh count() { # Usage: count /path/to/dir/* # count /path/to/dir/*/ printf '%s\n' "$#" } ``` **Example Usage:** ```shell # Count all files in dir. $ count ~/Downloads/* 232 # Count all dirs in dir. $ count ~/Downloads/*/ 45 # Count all jpg files in dir. $ count ~/Pictures/*.jpg 64 ``` ## Create an empty file Alternative to `touch`. ```shell :>file # OR (shellcheck warns for this) >file ``` # FILE PATHS ## Get the directory name of a file path Alternative to the `dirname` command. **Example Function:** ```sh dirname() { # Usage: dirname "path" printf '%s\n' "${1%/*}/" } ``` **Example Usage:** ```shell $ dirname ~/Pictures/Wallpapers/1.jpg /home/black/Pictures/Wallpapers/ $ dirname ~/Pictures/Downloads/ /home/black/Pictures/ ``` ## Get the base-name of a file path Alternative to the `basename` command. **Example Function:** ```sh basename() { # Usage: basename "path" path=${1%/} printf '%s\n' "${path##*/}" } ``` **Example Usage:** ```shell $ basename ~/Pictures/Wallpapers/1.jpg 1.jpg $ basename ~/Pictures/Downloads/ Downloads ``` # ESCAPE SEQUENCES Contrary to popular belief, there is no issue in utilizing raw escape sequences. Using `tput` abstracts the same ANSI sequences as if printed manually. Worse still, `tput` is not actually portable. There are a number of `tput` variants each with different commands and syntaxes (*try `tput setaf 3` on a FreeBSD system*). Raw sequences are fine. ## Text Colors **NOTE:** Sequences requiring RGB values only work in True-Color Terminal Emulators. | Sequence | What does it do? | Value | | -------- | ---------------- | ----- | | `\e[38;5;m` | Set text foreground color. | `0-255` | `\e[48;5;m` | Set text background color. | `0-255` | `\e[38;2;;;m` | Set text foreground color to RGB color. | `R`, `G`, `B` | `\e[48;2;;;m` | Set text background color to RGB color. | `R`, `G`, `B` ## Text Attributes | Sequence | What does it do? | | -------- | ---------------- | | `\e[m` | Reset text formatting and colors. | `\e[1m` | Bold text. | | `\e[2m` | Faint text. | | `\e[3m` | Italic text. | | `\e[4m` | Underline text. | | `\e[5m` | Slow blink. | | `\e[7m` | Swap foreground and background colors. | ## Cursor Movement | Sequence | What does it do? | Value | | -------- | ---------------- | ----- | | `\e[;H` | Move cursor to absolute position. | `line`, `column` | `\e[H` | Move cursor to home position (`0,0`). | | `\e[A` | Move cursor up N lines. | `num` | `\e[B` | Move cursor down N lines. | `num` | `\e[C` | Move cursor right N columns. | `num` | `\e[D` | Move cursor left N columns. | `num` | `\e[s` | Save cursor position. | | `\e[u` | Restore cursor position. | ## Erasing Text | Sequence | What does it do? | | -------- | ---------------- | | `\e[K` | Erase from cursor position to end of line. | `\e[1K` | Erase from cursor position to start of line. | `\e[2K` | Erase the entire current line. | `\e[J` | Erase from the current line to the bottom of the screen. | `\e[1J` | Erase from the current line to the top of the screen. | `\e[2J` | Clear the screen. | `\e[2J\e[H` | Clear the screen and move cursor to `0,0`. # PARAMETER EXPANSION ## Replacement | Parameter | What does it do? | | --------- | ---------------- | | `${VAR#PATTERN}` | Remove shortest match of pattern from start of string. | | `${VAR##PATTERN}` | Remove longest match of pattern from start of string. | | `${VAR%PATTERN}` | Remove shortest match of pattern from end of string. | | `${VAR%%PATTERN}` | Remove longest match of pattern from end of string. | ## Length | Parameter | What does it do? | | --------- | ---------------- | | `${#VAR}` | Length of var in characters. ## Default Value | Parameter | What does it do? | | --------- | ---------------- | | `${VAR:-STRING}` | If `VAR` is empty or unset, use `STRING` as its value. | `${VAR-STRING}` | If `VAR` is unset, use `STRING` as its value. | `${VAR:=STRING}` | If `VAR` is empty or unset, set the value of `VAR` to `STRING`. | `${VAR=STRING}` | If `VAR` is unset, set the value of `VAR` to `STRING`. | `${VAR:+STRING}` | If `VAR` is not empty, use `STRING` as its value. | `${VAR+STRING}` | If `VAR` is set, use `STRING` as its value. | `${VAR:?STRING}` | Display an error if empty or unset. | `${VAR?STRING}` | Display an error if unset. # CONDITIONAL EXPRESSIONS For use in `[ ]` `if [ ]; then` and `test`. ## File Conditionals | Expression | Value | What does it do? | | ---------- | ------ | ---------------- | | `-b` | `file` | If file exists and is a block special file. | `-c` | `file` | If file exists and is a character special file. | `-d` | `file` | If file exists and is a directory. | `-e` | `file` | If file exists. | `-f` | `file` | If file exists and is a regular file. | `-g` | `file` | If file exists and its set-group-id bit is set. | `-h` | `file` | If file exists and is a symbolic link. | `-p` | `file` | If file exists and is a named pipe (*FIFO*). | `-r` | `file` | If file exists and is readable. | `-s` | `file` | If file exists and its size is greater than zero. | `-t` | `fd` | If file descriptor is open and refers to a terminal. | `-u` | `file` | If file exists and its set-user-id bit is set. | `-w` | `file` | If file exists and is writable. | `-x` | `file` | If file exists and is executable. | `-L` | `file` | If file exists and is a symbolic link. | `-S` | `file` | If file exists and is a socket. ## Variable Conditionals | Expression | Value | What does it do? | | ---------- | ----- | ---------------- | | `-z` | `var` | If the length of string is zero. | `-n` | `var` | If the length of string is non-zero. ## Variable Comparisons | Expression | What does it do? | | ---------- | ---------------- | | `var = var2` | Equal to. | `var != var2` | Not equal to. | `var -eq var2` | Equal to (*algebraically*). | `var -ne var2` | Not equal to (*algebraically*). | `var -gt var2` | Greater than (*algebraically*). | `var -ge var2` | Greater than or equal to (*algebraically*). | `var -lt var2` | Less than (*algebraically*). | `var -le var2` | Less than or equal to (*algebraically*). # ARITHMETIC OPERATORS ## Assignment | Operators | What does it do? | | --------- | ---------------- | | `=` | Initialize or change the value of a variable. ## Arithmetic | Operators | What does it do? | | --------- | ---------------- | | `+` | Addition | `-` | Subtraction | `*` | Multiplication | `/` | Division | `**` | Exponentiation | `%` | Modulo | `+=` | Plus-Equal (*Increment a variable.*) | `-=` | Minus-Equal (*Decrement a variable.*) | `*=` | Times-Equal (*Multiply a variable.*) | `/=` | Slash-Equal (*Divide a variable.*) | `%=` | Mod-Equal (*Remainder of dividing a variable.*) ## Bitwise | Operators | What does it do? | | --------- | ---------------- | | `<<` | Bitwise Left Shift | `<<=` | Left-Shift-Equal | `>>` | Bitwise Right Shift | `>>=` | Right-Shift-Equal | `&` | Bitwise AND | `&=` | Bitwise AND-Equal | `\|` | Bitwise OR | `\|=` | Bitwise OR-Equal | `~` | Bitwise NOT | `^` | Bitwise XOR | `^=` | Bitwise XOR-Equal ## Logical | Operators | What does it do? | | --------- | ---------------- | | `!` | NOT | `&&` | AND | `\|\|` | OR ## Miscellaneous | Operators | What does it do? | Example | | --------- | ---------------- | ------- | | `,` | Comma Separator | `((a=1,b=2,c=3))`