pure-sh-bible/README.md

568 lines
13 KiB
Markdown
Raw Normal View History

2019-09-19 09:36:16 +02:00
# pure sh bible
A collection of pure POSIX `sh` alternatives to external processes
2019-09-19 10:56:39 +02:00
**NOTE**: If anything seen here is **not** POSIX `sh`, open an issue. This is a living document that is open to change and improvement.
2019-09-19 09:36:16 +02:00
## Table of Contents
<!-- vim-markdown-toc GFM -->
2019-09-19 09:56:28 +02:00
* [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)
2019-09-19 09:58:29 +02:00
* [Trim all white-space from string and truncate spaces](#trim-all-white-space-from-string-and-truncate-spaces)
2019-09-19 10:01:21 +02:00
* [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)
2019-09-19 10:22:33 +02:00
* [FILES](#files)
* [Get the first N lines of a file](#get-the-first-n-lines-of-a-file)
2019-09-19 10:30:09 +02:00
* [Get the number of lines in a file](#get-the-number-of-lines-in-a-file)
2019-09-19 10:33:53 +02:00
* [Count files or directories in directory](#count-files-or-directories-in-directory)
* [Create an empty file](#create-an-empty-file)
2019-09-19 10:36:27 +02:00
* [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)
2019-09-19 10:42:19 +02:00
* [ESCAPE SEQUENCES](#escape-sequences)
* [Text Colors](#text-colors)
* [Text Attributes](#text-attributes)
* [Cursor Movement](#cursor-movement)
* [Erasing Text](#erasing-text)
2019-09-19 10:45:48 +02:00
* [PARAMETER EXPANSION](#parameter-expansion)
* [Replacement](#replacement)
* [Length](#length)
* [Default Value](#default-value)
2019-09-19 10:52:26 +02:00
* [CONDITIONAL EXPRESSIONS](#conditional-expressions)
* [File Conditionals](#file-conditionals)
* [Variable Conditionals](#variable-conditionals)
* [Variable Comparisons](#variable-comparisons)
2019-09-19 10:56:39 +02:00
* [ARITHMETIC OPERATORS](#arithmetic-operators)
* [Assignment](#assignment)
* [Arithmetic](#arithmetic)
* [Bitwise](#bitwise)
* [Logical](#logical)
* [Miscellaneous](#miscellaneous)
2019-09-19 10:58:40 +02:00
* [ARITHMETIC](#arithmetic-1)
* [Ternary Tests](#ternary-tests)
2019-09-19 11:00:25 +02:00
* [TRAPS](#traps)
* [Do something on script exit](#do-something-on-script-exit)
* [Ignore terminal interrupt (CTRL+C, SIGINT)](#ignore-terminal-interrupt-ctrlc-sigint)
2019-09-19 11:01:15 +02:00
* [OBSOLETE SYNTAX](#obsolete-syntax)
* [Command Substitution](#command-substitution)
2019-09-19 09:56:28 +02:00
2019-09-19 09:36:16 +02:00
<!-- vim-markdown-toc -->
2019-09-19 09:56:28 +02:00
# 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
```
2019-09-19 09:58:29 +02:00
## 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.
```
2019-09-19 10:01:21 +02:00
## 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
```
2019-09-19 10:22:33 +02:00
# 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
```
2019-09-19 10:30:09 +02:00
## 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
```
2019-09-19 10:33:53 +02:00
## 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
```
2019-09-19 10:36:27 +02:00
# 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
```
2019-09-19 10:42:19 +02:00
# 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;<NUM>m` | Set text foreground color. | `0-255`
| `\e[48;5;<NUM>m` | Set text background color. | `0-255`
| `\e[38;2;<R>;<G>;<B>m` | Set text foreground color to RGB color. | `R`, `G`, `B`
| `\e[48;2;<R>;<G>;<B>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[<LINE>;<COLUMN>H` | Move cursor to absolute position. | `line`, `column`
| `\e[H` | Move cursor to home position (`0,0`). |
| `\e[<NUM>A` | Move cursor up N lines. | `num`
| `\e[<NUM>B` | Move cursor down N lines. | `num`
| `\e[<NUM>C` | Move cursor right N columns. | `num`
| `\e[<NUM>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`.
2019-09-19 10:45:48 +02:00
# 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.
2019-09-19 10:52:26 +02:00
# CONDITIONAL EXPRESSIONS
2019-09-19 10:53:47 +02:00
For use in `[ ]` `if [ ]; then` and `test`.
2019-09-19 10:52:26 +02:00
## 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*).
2019-09-19 10:56:39 +02:00
# 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))`
2019-09-19 10:58:40 +02:00
# ARITHMETIC
## Ternary Tests
```shell
# Set the value of var to var2 if var2 is greater than var.
# 'var2 > var': Condition to test.
# '? var2': If the test succeeds.
# ': var': If the test fails.
var=$((var2 > var ? var2 : var))
```
2019-09-19 11:00:25 +02:00
# TRAPS
Traps allow a script to execute code on various signals. In [pxltrm](https://github.com/dylanaraps/pxltrm) (*a pixel art editor written in bash*) traps are used to redraw the user interface on window resize. Another use case is cleaning up temporary files on script exit.
Traps should be added near the start of scripts so any early errors are also caught.
## Do something on script exit
```shell
# Clear screen on script exit.
trap 'printf \\e[2J\\e[H\\e[m' EXIT
# Run a function on script exit.
# 'clean_up' is the name of a function.
trap clean_up EXIT
```
## Ignore terminal interrupt (CTRL+C, SIGINT)
```shell
trap '' INT
```
2019-09-19 11:01:15 +02:00
# OBSOLETE SYNTAX
## Command Substitution
Use `$()` instead of `` ` ` ``.
```shell
# Right.
var="$(command)"
# Wrong.
var=`command`
# $() can easily be nested whereas `` cannot.
var="$(command "$(command)")"
```