๐ฅ๏ธ Bash
Aliases¶
alias cat='bat'
# LS
alias ls='lsd'
alias la='ls -al'
alias ll='ls -alF'
alias lt='ls --tree'
# Quick edit dotfiles
alias bash_aliases="open ~/.bash_aliases"
alias bashrc="open ~/.bashrc"
alias zshrc="open ~/.zshrc"
alias zprofile="open ~/.zprofile"
alias gitconfig="open ~/.gitconfig"
alias gitattributes="open ~/.gitattributes"
Arrays¶
-
Definitions:
Persons=('Alice' 'Bob' 'Charlie') Persons[0]="Alice" Persons[1]="Bob" Persons[2]="Charlie"
-
Getters:
echo "${Persons[0]}" # Element #0 echo "${Persons[-1]}" # Last element echo "${Persons[@]}" # All elements, space-separated echo "${#Persons[@]}" # Number of elements echo "${#Persons}" # String length of the 1st element echo "${#Persons[3]}" # String length of the Nth element echo "${Persons[@]:3:2}" # Range (from position 3, length 2) echo "${!Persons[@]}" # Keys of all elements, space-separated
-
Setters:
Persons=("${Persons[@]}" "Dave") # Push Persons+=('Dave') # Also Push Persons=( "${Persons[@]/Al*/}" ) # Remove by regex match unset Persons[2] # Remove one item Persons=("${Persons[@]}") # Duplicate Persons=("${Persons[@]}" "${Others[@]}") # Concatenate Persons=(`cat "persons.txt"`) # Read from file
-
Iterations:
for p in "${Persons[@]}"; do echo "$p" done
Dictionaries¶
-
Definitions:
declare -A sounds sounds[dog]="bark" sounds[cow]="moo" sounds[bird]="tweet" sounds[wolf]="howl"
-
Getters/Setters:
echo "${sounds[dog]}" # Dog's sound echo "${sounds[@]}" # All values echo "${!sounds[@]}" # All keys echo "${#sounds[@]}" # Number of elements unset sounds[dog] # Delete dog
-
Iterations:
keysfor key in "${!dictionary[@]}"; do echo "$key" done
for value in "${dictionary[@]}"; do
echo "$value"
done
Directory of script¶
dir=${0%/*}
Parameter expansions¶
-
Substitutions:
# Remove suffix ${foo%suffix} # Remove prefix ${foo#prefix} # Remove long suffix ${foo%%suffix} # Remove long suffix ${foo/%suffix} # Remove long prefix ${foo##prefix} # Remove long prefix ${foo/#prefix} # Replace first match ${foo/old/new} # Replace all instances ${foo//old/new} # Replace suffix ${foo/%old/new} # Replace prefix ${foo/#old/new}
-
Substrings:
${foo:offset:length} # offset from right ${foo:(-offset):length}
-
Default values:
# $foo, or val if unset (or null) ${foo:-val} # set $foo to val if unset (or null) ${foo:=val} # val if $foo is set (and not null) ${foo:+val} # print error and exit if $foo is unset (or null) ${foo:?error}
-
Misc:
# length of string or array ${#foo} # lowercase 1st char ${foo,} # lowercase all chars ${foo,,} # uppercase 1st char ${foo^} # uppercase 1st char ${foo^^}
Check if command exists¶
command -v foo >/dev/null || error 'foo is required'
if command -v foo >/dev/null; then
echo "foo is available"
else
echo "foo is required"
exit 1
fi
Command options and positional arguments¶
Quote
A double dash --
is used in most Bash built-in commands and many other commands to signify the end of command options, after which only positional arguments are accepted.
Example use: Let's say you want to grep a file for the string -v
.
Normally -v
will be considered the option to reverse the matching meaning (only show lines that do not match), but with --
you can grep for the string -v
like this:
grep -- -v file
Debugging¶
This DEBUG
trap pauses the script execution on each line, prints the command that will be executed, and prompts the user to continue.
#!/usr/bin/env bash
trap 'read -p "[$BASH_SOURCE:$LINENO] $BASH_COMMAND"' DEBUG
echo "Foo"
echo "Bar"
echo "Baz"
./script.sh
[./script.sh:4] echo "Foo"
Foo
[./script.sh:5] echo "Bar"
Bar
[./script.sh:6] echo "Baz"
Baz
Delete empty directories¶
find . -type d -empty -print -delete
Dynamic command arguments¶
args=()
[[ -n "$INPUT" ]] && args+=( '--input' "$INPUT" )
foo "${args[@]}"
Find and run commands in parallel¶
find . -type f -name "*" -print0 | xargs -0 --verbose --max-args=1 --max-procs=0 -I % echo "%"
Heredoc¶
[cmd] <<[-] delimeter [cmd]
content
delimeter
Notes
<<-
instead of<<
to ignore leading (tab-only) indentation|| true
to suppress the return code (1 by default)'EOF'
to disable command and variable expansion>/dev/null
to not echo content to stdout
read -d '' my_var << EOF || true
โโโ
$PWD
โโโ
EOF
โโโ
/home/user
โโโ
read -d '' my_var << 'EOF' || true
โโโ
$PWD
โโโ
EOF
โโโ
$PWD
โโโ
tee file << EOF >/dev/null || true
โโโ
$PWD
โโโ
EOF
tee file.txt << EOF >/dev/null || true
โโโ
$PWD
โโโ
EOF
Inline comment on multiline commands¶
foo `: # This is a comment` |
bar `: # This is another comment` |
baz `: # This is the last comment`
This method uses both the buit-in no-op command :
together with the comment character #
to support shell with interactive_comments
disabled.
jq¶
# Identity:
.
# Object Identifier-Index:
.foo, .foo.bar
# Optional Object Identifier-Index:
.foo?
# Object Index:
.[<string>]
# Array Index:
.[<number>]
# Array/String Slice:
.[<number>:<number>]
# Array/Object Value Iterator:
.[]
# Comma:
,
# Pipe:
|
# Addition:
+
# Subtraction:
-
# Multiplication, division, modulo:
*, /, %
# Length (array, object, string, number, etc.):
length
# Keys of an object as an array:
keys, keys_unsorted
# Presence of the given key:
has(key)
# Presence of input key in the given object:
in(object)
# Apply f to each of the values in the input array or object:
map(f), map_values(f)
# Convert between an object and an array of key-value pairs:
to_entries, from_entries, with_entries(f)
# Filter input with predicate:
select(f)
# Remove a key and its corresponding value from an object:
del(path_expression)
# Add elements of the input array (summed, concatenated, or merged):
add
# Sort the input array
sort, sort_by(path_expression)
# Group elements by expression
group_by(path_expression)
curl -s 'https://api.github.com/repos/github/.github/commits' \
| jq '.[] | {message: .commit.message, name: .commit.author.name}'
{
"message": "Merge pull request #340 from trishanu-init/patch-1\n\nchanged the content structure in CODE_OF_CONDUCT.md",
"name": "Zack Koppert"
}
{/*...*/}
{
"message": "Create README.md",
"name": "Ben Balter"
}
curl -s 'https://api.github.com/repos/github/.github/commits' \
| jq '[.[] | {message: .commit.message, name: .commit.author.name}]'
[
{
"message": "Merge pull request #340 from trishanu-init/patch-1\n\nchanged the content structure in CODE_OF_CONDUCT.md",
"name": "Zack Koppert"
},
{/*...*/},
{
"message": "Create README.md",
"name": "Ben Balter"
}
]
curl -s 'https://api.github.com/repos/github/.github/commits' \
| jq '[.[] | {message: .commit.message, name: .commit.author.name, parents: [.parents[].html_url]}]'
[
{
"message": "Merge pull request #340 from trishanu-init/patch-1\n\nchanged the content structure in CODE_OF_CONDUCT.md",
"name": "Zack Koppert",
"parents": [
"https://github.com/github/.github/commit/d9c05dfc6b02cafe78a6342a73e9e9096273c4fd",
"https://github.com/github/.github/commit/b41159a31d90042b9e612b3eadeb607ac880ce02"
]
},
{/*...*/},
{
"message": "Create README.md",
"name": "Ben Balter",
"parents": [
"https://github.com/github/.github/commit/d707be9dc0c8e9ebf6e198aa925f89f88486c273"
]
}
]
curl -s 'https://api.github.com/repos/github/.github/commits' \
| jq 'group_by(.commit.author.name) | map({(.[0].commit.author.name): length}) | add'
{
"Alex Webb": 1,
"Ashley Wolf": 6,
"Ben Balter": 3,
"Daniel Adams": 1,
"Diana Moore": 1,
"Dinakar": 1,
"Fayas Noushad": 1,
"Justin Hutchings": 1,
"Matthias Wenz": 1,
"Nihaal Sangha": 1,
"Paranoรฏd User": 1,
"Phil Turnbull": 3,
"Trishanu Nayak": 2,
"Zack Koppert": 7
}
Multiline comment¶
<< 'COMMENT'
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Fusce in justo faucibus, venenatis libero vitae, rutrum sem.
Donec ut aliquam urna. Nulla facilisi.
COMMENT
: '
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Fusce in justo faucibus, venenatis libero vitae, rutrum sem.
Donec ut aliquam urna. Nulla facilisi.
'
Merge files together¶
time {
echo "โโโ"
find . -type f -name "*.txt" -print0 | xargs -0 cat
echo "โโโ"
} > merged.log
Multiline commands output to variable¶
RESULT=$( \
echo "โโโ" ;\
echo "Hello, $RANDOM!" ;\
echo "โโโ" ;\
)
And to capture stderr
as well:
RESULT=$(( \
echo "โโโ" ;\
>&2 echo "Hello, $RANDOM!" ;\
echo "โโโ" ;\
) 2>&1 )
Prefered Bash shebang¶
#!/usr/bin/env bash
Process substitution¶
diff <(ls foo/) <(ls bar/)
Prune find results¶
find . -path "*/build" -prune -o -name "*.kt" -print
Remove ANSI escape sequences¶
sed -e 's/\x1b\[[0-9;]*m//g'
Run command and ignore result¶
foo &>/dev/null || :
Safer scripts¶
#!/usr/bin/env bash
set -euo pipefail
Split string into array¶
INPUT="a (b) \"c\""
IFS=" " read -ra array <<< "$INPUT"
printf '%s\n' "${array[@]}"
a
(b)
"c"
Strip XML comments¶
cat foo.xml |
sed 's/<!--/\x0<!--/g;s/-->/-->\x0/g' `: # Add null chars before and after delimiters` |
grep -zv '^<!--' `: # Set null chars as grep delimiter and invert match` |
tr -d '\0' `: # Remove null chars` |
xmllint --format - `: # Optional reformat to remove empty lines`
Write variable to file¶
echo "$var" > out.txt
printf "%s\n" "$var" > out.txt
cat <<< "$var" > out.txt
cat << EOF > out.txt
$var
EOF
Special parameters¶
$0
Name of the script$#
Number of arguments$*
Arguments joined as a String$@
Arguments as an array$?
Exit status of last command$!
PID of last background command$$
PID of current shell$-
Set of option flags in current shell$_
Last argument of the last command
Total size of files¶
find . -type f -name "*.log" -print0 `: # find all .log files` |
xargs -0 du --total --human-readable `: # compute total space usage` |
awk 'END{print $1}' `: # extract total value`