diff --git a/.travis.yml b/.travis.yml index 60d8fd7..55c10f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,5 @@ os: - linux script: - - shellcheck -s bash --exclude=SC2034,SC2154 <(awk '/```sh/{f=1;next}/```/{f=0}f' README.md) + - shellcheck -s bash --exclude=SC2034,SC2154 <(awk '/```sh$/{f=1;next}/```/{f=0}f' README.md) - ./test.sh diff --git a/README.md b/README.md index a268898..407043b 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,8 @@ scripts and not full blown utilities. ### Trim leading and trailing white-space from string. +**Example Function:** + ```sh trim_string() { # Usage: trim_string " example string " @@ -113,12 +115,26 @@ trim_string() { } ``` +**Example Usage:** + +```shell +$ trim_string " Hello, World " +Hello, World + +$ name=" John Black " +$ trim_string "$name" +John Black +``` + + ### Trim all white-space from string and truncate spaces. +**Example Function:** + ```sh # shellcheck disable=SC2086,SC2048 trim_all() { - # Usage: trim " example string " + # Usage: trim_all " example string " set -f set -- $* printf '%s\n' "$*" @@ -126,6 +142,17 @@ trim_all() { } ``` +**Example Usage:** + +```shell +$ trim_all " Hello, World " +Hello, World + +$ name=" John Black is my name. " +$ trim_all "$name" +John Black is my name. +``` + ### Use REGEX on a string. We can use the result of `bash`'s regex matching to create a simple `sed` @@ -138,55 +165,68 @@ Stick to POSIX regex features if aiming for compatibility. **NOTE**: This example only prints the first matching group. When using multiple capture groups some modification will be needed. +**Example Function:** + ```sh regex() { # Usage: regex "string" "regex" [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}" } +``` -# Example: -# Trim leading white-space. -: regex ' hello' '^\s*(.*)' +**Example Usage:** +```shell +$ # Trim leading white-space. +$ regex ' hello' '^\s*(.*)' +hello -# Example script usage (Validate hex colors): -_() { - colors=( - "#FFFFFF" - "#000000" - "#CDEFDC" - "#12dlks" - "red" - ) +$ # Validate a hex color. +$ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' +#FFFFFF - for color in "${colors[@]}"; do - if [[ "$color" =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then - printf '%s\n' "${BASH_REMATCH[1]}" - else - printf '%s\n' "error: $color is an invalid color." - fi - done +$ # Validate a hex color (invalid). +$ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' +# no output (invalid) +``` + +**Example Usage in script:** + +```shell +is_hex_color() { + if [[ "$1" =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then + printf '%s\n' "${BASH_REMATCH[1]}" + else + printf '%s\n' "error: $1 is an invalid color." + return 1 + fi } + +read -r color +is_hex_color "$color" || color="#FFFFFF" + +# Do stuff. ``` ### Split a string on a delimiter. -```sh -_() { - # To multiple variables. - string="1,2,3" - IFS=, read -r var1 var2 var3 <<< "$string" +```shell +string="1,2,3" - # To an array. - IFS=, read -ra vars <<< "$string" -} +# To multiple variables. +IFS=, read -r var1 var2 var3 <<< "$string" + +# To an array. +IFS=, read -ra vars <<< "$string" ``` ### Change a string to lowercase. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh lower() { # Usage: lower "string" @@ -194,10 +234,25 @@ lower() { } ``` +**Example Usage:** + +```shell +$ lower "HELLO" +hello + +$ lower "HeLlO" +hello + +$ lower "hello" +hello +``` + ### Change a string to uppercase. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh upper() { # Usage: upper "string" @@ -205,8 +260,23 @@ upper() { } ``` +**Example Usage:** + +```shell +$ upper "hello" +HELLO + +$ upper "HeLlO" +HELLO + +$ upper "HELLO" +HELLO +``` + ### Trim quotes from a string. +**Example Function:** + ```sh trim_quotes() { # Usage: trim_quotes "string" @@ -215,42 +285,63 @@ trim_quotes() { } ``` +**Example Usage:** + +```shell +$ var="'Hello', \"World\"" +$ trim_quotes "$var" +Hello, World +``` + ### Strip all instances of pattern from string. +**Example Function:** + ```sh strip_all() { # Usage: strip_all "string" "pattern" printf '%s\n' "${1//$2}" } +``` -# Examples: +**Example Usage:** -# Output: "Th Qck Brwn Fx" -: strip_all "The Quick Brown Fox" "[aeiou]" +```shell +$ strip_all "The Quick Brown Fox" "[aeiou]" +Th Qck Brwn Fx -# Output: "TheQuickBrownFox" -: strip_all "The Quick Brown Fox" "[[:space:]]" +$ strip_all "The Quick Brown Fox" "[[:space:]]" +TheQuickBrownFox + +$ strip_all "The Quick Brown Fox" "Quick " +The Brown Fox ``` ### Strip first occurrence of pattern from string. +**Example Function:** + ```sh strip() { # Usage: strip "string" "pattern" printf '%s\n' "${1/$2}" } +``` -# Examples: +**Example Usage:** -# Output: "Th Quick Brown Fox" -: strip_all "The Quick Brown Fox" "[aeiou]" +```shell +$ strip "The Quick Brown Fox" "[aeiou]" +Th Quick Brown Fox -# Output: "TheQuick Brown Fox" -: strip_all "The Quick Brown Fox" "[[:space:]]" +$ strip "The Quick Brown Fox" "[[:space:]]" +TheQuick Brown Fox ``` ### Strip pattern from start of string. +**Example Function:** + ```sh lstrip() { # Usage: lstrip "string" "pattern" @@ -258,8 +349,17 @@ lstrip() { } ``` +**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" @@ -267,21 +367,26 @@ rstrip() { } ``` +**Example Usage:** + +```shell +$ rstrip "The Quick Brown Fox" " Fox" +The Quick Brown +``` + ## Variables ### Assign and access a variable using a variable. -```sh -_() { - hello_world="test" +```shell +hello_world="test" - # Create the variable name. - var1="world" - var2="hello_${var1}" +# Create the variable name. +var1="world" +var2="hello_${var1}" - # Print the value of the variable name stored in 'hello_$var1'. - printf '%s\n' "${!var2}" -} +# Print the value of the variable name stored in 'hello_$var1'. +printf '%s\n' "${!var2}" ``` @@ -292,16 +397,34 @@ _() { Enabling `extdebug` allows access to the `BASH_ARGV` array which stores the current function’s arguments in reverse. +**Example Function:** + ```sh reverse_array() { # Usage: reverse_array "array" - # reverse_array 1 2 3 4 5 6 shopt -s extdebug f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@" shopt -u extdebug } ``` +**Example Usage:** + +```shell +$ reverse_array 1 2 3 4 5 +5 +4 +3 +2 +1 + +$ arr=(red blue green) +$ reverse_array "${arr[@]}" +green +blue +red +``` + ### Remove duplicate array elements. Create a temporary associative array. When setting associative array @@ -310,6 +433,8 @@ allows us to effectively remove array duplicates. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh remove_array_dups() { # Usage: remove_array_dups "array" @@ -323,6 +448,23 @@ remove_array_dups() { } ``` +**Example Usage:** + +```shell +$ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 +1 +2 +3 +4 +5 + +$ arr=(red red green blue blue) +$ remove_array_dups "${arr[@]}" +red +green +blue +``` + ### Cycle through an array. Each time the `printf` is called, the next array element is printed. When @@ -360,24 +502,20 @@ cycle() { Alternative to the `cat` command. -```sh -_() { - file_data="$(<"file")" -} +```shell +file_data="$(<"file")" ``` ### Read a file to an array (*by line*). Alternative to the `cat` command. -```sh -_() { - # Bash <4 - IFS=$'\n' read -d "" -ra file_data < "file" +```shell +# Bash <4 +IFS=$'\n' read -d "" -ra file_data < "file" - # Bash 4+ - mapfile -t file_data < "file" -} +# Bash 4+ +mapfile -t file_data < "file" ``` ### Get the first N lines of a file. @@ -386,6 +524,8 @@ Alternative to the `head` command. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh head() { # Usage: head "n" "file" @@ -394,12 +534,25 @@ head() { } ``` +**Example Usage:** + +```shell +$ head 2 ~/.bashrc +# Prompt +PS1='➜ ' + +$ head 1 ~/.bashrc +# Prompt +``` + ### Get the last N lines of a file. Alternative to the `tail` command. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh tail() { # Usage: tail "n" "file" @@ -408,12 +561,25 @@ tail() { } ``` +**Example Usage:** + +```shell +$ tail 2 ~/.bashrc +# Enable tmux. +# [[ -z "$TMUX" ]] && exec tmux + +$ tail 1 ~/.bashrc +# [[ -z "$TMUX" ]] && exec tmux +``` + ### Get the number of lines in a file. Alternative to `wc -l`. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh lines() { # Usage lines "file" @@ -422,34 +588,39 @@ lines() { } ``` +**Example Usage:** + +```shell +$ lines ~/.bashrc +48 +``` + ### Iterate over files. Don’t use `ls`. -```sh -_() { - # Greedy example. - for file in *; do - printf '%s\n' "$file" - done +```shell +# Greedy example. +for file in *; do + printf '%s\n' "$file" +done - # PNG files in dir. - for file in ~/Pictures/*.png; do - printf '%s\n' "$file" - done +# PNG files in dir. +for file in ~/Pictures/*.png; do + printf '%s\n' "$file" +done - # Iterate over directories. - for dir in ~/Downloads/*/; do - printf '%s\n' "$dir" - done +# Iterate over directories. +for dir in ~/Downloads/*/; do + printf '%s\n' "$dir" +done - # Iterate recursively. - shopt -s globstar - for file in ~/Pictures/**/*; do - printf '%s\n' "$file" - done - shopt -u globstar -} +# Iterate recursively. +shopt -s globstar +for file in ~/Pictures/**/*; do + printf '%s\n' "$file" +done +shopt -u globstar ``` ### Count files or directories in directory. @@ -457,6 +628,8 @@ _() { This works by passing the output of the glob as function arguments. We then count the arguments and print the number. +**Example Function:** + ```sh count() { # Usage: count /path/to/dir/* @@ -465,19 +638,33 @@ count() { } ``` +**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`. -```sh -_() { - # Shortest. - :> file +```shell +# Shortest. +:> file - # Longer alternatives: - echo -n > file - printf '' > file -} +# Longer alternatives: +echo -n > file +printf '' > file ``` ## File Paths @@ -486,6 +673,8 @@ _() { Alternative to the `dirname` command. +**Example Function:** + ```sh dirname() { # Usage: dirname "path" @@ -493,10 +682,22 @@ dirname() { } ``` +**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" @@ -505,43 +706,52 @@ basename() { } ``` +**Example Usage:** + +```shell +$ basename ~/Pictures/Wallpapers/1.jpg +1.jpg + +$ basename ~/Pictures/Downloads/ +Downloads +``` + + ## Arithmetic ### Simpler syntax to set variables. -```sh -_() { - # Simple math - ((var=1+2)) +```shell +# Simple math +((var=1+2)) - # Decrement/Increment variable - ((var++)) - ((var--)) - ((var+=1)) - ((var-=1)) +# Decrement/Increment variable +((var++)) +((var--)) +((var+=1)) +((var-=1)) - # Using variables - ((var=var2*arr[2])) -} +# Using variables +((var=var2*arr[2])) ``` ### Ternary tests. -```sh -_() { - # Set the value of var to var2 if var2 is greater than var. - # var: variable to set. - # var2>var: Condition to test. - # ?var2: If the test succeeds. - # :var: If the test fails. - ((var=var2>var?var2:var)) -} +```shell +# Set the value of var to var2 if var2 is greater than var. +# var: variable to set. +# var2>var: Condition to test. +# ?var2: If the test succeeds. +# :var: If the test fails. +((var=var2>var?var2:var)) ``` ## Colors ### Convert a hex color to RGB. +**Example Function:** + ```sh hex_to_rgb() { # Usage: hex_to_rgb "#FFFFFF" @@ -553,8 +763,18 @@ hex_to_rgb() { } ``` +**Example Usage:** + +```shell +$ hex_to_rgb "#FFFFFF" +255 255 255 +``` + + ### Convert an RGB color to hex. +**Example Function:** + ```sh rgb_to_hex() { # Usage: rgb_to_hex "r" "g" "b" @@ -562,6 +782,13 @@ rgb_to_hex() { } ``` +**Example Usage:** + +```shell +$ rgb_to_hex "255" "255" "255" +#FFFFFF +``` + ## Information about the terminal ### Get the terminal size in lines and columns (*from a script*). @@ -569,6 +796,8 @@ rgb_to_hex() { This is handy when writing scripts in pure bash and `stty`/`tput` can’t be called. +**Example Function:** + ```sh get_term_size() { # Usage: get_term_size @@ -580,10 +809,20 @@ get_term_size() { } ``` +**Example Usage:** + +```shell +# Output: LINES COLUMNS +$ get_term_size +15 55 +``` + ### Get the terminal size in pixels. **NOTE**: This does not work in some terminal emulators. +**Example Function:** + ```sh get_window_size() { # Usage: get_window_size @@ -593,10 +832,25 @@ get_window_size() { } ``` +**Example Usage:** + +```shell +# Output: WIDTHxHEIGHT +$ get_window_size +1200x800 + +# Output (fail): +$ get_window_size +x +``` + + ### Get the current cursor position. This is useful when creating a TUI in pure bash. +**Example Function:** + ```sh get_cursor_pos() { # Usage: get_cursor_pos @@ -605,80 +859,78 @@ get_cursor_pos() { } ``` +**Example Usage:** + +```shell +# Output: X Y +$ get_cursor_pos +1 8 +``` + ## Code Golf ### Shorter `for` loop syntax. -```sh -_() { - # Tiny C Style. - for((;i++<10;)){ echo "$i";} +```shell +# Tiny C Style. +for((;i++<10;)){ echo "$i";} - # Undocumented method. - # Note: This is commented to make shellcheck play nice. - # for i in {1..10};{ echo "$i";} +# Undocumented method. +for i in {1..10};{ echo "$i";} - # Expansion. - for i in {1..10}; do echo "$i"; done +# Expansion. +for i in {1..10}; do echo "$i"; done - # C Style. - for((i=0;i<=10;i++)); do echo "$i"; done -} +# C Style. +for((i=0;i<=10;i++)); do echo "$i"; done ``` ### Shorter infinite loops. -```sh -_() { - # Normal method - while :; do echo hi; done +```shell +# Normal method +while :; do echo hi; done - # Shorter - for((;;)){ echo hi;} -} +# Shorter +for((;;)){ echo hi;} ``` ### Shorter function declaration. -```sh -_() { - # Normal method - f(){ echo hi;} +```shell +# Normal method +f(){ echo hi;} - # Using a subshell - f()(echo hi) +# Using a subshell +f()(echo hi) - # Using arithmetic - # You can use this to assign integer values. - # Example: f a=1 - # f a++ - f()(($1)) +# Using arithmetic +# You can use this to assign integer values. +# Example: f a=1 +# f a++ +f()(($1)) - # Using tests, loops etc. - # NOTE: You can also use ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’. - # NOTE: These are commented to make shellcheck play nice. - # f()if true; then echo "$1"; fi - # f()for i in "$@"; do echo "$i"; done -} +# Using tests, loops etc. +# NOTE: You can also use ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’. +f()if true; then echo "$1"; fi +f()for i in "$@"; do echo "$i"; done ``` ### Shorter `if` syntax. -```sh -_() { - # One line - [[ "$var" == hello ]] && echo hi || echo bye - [[ "$var" == hello ]] && { echo hi; echo there; } || echo bye +```shell +# One line +[[ "$var" == hello ]] && echo hi || echo bye +[[ "$var" == hello ]] && { echo hi; echo there; } || echo bye - # Multi line (no else, single statement) - [[ "$var" == hello ]] && \ - echo hi +# Multi line (no else, single statement) +[[ "$var" == hello ]] && \ + echo hi - # Multi line (no else) - [[ "$var" == hello ]] && { - echo hi - # ... - } +# Multi line (no else) +[[ "$var" == hello ]] && { + echo hi + # ... } ``` @@ -689,31 +941,29 @@ statement. The `$_` variable stores the last argument of the last successful command. `:` always succeeds so we can abuse it to store the variable value. -```sh -_() { - # Example snippet from Neofetch. - case "$(uname)" in - "Linux" | "GNU"*) - : "Linux" - ;; +```shell +# Example snippet from Neofetch. +case "$(uname)" in + "Linux" | "GNU"*) + : "Linux" + ;; - *"BSD" | "DragonFly" | "Bitrig") - : "BSD" - ;; + *"BSD" | "DragonFly" | "Bitrig") + : "BSD" + ;; - "CYGWIN"* | "MSYS"* | "MINGW"*) - : "Windows" - ;; + "CYGWIN"* | "MSYS"* | "MINGW"*) + : "Windows" + ;; - *) - printf '%s\n' "Unknown OS detected, aborting..." >&2 - exit 1 - ;; - esac + *) + printf '%s\n' "Unknown OS detected, aborting..." >&2 + exit 1 + ;; +esac - # Finally, set the variable. - os="$_" -} +# Finally, set the variable. +os="$_" ``` ## Internal Variables @@ -726,60 +976,60 @@ http://tldp.org/LDP/abs/html/internalvariables.html ### Get the location to the `bash` binary. -```sh -: "$BASH" +```shell +"$BASH" ``` ### Get the version of the current running `bash` process. -```sh +```shell # As a string. -: "$BASH_VERSION" +"$BASH_VERSION" # As an array. -: "${BASH_VERSINFO[@]}" +"${BASH_VERSINFO[@]}" ``` ### Open the user's preferred text editor. -```sh -: "$EDITOR" "$file" +```shell +"$EDITOR" "$file" # NOTE: This variable may be empty, set a fallback value. -: "${EDITOR:-vi}" "$file" +"${EDITOR:-vi}" "$file" ``` ### Get the name of the current function. -```sh +```shell # Current function. -: "${FUNCNAME[0]}" +"${FUNCNAME[0]}" # Parent function. -: "${FUNCNAME[1]}" +"${FUNCNAME[1]}" # So on and so forth. -: "${FUNCNAME[2]}" -: "${FUNCNAME[3]}" +"${FUNCNAME[2]}" +"${FUNCNAME[3]}" # All functions including parents. -: "${FUNCNAME[@]}" +"${FUNCNAME[@]}" ``` ### Get the host-name of the system. -```sh -: "$HOSTNAME" +```shell +"$HOSTNAME" # NOTE: This variable may be empty. # Optionally set a fallback to the hostname command. -: "${HOSTNAME:-$(hostname)}" +"${HOSTNAME:-$(hostname)}" ``` ### Get the architecture of the Operating System. -```sh -: "$HOSTTYPE" +```shell +"$HOSTTYPE" ``` ### Get the name of the Operating System / Kernel. @@ -787,22 +1037,22 @@ http://tldp.org/LDP/abs/html/internalvariables.html This can be used to add conditional support for different Operating Systems without needing to call `uname`. -```sh -: "$OSTYPE" +```shell +"$OSTYPE" ``` ### Get the current working directory. This is an alternative to the `pwd` built-in. -```sh -: "$PWD" +```shell +"$PWD" ``` ### Get the number of seconds the script has been running. -```sh -: "$SECONDS" +```shell +"$SECONDS" ``` ## Other @@ -814,6 +1064,8 @@ in place of the `date` command in a lot of cases. **NOTE:** Requires `bash` 4+ +**Example Function:** + ```sh date() { # Usage: date "format" @@ -835,22 +1087,22 @@ date() { ### Bypass shell aliases. -```sh +```shell # alias -: ls +ls # command # shellcheck disable=SC1001 -: \ls +\ls ``` ### Bypass shell functions. -```sh +```shell # function -: ls +ls # command -: command ls +command ls ``` diff --git a/test.sh b/test.sh index 5d114b0..374f9da 100755 --- a/test.sh +++ b/test.sh @@ -138,7 +138,7 @@ assert_equals() { } main() { - source <(awk '/```sh/{f=1;next}/```/{f=0}f' README.md) 2>/dev/null + source <(awk '/```sh$/{f=1;next}/```/{f=0}f' README.md) 2>/dev/null head="-> Running tests on the Pure Bash Bible.." printf '\n%s\n%s\n' "$head" "${head//?/-}"