From 271e0b7c5d229a5dfe52cbf183bdb09121ca832d Mon Sep 17 00:00:00 2001 From: tripleee Date: Sun, 30 Oct 2022 13:55:55 +0200 Subject: [PATCH] bash.md: update with best practices (#1897) --- bash.md | 197 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 91 deletions(-) diff --git a/bash.md b/bash.md index b9d8eb33f..40369cf0a 100644 --- a/bash.md +++ b/bash.md @@ -32,32 +32,39 @@ This is a quick reference to getting started with Bash scripting. ```bash #!/usr/bin/env bash -NAME="John" -echo "Hello $NAME!" +name="John" +echo "Hello $name!" ``` ### Variables ```bash -NAME="John" -echo $NAME -echo "$NAME" -echo "${NAME}!" +name="John" +echo $name # see below +echo "$name" +echo "${name}!" +``` +Generally quote your variables unless they contain wildcards to expand or command fragments. + +```bash +wildcard="*.txt" +option="iv" +cp -$options $wildcard /tmp ``` ### String quotes ```bash -NAME="John" -echo "Hi $NAME" #=> Hi John -echo 'Hi $NAME' #=> Hi $NAME +name="John" +echo "Hi $name" #=> Hi John +echo 'Hi $name' #=> Hi $name ``` ### Shell execution ```bash echo "I'm in $(pwd)" -echo "I'm in `pwd`" +echo "I'm in `pwd`" # obsolescent # Same ``` @@ -128,65 +135,65 @@ Parameter expansions ```bash name="John" -echo ${name} -echo ${name/J/j} #=> "john" (substitution) -echo ${name:0:2} #=> "Jo" (slicing) -echo ${name::2} #=> "Jo" (slicing) -echo ${name::-1} #=> "Joh" (slicing) -echo ${name:(-1)} #=> "n" (slicing from right) -echo ${name:(-2):1} #=> "h" (slicing from right) -echo ${food:-Cake} #=> $food or "Cake" +echo "${name}" +echo "${name/J/j}" #=> "john" (substitution) +echo "${name:0:2}" #=> "Jo" (slicing) +echo "${name::2}" #=> "Jo" (slicing) +echo "${name::-1}" #=> "Joh" (slicing) +echo "${name:(-1)}" #=> "n" (slicing from right) +echo "${name:(-2):1}" #=> "h" (slicing from right) +echo "${food:-Cake}" #=> $food or "Cake" ``` ```bash length=2 -echo ${name:0:length} #=> "Jo" +echo "${name:0:length}" #=> "Jo" ``` See: [Parameter expansion](http://wiki.bash-hackers.org/syntax/pe) ```bash -STR="/path/to/foo.cpp" -echo ${STR%.cpp} # /path/to/foo -echo ${STR%.cpp}.o # /path/to/foo.o -echo ${STR%/*} # /path/to +str="/path/to/foo.cpp" +echo "${str%.cpp}" # /path/to/foo +echo "${str%.cpp}.o" # /path/to/foo.o +echo "${str%/*}" # /path/to -echo ${STR##*.} # cpp (extension) -echo ${STR##*/} # foo.cpp (basepath) +echo "${str##*.}" # cpp (extension) +echo "${str##*/}" # foo.cpp (basepath) -echo ${STR#*/} # path/to/foo.cpp -echo ${STR##*/} # foo.cpp +echo "${str#*/}" # path/to/foo.cpp +echo "${str##*/}" # foo.cpp -echo ${STR/foo/bar} # /path/to/bar.cpp +echo "${str/foo/bar}" # /path/to/bar.cpp ``` ```bash -STR="Hello world" -echo ${STR:6:5} # "world" -echo ${STR: -5:5} # "world" +str="Hello world" +echo "${str:6:5}" # "world" +echo "${str: -5:5}" # "world" ``` ```bash -SRC="/path/to/foo.cpp" -BASE=${SRC##*/} #=> "foo.cpp" (basepath) -DIR=${SRC%$BASE} #=> "/path/to/" (dirpath) +src="/path/to/foo.cpp" +base=${src##*/} #=> "foo.cpp" (basepath) +dir=${src%$base} #=> "/path/to/" (dirpath) ``` ### Substitution | Code | Description | | ----------------- | ------------------- | -| `${FOO%suffix}` | Remove suffix | -| `${FOO#prefix}` | Remove prefix | +| `${foo%suffix}` | Remove suffix | +| `${foo#prefix}` | Remove prefix | | --- | --- | -| `${FOO%%suffix}` | Remove long suffix | -| `${FOO##prefix}` | Remove long prefix | +| `${foo%%suffix}` | Remove long suffix | +| `${foo##prefix}` | Remove long prefix | | --- | --- | -| `${FOO/from/to}` | Replace first match | -| `${FOO//from/to}` | Replace all | +| `${foo/from/to}` | Replace first match | +| `${foo//from/to}` | Replace all | | --- | --- | -| `${FOO/%from/to}` | Replace suffix | -| `${FOO/#from/to}` | Replace prefix | +| `${foo/%from/to}` | Replace suffix | +| `${foo/#from/to}` | Replace prefix | ### Comments @@ -206,37 +213,37 @@ comment | Expression | Description | | --------------- | ------------------------------ | -| `${FOO:0:3}` | Substring _(position, length)_ | -| `${FOO:(-3):3}` | Substring from the right | +| `${foo:0:3}` | Substring _(position, length)_ | +| `${foo:(-3):3}` | Substring from the right | ### Length | Expression | Description | | ---------- | ---------------- | -| `${#FOO}` | Length of `$FOO` | +| `${#foo}` | Length of `$foo` | ### Manipulation ```bash -STR="HELLO WORLD!" -echo ${STR,} #=> "hELLO WORLD!" (lowercase 1st letter) -echo ${STR,,} #=> "hello world!" (all lowercase) +str="HELLO WORLD!" +echo "${str,}" #=> "hELLO WORLD!" (lowercase 1st letter) +echo "${str,,}" #=> "hello world!" (all lowercase) -STR="hello world!" -echo ${STR^} #=> "Hello world!" (uppercase 1st letter) -echo ${STR^^} #=> "HELLO WORLD!" (all uppercase) +str="hello world!" +echo "${str^}" #=> "Hello world!" (uppercase 1st letter) +echo "${str^^}" #=> "HELLO WORLD!" (all uppercase) ``` ### Default values | Expression | Description | | ----------------- | -------------------------------------------------------- | -| `${FOO:-val}` | `$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:?message}` | Show error message and exit if `$FOO` is unset (or null) | +| `${foo:-val}` | `$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:?message}` | Show error message and exit if `$foo` is unset (or null) | -Omitting the `:` removes the (non)nullity checks, e.g. `${FOO-val}` expands to `val` if unset otherwise `$FOO`. +Omitting the `:` removes the (non)nullity checks, e.g. `${foo-val}` expands to `val` if unset otherwise `$foo`. Loops ----- @@ -246,7 +253,7 @@ Loops ```bash for i in /etc/rc.*; do - echo $i + echo "$i" done ``` @@ -254,7 +261,7 @@ done ```bash for ((i = 0 ; i < 100 ; i++)); do - echo $i + echo "$i" done ``` @@ -277,9 +284,9 @@ done ### Reading lines ```bash -cat file.txt | while read line; do - echo $line -done +while read -r line; do + echo "$line" +done output.txt # stdout to (file) -python hello.py >> output.txt # stdout to (file), append -python hello.py 2> error.log # stderr to (file) -python hello.py 2>&1 # stderr to stdout -python hello.py 2>/dev/null # stderr to (null) -python hello.py &>/dev/null # stdout and stderr to (null) +python hello.py > output.txt # stdout to (file) +python hello.py >> output.txt # stdout to (file), append +python hello.py 2> error.log # stderr to (file) +python hello.py 2>&1 # stderr to stdout +python hello.py 2>/dev/null # stderr to (null) +python hello.py >output.txt 2>&1 # stdout and stderr to (file), equivalent to &> +python hello.py &>/dev/null # stdout and stderr to (null) +echo "$0: warning: too many users" >&2 # print diagnostic message to stderr ``` ```bash @@ -708,6 +717,10 @@ printf "1 + 1 = %d" 2 printf "This is how you print a float: %f" 2 #=> "This is how you print a float: 2.000000" + +printf '%s\n' '#!/bin/bash' 'echo hello' >file +# format string is applied to each group of arguments +printf '%i+%i=%i\n' 1 2 3 4 5 9 ``` ### Transform strings @@ -728,14 +741,14 @@ printf "This is how you print a float: %f" 2 #### Example ```bash -echo "Welcome To Devhints" | tr [:lower:] [:upper:] +echo "Welcome To Devhints" | tr '[:lower:]' '[:upper:]' WELCOME TO DEVHINTS ``` ### Directory of script ```bash -DIR="${0%/*}" +dir=${0%/*} ``` ### Getting options @@ -743,7 +756,7 @@ DIR="${0%/*}" ```bash while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in -V | --version ) - echo $version + echo "$version" exit ;; -s | --string ) @@ -768,10 +781,12 @@ END ```bash echo -n "Proceed? [y/n]: " -read ans -echo $ans +read -r ans +echo "$ans" ``` +The `-r` option disables a peculiar legacy behavior with backslashes. + ```bash read -n 1 ans # Just one character ```