NEW: pure sh bible (
A collection of pure bash alternatives to externalprocesses.
The goal of this book is to document commonly-known and lesser-known methods of doing various tasks using only built-in bash
features. Using the snippets from this bible can help remove unneeded dependencies from scripts and in most cases make them faster. I came across these tips and discovered a few while developing neofetch, pxltrm and other smaller projects.
The snippets below are linted using shellcheck
and tests have been written where applicable. Want to contribute? Read the CONTRIBUTING.md. It outlines how the unit tests work and what is required when adding snippets to the bible.
See something incorrectly described, buggy or outright wrong? Open an issue or send a pull request. If the bible is missing something, open an issue and a solution will be found.
This book is also available to purchase on leanpub. https://leanpub.com/bash
bash
binarybash
processread
as an alternative to the sleep
commandstrftime
A collection of pure bash
alternatives to external processes and programs. The bash
scripting language is more powerful than people realise and most tasks can be accomplished without depending on external programs.
Calling an external process in bash
is expensive and excessive use will cause a noticeable slowdown. Scripts and programs written using built-in methods (where applicable) will be faster, require fewer dependencies and afford a better understanding of the language itself.
The contents of this book provide a reference for solving problems encountered when writing programs and scripts in bash
. Examples are in function formats showcasing how to incorporate these solutions into code.
This is an alternative to sed
, awk
, perl
and other tools. Thefunction below works by finding all leading and trailing white-space andremoving it from the start and end of the string. The :
built-in is used in place of a temporary variable.
Example Function:
trim_string() {
# Usage: trim_string " example string "
: "${1#"${1%%[![:space:]]*}"}"
: "${_%"${_##*[![:space:]]}"}"
printf '%s\n' "$_"
}
Example Usage:
$ trim_string " Hello, World "
Hello, World
$ name=" John Black "
$ trim_string "$name"
John Black
This is an alternative to sed
, awk
, perl
and other tools. Thefunction below works by abusing word splitting to create a new stringwithout leading/trailing white-space and with truncated spaces.
Example Function:
# shellcheck disable=SC2086,SC2048
trim_all() {
# Usage: trim_all " example string "
set -f
set -- $*
printf '%s\n' "$*"
set +f
}
Example Usage:
$ trim_all " Hello, World "
Hello, World
$ name=" John Black is my name. "
$ trim_all "$name"
John Black is my name.
The result of bash
's regex matching can be used to replace sed
for alarge number of use-cases.
CAVEAT: This is one of the few platform dependent bash
features.bash
will use whatever regex engine is installed on the user's system.Stick to POSIX regex features if aiming for compatibility.
CAVEAT: This example only prints the first matching group. When usingmultiple capture groups some modification is needed.
Example Function:
regex() {
# Usage: regex "string" "regex"
[[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}"
}
Example Usage:
$ # Trim leading white-space.
$ regex ' hello' '^\s*(.*)'
hello
$ # Validate a hex color.
$ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
#FFFFFF
$ # Validate a hex color (invalid).
$ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
# no output (invalid)
Example Usage in script:
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.
CAVEAT: Requires bash
4+
This is an alternative to cut
, awk
and other tools.
Example Function:
split() {
# Usage: split "string" "delimiter"
IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}"
printf '%s\n' "${arr[@]}"
}
Example Usage:
$ split "apples,oranges,pears,grapes" ","
apples
oranges
pears
grapes
$ split "1, 2, 3, 4, 5" ", "
1
2
3
4
5
# Multi char delimiters work too!
$ split "hello---world---my---name---is---john" "---"
hello
world
my
name
is
john
CAVEAT: Requires bash
4+
Example Function:
lower() {
# Usage: lower "string"
printf '%s\n' "${1,,}"
}
Example Usage:
$ lower "HELLO"
hello
$ lower "HeLlO"
hello
$ lower "hello"
hello
CAVEAT: Requires bash
4+
Example Function:
upper() {
# Usage: upper "string"
printf '%s\n' "${1^^}"
}
Example Usage:
$ upper "hello"
HELLO
$ upper "HeLlO"
HELLO
$ upper "HELLO"
HELLO
CAVEAT: Requires bash
4+
Example Function:
reverse_case() {
# Usage: reverse_case "string"
printf '%s\n' "${1~~}"
}
Example Usage:
$ reverse_case "hello"
HELLO
$ reverse_case "HeLlO"
hElLo
$ reverse_case "HELLO"
hello
Example Function:
trim_quotes() {
# Usage: trim_quotes "string"
: "${1//\'}"
printf '%s\n' "${_//\"}"
}
Example Usage:
$ var="'Hello', \"World\""
$ trim_quotes "$var"
Hello, World
Example Function:
strip_all() {
# Usage: strip_all "string" "pattern"
printf '%s\n' "${1//$2}"
}
Example Usage:
$ strip_all "The Quick Brown Fox" "[aeiou]"
Th Qck Brwn Fx
$ strip_all "The Quick Brown Fox" "[[:space:]]"
TheQuickBrownFox
$ strip_all "The Quick Brown Fox" "Quick "
The Brown Fox
Example Function:
strip() {
# Usage: strip "string" "pattern"
printf '%s\n' "${1/$2}"
}
Example Usage:
$ strip "The Quick Brown Fox" "[aeiou]"
Th Quick Brown Fox
$ strip "The Quick Brown Fox" "[[:space:]]"
TheQuick Brown Fox
Example Function:
lstrip() {
# Usage: lstrip "string" "pattern"
printf '%s\n' "${1##$2}"
}
Example Usage:
$ lstrip "The Quick Brown Fox" "The "
Quick Brown Fox
Example Function:
rstrip() {
# Usage: rstrip "string" "pattern"
printf '%s\n' "${1%%$2}"
}
Example Usage:
$ rstrip "The Quick Brown Fox" " Fox"
The Quick Brown
Example Function:
urlencode() {
# Usage: urlencode "string"
local LC_ALL=C
for (( i = 0; i < ${#1}; i++ )); do
: "${1:i:1}"
case "$_" in
[a-zA-Z0-9.~_-])
printf '%s' "$_"
;;
*)
printf '%%%02X' "'$_"
;;
esac
done
printf '\n'
}
Example Usage:
$ urlencode "https://github.com/dylanaraps/pure-bash-bible"
https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible
Example Function:
urldecode() {
# Usage: urldecode "string"
: "${1//+/ }"
printf '%b\n' "${_//%/\\x}"
}
Example Usage:
$ urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible"
https://github.com/dylanaraps/pure-bash-bible
Using a test:
if [[ $var == *sub_string* ]]; then
printf '%s\n' "sub_string is in var."
fi
# Inverse (substring not in string).
if [[ $var != *sub_string* ]]; then
printf '%s\n' "sub_string is not in var."
fi
# This works for arrays too!
if [[ ${arr[*]} == *sub_string* ]]; then
printf '%s\n' "sub_string is in array."
fi
Using a case statement:
case "$var" in
*sub_string*)
# Do stuff
;;
*sub_string2*)
# Do more stuff
;;
*)
# Else
;;
esac
if [[ $var == sub_string* ]]; then
printf '%s\n' "var starts with sub_string."
fi
# Inverse (var does not start with sub_string).
if [[ $var != sub_string* ]]; then
printf '%s\n' "var does not start with sub_string."
fi
if [[ $var == *sub_string ]]; then
printf '%s\n' "var ends with sub_string."
fi
# Inverse (var does not end with sub_string).
if [[ $var != *sub_string ]]; then
printf '%s\n' "var does not end with sub_string."
fi
Enabling extdebug
allows access to the BASH_ARGV
array which storesthe current function’s arguments in reverse.
CAVEAT: Requires shopt -s compat44
in bash
5.0+.
Example Function:
reverse_array() {
# Usage: reverse_array "array"
shopt -s extdebug
f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@"
shopt -u extdebug
}
Example Usage:
$ reverse_array 1 2 3 4 5
5
4
3
2
1
$ arr=(red blue green)
$ reverse_array "${arr[@]}"
green
blue
red
Create a temporary associative array. When setting associative arrayvalues and a duplicate assignment occurs, bash overwrites the key. Thisallows us to effectively remove array duplicates.
CAVEAT: Requires bash
4+
CAVEAT: List order may not stay the same.
Example Function:
remove_array_dups() {
# Usage: remove_array_dups "array"
declare -A tmp_array
for i in "$@"; do
[[ $i ]] && IFS=" " tmp_array["${i:- }"]=1
done
printf '%s\n' "${!tmp_array[@]}"
}
Example Usage:
$ 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
Example Function:
random_array_element() {
# Usage: random_array_element "array"
local arr=("$@")
printf '%s\n' "${arr[RANDOM % $#]}"
}
Example Usage:
$ array=(red green blue yellow brown)
$ random_array_element "${array[@]}"
yellow
# Multiple arguments can also be passed.
$ random_array_element 1 2 3 4 5 6 7
3
Each time the printf
is called, the next array element is printed. Whenthe print hits the last array element it starts from the first elementagain.
arr=(a b c d)
cycle() {
printf '%s ' "${arr[${i:=0}]}"
((i=i>=${#arr[@]}-1?0:++i))
}
This works the same as above, this is just a different use case.
arr=(true false)
cycle() {
printf '%s ' "${arr[${i:=0}]}"
((i=i>=${#arr[@]}-1?0:++i))
}
Alternative to seq
.
# Loop from 0-100 (no variable support).
for i in {0..100}; do
printf '%s\n' "$i"
done
Alternative to seq
.
# Loop from 0-VAR.
VAR=50
for ((i=0;i<=VAR;i++)); do
printf '%s\n' "$i"
done
arr=(apples oranges tomatoes)
# Just elements.
for element in "${arr[@]}"; do
printf '%s\n' "$element"
done
arr=(apples oranges tomatoes)
# Elements and index.
for i in "${!arr[@]}"; do
printf '%s\n' "${arr[i]}"
done
# Alternative method.
for ((i=0;i<${#arr[@]};i++)); do
printf '%s\n' "${arr[i]}"
done
while read -r line; do
printf '%s\n' "$line"
done < "file"
Don’t use ls
.
# 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
# Iterate over directories.
for dir in ~/Downloads/*/; do
printf '%s\n' "$dir"
done
# Brace Expansion.
for file in /path/to/parentdir/{file1,file2,subdir/file3}; do
printf '%s\n' "$file"
done
# Iterate recursively.
shopt -s globstar
for file in ~/Pictures/**/*; do
printf '%s\n' "$file"
done
shopt -u globstar
CAVEAT: bash
does not handle binary data properly in versions < 4.4
.
Alternative to the cat
command.
file_data="$(<"file")"
Alternative to the cat
command.
# Bash <4 (discarding empty lines).
IFS=$'\n' read -d "" -ra file_data < "file"
# Bash <4 (preserving empty lines).
while read -r line; do
file_data+=("$line")
done < "file"
# Bash 4+
mapfile -t file_data < "file"
Alternative to the head
command.
CAVEAT: Requires bash
4+
Example Function:
head() {
# Usage: head "n" "file"
mapfile -tn "$1" line < "$2"
printf '%s\n' "${line[@]}"
}
Example Usage:
$ head 2 ~/.bashrc
# Prompt
PS1='➜ '
$ head 1 ~/.bashrc
# Prompt
Alternative to the tail
command.
CAVEAT: Requires bash
4+
Example Function:
tail() {
# Usage: tail "n" "file"
mapfile -tn 0 line < "$2"
printf '%s\n' "${line[@]: -$1}"
}
Example Usage:
$ tail 2 ~/.bashrc
# Enable tmux.
# [[ -z "$TMUX" ]] && exec tmux
$ tail 1 ~/.bashrc
# [[ -z "$TMUX" ]] && exec tmux
Alternative to wc -l
.
Example Function (bash 4):
lines() {
# Usage: lines "file"
mapfile -tn 0 lines < "$1"
printf '%s\n' "${#lines[@]}"
}
Example Function (bash 3):
This method uses less memory than the mapfile
method and works in bash
3 but it is slower for bigger files.
lines_loop() {
# Usage: lines_loop "file"
count=0
while IFS= read -r _; do
((count++))
done < "$1"
printf '%s\n' "$count"
}
Example Usage:
$ lines ~/.bashrc
48
$ lines_loop ~/.bashrc
48
This works by passing the output of the glob to the function and then counting the number of arguments.
Example Function:
count() {
# Usage: count /path/to/dir/*
# count /path/to/dir/*/
printf '%s\n' "$#"
}
Example Usage:
# 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
Alternative to touch
.
# Shortest.
>file
# Longer alternatives:
:>file
echo -n >file
printf '' >file
Example Function:
extract() {
# Usage: extract file "opening marker" "closing marker"
while IFS=$'\n' read -r line; do
[[ $extract && $line != "$3" ]] &&
printf '%s\n' "$line"
[[ $line == "$2" ]] && extract=1
[[ $line == "$3" ]] && extract=
done < "$1"
}
Example Usage:
# Extract code blocks from MarkDown file.
$ extract ~/projects/pure-bash/README.md '```sh' '```'
# Output here...
Alternative to the dirname
command.
Example Function:
dirname() {
# Usage: dirname "path"
local tmp=${1:-.}
[[ $tmp != *[!/]* ]] && {
printf '/\n'
return
}
tmp=${tmp%%"${tmp##*[!/]}"}
[[ $tmp != */* ]] && {
printf '.\n'
return
}
tmp=${tmp%/*}
tmp=${tmp%%"${tmp##*[!/]}"}
printf '%s\n' "${tmp:-/}"
}
Example Usage:
$ dirname ~/Pictures/Wallpapers/1.jpg
/home/black/Pictures/Wallpapers
$ dirname ~/Pictures/Downloads/
/home/black/Pictures
Alternative to the basename
command.
Example Function:
basename() {
# Usage: basename "path" ["suffix"]
local tmp
tmp=${1%"${1##*[!/]}"}
tmp=${tmp##*/}
tmp=${tmp%"${2/"$tmp"}"}
printf '%s\n' "${tmp:-/}"
}
Example Usage:
$ basename ~/Pictures/Wallpapers/1.jpg
1.jpg
$ basename ~/Pictures/Wallpapers/1.jpg .jpg
1
$ basename ~/Pictures/Downloads/
Downloads
$ hello_world="value"
# Create the variable name.
$ var="world"
$ ref="hello_$var"
# Print the value of the variable name stored in 'hello_$var'.
$ printf '%s\n' "${!ref}"
value
Alternatively, on bash
4.3+:
$ hello_world="value"
$ var="world"
# Declare a nameref.
$ declare -n ref=hello_$var
$ printf '%s\n' "$ref"
value
$ var="world"
$ declare "hello_$var=value"
$ printf '%s\n' "$hello_world"
value
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.
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 |
NOTE: Prepend 2 to any code below to turn it's effect off(examples: 21=bold text off, 22=faint text off, 23=italic text off).
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 |
Blinking text. |
\e[7m |
Highlighted text. |
\e[8m |
Hidden text. |
\e[9m |
Strike-through text. |
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. |
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 | What does it do? |
---|---|
${!VAR} |
Access a variable based on the value of VAR . |
${!VAR*} |
Expand to IFS separated list of variable names starting with VAR . |
${!VAR@} |
Expand to IFS separated list of variable names starting with VAR . If double-quoted, each variable name expands to a separate word. |
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. |
${VAR/PATTERN/REPLACE} |
Replace first match with string. |
${VAR//PATTERN/REPLACE} |
Replace all matches with string. |
${VAR/PATTERN} |
Remove first match. |
${VAR//PATTERN} |
Remove all matches. |
Parameter | What does it do? |
---|---|
${#VAR} |
Length of var in characters. |
${#ARR[@]} |
Length of array in elements. |
Parameter | What does it do? |
---|---|
${VAR:OFFSET} |
Remove first N chars from variable. |
${VAR:OFFSET:LENGTH} |
Get substring from N character to N character. ( ${VAR:10:10} : Get sub-string from char 10 to char 20 ) |
${VAR:: OFFSET} |
Get first N chars from variable. |
${VAR:: -OFFSET} |
Remove last N chars from variable. |
${VAR: -OFFSET} |
Get last N chars from variable. |
${VAR:OFFSET:-OFFSET} |
Cut first N chars and last N chars. |
Parameter | What does it do? | CAVEAT |
---|---|---|
${VAR^} |
Uppercase first character. | bash 4+ |
${VAR^^} |
Uppercase all characters. | bash 4+ |
${VAR,} |
Lowercase first character. | bash 4+ |
${VAR,,} |
Lowercase all characters. | bash 4+ |
${VAR~} |
Reverse case of first character. | bash 4+ |
${VAR~~} |
Reverse case of all characters. | bash 4+ |
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. |
# Syntax: {<START>..<END>}
# Print numbers 1-100.
echo {1..100}
# Print range of floats.
echo 1.{1..9}
# Print chars a-z.
echo {a..z}
echo {A..Z}
# Nesting.
echo {A..Z}{0..9}
# Print zero-padded numbers.
# CAVEAT: bash 4+
echo {01..100}
# Change increment amount.
# Syntax: {<START>..<END>..<INCREMENT>}
# CAVEAT: bash 4+
echo {1..10..2} # Increment by 2.
echo {apples,oranges,pears,grapes}
# Example Usage:
# Remove dirs Movies, Music and ISOS from ~/Downloads/.
rm -rf ~/Downloads/{Movies,Music,ISOS}
Expression | Value | What does it do? |
---|---|---|
-a |
file |
If file exists. |
-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. |
-k |
file |
If file exists and its sticky-bit is set |
-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. |
-G |
file |
If file exists and is owned by the effective group ID. |
-L |
file |
If file exists and is a symbolic link. |
-N |
file |
If file exists and has been modified since last read. |
-O |
file |
If file exists and is owned by the effective user ID. |
-S |
file |
If file exists and is a socket. |
Expression | What does it do? |
---|---|
file -ef file2 |
If both files refer to the same inode and device numbers. |
file -nt file2 |
If file is newer than file2 (uses modification time) or file exists and file2 does not. |
file -ot file2 |
If file is older than file2 (uses modification time) or file2 exists and file does not. |
Expression | Value | What does it do? |
---|---|---|
-o |
opt |
If shell option is enabled. |
-v |
var |
If variable has a value assigned. |
-R |
var |
If variable is a name reference. |
-z |
var |
If the length of string is zero. |
-n |
var |
If the length of string is non-zero. |
Expression | What does it do? |
---|---|
var = var2 |
Equal to. |
var == var2 |
Equal to (synonym for = ). |
var != var2 |
Not equal to. |
var < var2 |
Less than (in ASCII alphabetical order.) |
var > var2 |
Greater than (in ASCII alphabetical order.) |
Operators | What does it do? |
---|---|
= |
Initialize or change the value of a variable. |
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.) |
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 |
Operators | What does it do? |
---|---|
! |
NOT |
&& |
AND |
|| |
OR |
Operators | What does it do? | Example |
---|---|---|
, |
Comma Separator | ((a=1,b=2,c=3)) |
# Simple math
((var=1+2))
# Decrement/Increment variable
((var++))
((var--))
((var+=1))
((var-=1))
# Using variables
((var=var2*arr[2]))
# 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))
Traps allow a script to execute code on various signals. In 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.
NOTE: For a full list of signals, see trap -l
.
# Clear screen on script exit.
trap 'printf \\e[2J\\e[H\\e[m' EXIT
trap '' INT
# Call a function on window resize.
trap 'code_here' SIGWINCH
trap 'code_here' DEBUG
trap 'code_here' RETURN
If unicode is not required, it can be disabled for a performance increase. Results may vary however there have been noticeable improvements in neofetch and other programs.
# Disable unicode.
LC_ALL=C
LANG=C
Use #!/usr/bin/env bash
instead of #!/bin/bash
.
PATH
to find the bash
binary./bin/
which can cause issues.NOTE: There are times when one may have a good reason for using #!/bin/bash
or another direct path to the binary.
# Right:
#!/usr/bin/env bash
# Less right:
#!/bin/bash
Use $()
instead of ` `
.
# Right.
var="$(command)"
# Wrong.
var=`command`
# $() can easily be nested whereas `` cannot.
var="$(command "$(command)")"
Do not use the function
keyword, it reduces compatibility with older versions of bash
.
# Right.
do_something() {
# ...
}
# Wrong.
function do_something() {
# ...
}
bash
binary"$BASH"
bash
process# As a string.
"$BASH_VERSION"
# As an array.
"${BASH_VERSINFO[@]}"
"$EDITOR" "$file"
# NOTE: This variable may be empty, set a fallback value.
"${EDITOR:-vi}" "$file"
# Current function.
"${FUNCNAME[0]}"
# Parent function.
"${FUNCNAME[1]}"
# So on and so forth.
"${FUNCNAME[2]}"
"${FUNCNAME[3]}"
# All functions including parents.
"${FUNCNAME[@]}"
"$HOSTNAME"
# NOTE: This variable may be empty.
# Optionally set a fallback to the hostname command.
"${HOSTNAME:-$(hostname)}"
"$HOSTTYPE"
This can be used to add conditional support for different OperatingSystems without needing to call uname
.
"$OSTYPE"
This is an alternative to the pwd
built-in.
"$PWD"
"$SECONDS"
Each time $RANDOM
is used, a different integer between 0
and 32767
is returned. This variable should not be used for anything related to security (this includes encryption keys etc).
"$RANDOM"
This is handy when writing scripts in pure bash and stty
/tput
can’t becalled.
Example Function:
get_term_size() {
# Usage: get_term_size
# (:;:) is a micro sleep to ensure the variables are
# exported immediately.
shopt -s checkwinsize; (:;:)
printf '%s\n' "$LINES $COLUMNS"
}
Example Usage:
# Output: LINES COLUMNS
$ get_term_size
15 55
CAVEAT: This does not work in some terminal emulators.
Example Function:
get_window_size() {
# Usage: get_window_size
printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}"
IFS=';t' read -d t -t 0.05 -sra term_size
printf '%s\n' "${term_size[1]}x${term_size[2]}"
}
Example Usage:
# Output: WIDTHxHEIGHT
$ get_window_size
1200x800
# Output (fail):
$ get_window_size
x
This is useful when creating a TUI in pure bash.
Example Function:
get_cursor_pos() {
# Usage: get_cursor_pos
IFS='[;' read -p $'\e[6n' -d R -rs _ y x _
printf '%s\n' "$x $y"
}
Example Usage:
# Output: X Y
$ get_cursor_pos
1 8
Example Function:
hex_to_rgb() {
# Usage: hex_to_rgb "#FFFFFF"
# hex_to_rgb "000000"
: "${1/\#}"
((r=16#${_:0:2},g=16#${_:2:2},b=16#${_:4:2}))
printf '%s\n' "$r $g $b"
}
Example Usage:
$ hex_to_rgb "#FFFFFF"
255 255 255
Example Function:
rgb_to_hex() {
# Usage: rgb_to_hex "r" "g" "b"
printf '#%02x%02x%02x\n' "$1" "$2" "$3"
}
Example Usage:
$ rgb_to_hex "255" "255" "255"
#FFFFFF
for
loop syntax# Tiny C Style.
for((;i++<10;)){ echo "$i";}
# Undocumented method.
for i in {1..10};{ echo "$i";}
# Expansion.
for i in {1..10}; do echo "$i"; done
# C Style.
for((i=0;i<=10;i++)); do echo "$i"; done
# Normal method
while :; do echo hi; done
# Shorter
for((;;)){ echo hi;}
# Normal method
f(){ echo hi;}
# Using a subshell
f()(echo hi)
# Using arithmetic
# This can be used to assign integer values.
# Example: f a=1
# f a++
f()(($1))
# Using tests, loops etc.
# NOTE: ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’ can also be used.
f()if true; then echo "$1"; fi
f()for i in "$@"; do echo "$i"; done
if
syntax# One line
# Note: The 3rd statement may run when the 1st is true
[[ $var == hello ]] && echo hi || echo bye
[[ $var == hello ]] && { echo hi; echo there; } || echo bye
# Multi line (no else, single statement)
# Note: The exit status may not be the same as with an if statement
[[ $var == hello ]] &&
echo hi
# Multi line (no else)
[[ $var == hello ]] && {
echo hi
# ...
}
case
statement to set variableThe :
built-in can be used to avoid repeating variable=
in a case statement. The $_
variable stores the last argument of the last command. :
always succeeds so it can be used to store the variable value.
# Modified snippet from Neofetch.
case "$OSTYPE" in
"darwin"*)
: "MacOS"
;;
"linux"*)
: "Linux"
;;
*"bsd"* | "dragonfly" | "bitrig")
: "BSD"
;;
"cygwin" | "msys" | "win32")
: "Windows"
;;
*)
printf '%s\n' "Unknown OS detected, aborting..." >&2
exit 1
;;
esac
# Finally, set the variable.
os="$_"
read
as an alternative to the sleep
commandSurprisingly, sleep
is an external command and not a bash
built-in.
CAVEAT: Requires bash
4+
Example Function:
read_sleep() {
# Usage: read_sleep 1
# read_sleep 0.2
read -rt "$1" <> <(:) || :
}
Example Usage:
read_sleep 1
read_sleep 0.1
read_sleep 30
For performance-critical situations, where it is not economic to open and close an excessive number of file descriptors, the allocation of a file descriptor may be done only once for all invocations of read
:
(See the generic original implementation at https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever)
exec {sleep_fd}<> <(:)
while some_quick_test; do
# equivalent of sleep 0.001
read -t 0.001 -u $sleep_fd
done
# There are 3 ways to do this and either one can be used.
type -p executable_name &>/dev/null
hash executable_name &>/dev/null
command -v executable_name &>/dev/null
# As a test.
if type -p executable_name &>/dev/null; then
# Program is in PATH.
fi
# Inverse.
if ! type -p executable_name &>/dev/null; then
# Program is not in PATH.
fi
# Example (Exit early if program is not installed).
if ! type -p convert &>/dev/null; then
printf '%s\n' "error: convert is not installed, exiting..."
exit 1
fi
strftime
Bash’s printf
has a built-in method of getting the date which can be used in place of the date
command.
CAVEAT: Requires bash
4+
Example Function:
date() {
# Usage: date "format"
# See: 'man strftime' for format.
printf "%($1)T\\n" "-1"
}
Example Usage:
# Using above function.
$ date "%a %d %b - %l:%M %p"
Fri 15 Jun - 10:00 AM
# Using printf directly.
$ printf '%(%a %d %b - %l:%M %p)T\n' "-1"
Fri 15 Jun - 10:00 AM
# Assigning a variable using printf.
$ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1'
$ printf '%s\n' "$date"
Fri 15 Jun - 10:00 AM
CAVEAT: Requires bash
4.4+
$ : \\u
# Expand the parameter as if it were a prompt string.
$ printf '%s\n' "${_@P}"
black
CAVEAT: The generated value is not cryptographically secure.
Example Function:
uuid() {
# Usage: uuid
C="89ab"
for ((N=0;N<16;++N)); do
B="$((RANDOM%256))"
case "$N" in
6) printf '4%x' "$((B%16))" ;;
8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;;
3|5|7|9)
printf '%02x-' "$B"
;;
*)
printf '%02x' "$B"
;;
esac
done
printf '\n'
}
Example Usage:
$ uuid
d5b6c731-1310-4c24-9fe3-55d556d44374
This is a simple way of drawing progress bars without needing a for loopin the function itself.
Example Function:
bar() {
# Usage: bar 1 10
# ^----- Elapsed Percentage (0-100).
# ^-- Total length in chars.
((elapsed=$1*$2/100))
# Create the bar with spaces.
printf -v prog "%${elapsed}s"
printf -v total "%$(($2-elapsed))s"
printf '%s\r' "[${prog// /-}${total}]"
}
Example Usage:
for ((i=0;i<=100;i++)); do
# Pure bash micro sleeps (for the example).
(:;:) && (:;:) && (:;:) && (:;:) && (:;:)
# Print the bar.
bar "$i" "10"
done
printf '\n'
get_functions() {
# Usage: get_functions
IFS=$'\n' read -d "" -ra functions < <(declare -F)
printf '%s\n' "${functions[@]//declare -f }"
}
# alias
ls
# command
# shellcheck disable=SC1001
\ls
# function
ls
# command
command ls
This will run the given command and keep it running, even after the terminal or SSH connection is terminated. All output is ignored.
bkr() {
(nohup "$@" &>/dev/null &)
}
bkr ./some_script.sh # some_script.sh is now running in the background
CAVEAT: Requires bash
4+
This uses local namerefs to avoid using var=$(some_func)
style command substitution for function output capture.
to_upper() {
local -n ptr=${1}
ptr=${ptr^^}
}
foo="bar"
to_upper foo
printf "%s\n" "${foo}" # BAR
Thanks for reading! If this bible helped you in any way and you'd like to give back, consider donating. Donations give me the time to make this the best resource possible. Can't donate? That's OK, star the repo and share it with your friends!
Rock on.
该文档是在学习push-bash-bible的过程中将自己的思考和额外学习的技术点记录下来,方便自己后面查找,希望文中的信息对于看到这篇文章的你也能有所帮助 去除字符串前后空格 相对于sed、awk、peal或其他工具,以下是实现去除字符串前后空格的另一种方法 例子如下: trim_string() { # Usage: trim_string " example string
前言 bash外部流程和程序的纯替代品集合。该bash脚本语言是更强大的比人们认识和大多数任务能够不依赖于外部程序来完成。 调用外部进程bash是昂贵的,过度使用将导致明显的减速。使用内置方法(如果适用)编写的脚本和程序将更快,需要更少的依赖性,并且可以更好地理解语言本身。 本书的内容为解决编写程序和脚本时遇到的问题提供了参考bash。示例是函数格式,展示了如何将这些解决方案合并到代码中。 STR
# FOREWORD A collection of pure `bash` alternatives to external processes and programs. The `bash` scripting language is more powerful than people realise and most tasks can be accomplished without d
百分号编码字符串 示例函数: urlencode() { # Usage: urlencode "string" local LC_ALL=C for (( i = 0; i < ${#1}; i++ )); do : "${1:i:1}" case "$_" in [a-zA-Z0-9.~_-])
pure bash bible bash奇巧淫技. 本书原作者将书中的内容发布到了github上,我仅仅是将其翻译为中文,并解释了其中的部分语句语法,希望可以对今后的工作有所帮助。 以下是翻译后的原文 这本书的目的是汇总只使用内置bash的特性来实现总所周知和鲜为人知的各项任务。 使用此参考书中的代码段可以帮助你从脚本中删除不需要的依赖项,并且在大多数情况下可以使它们运行的更快。 我偶然碰到了这些
填充 JSON 数据到 html 页面的模板工具。 用纯 HTML 编写模板: 清除任何内联逻辑或特殊标签 CSS 选择器用于桥接 HTML 和 Javascript 操作 彻底分离 representation 和 logic
Pure 是来自雅虎的 CSS 框架,使用 Normalize.CSS 无需任何 JavaScript 代码。框架基于响应式设计,提供多种样式的组件,包括表格、表单、按钮、表、导航等。标识使用非常简单,整个框架非常轻量级,压缩后只有 5.7k。
Pure Release 是用于 Bower 包的 Pure 目录。它是一套小的 CSS 模块,你可以用在每个 Web 工程上。
Pure-FTPd 是一个快速、高效、轻便、安全的FTP服务器,它不象其他流行的FTP SERVER软件。以安全和配置简单为设计目标,支持虚拟主机,IPV6,PAM等功能。 它的功能相当多,也很实用。如果你正在寻找一个方便可靠的服务器,那么它是你最好的选择。
fis-pure 是基于FIS的纯前端模块化解决方案pure pure是基于FIS二次封装能力封装而成的解决方案,可以直接使用,也可以参考fis-pure自行封装或组合使用插件来构建属于自己的一体化解决方案。 模块化开发 不仅仅是脚本模块化,pure带给你模版、脚本、样式的组件化方案。 自动加载模块化依赖,无需手动引入静态资源。 与Node.js风格一致的模块化开发体验,告别 define。 自动