Marcin Stożek "Perk"
# figure out the absolute path to the script being run a bit
# non-obvious, the ${0%/*} pulls the path out of $0, cd's into the
# specified directory, then uses $PWD to figure out where that
# directory lives - and all this in a subshell, so we don't affect
# $PWD
STEAMROOT="$(cd "${0%/*}" && echo $PWD)"
rm -rf "$STEAMROOT/"*
Shell
(command processor)
Command language
(scripting language subtype)
It's a DSL for job control
It's Turing complete
$ whatis bash
bash (1) - GNU Bourne-Again SHell
$ whatis javascript
javascript: nothing appropriate.
$ whatis
whatis what?
Bash variables are untyped
Bash variables are character strings
Bash is stringly typed
* it allows for arithmetic operations if
a variable contains digits only;
also there are arrays
echo $1
echo one two *
echo "$1"
echo "one two *"
$ VAR="one two *"
$ echo "${VAR}"
one two *
$ echo ${VAR}
one two bin boot cdrom dev etc home...
LANGUAGE= "w"
LANGUAGE =w
LANGUAGE = w
LANGUAGE="w"
$ FOO = bar
FOO: command not found
$ env | grep bar
$ FOO=bar env | grep bar
FOO=bar
OWNERSHIP=Ala has a cat
OWNERSHIP="Ala has a cat"
$ OWNERSHIP=Ala has a cat
has: command not found
$ OWNERSHIP="Ala has a cat"
$ echo "${OWNERSHIP}"
Ala has a cat
#!/bin/bash
my_var="Some value"
#!/bin/bash
readonly MY_VAR="Some value"
with the help of ${1:-"default"}
function list_files() {
find . -iname "*.$1"
}
function list_files() {
local type=${1:-"tmp"}
find . -iname "*.${type}"
}
#!/bin/bash
for f in "$@"; do
echo "$1"
shift
done
#!/bin/bash
declare -ra ARGS=("$@")
main "${ARGS[@]}"
$ fun() { ARGS="$@"; for f in $ARGS; do echo "$f"; done; }
$ fun 1 "2 3"
1
2
3
$ fun() { ARGS="$@"; for f in "$ARGS"; do echo "$f"; done; }
$ fun 1 "2 3"
1 2 3
$ fun() { declare -a ARGS=("$@"); for f in "${ARGS[@]}"; do echo "$f"; done; }
$ fun 1 "2 3"
1
2 3
for file in *; do
something "$file"
done
for file in ./*; do
something "$file"
done
var="`command`"
var="$(command)"
echo "$IFS"
(IFS="\n"; echo "$IFS")
echo "$IFS"
var_backup="$var"
var="whatev"
do_something_with "$var"
var="$var_backup"
$ VAR="one"
$ echo "${VAR}"
one
$ (VAR="two"; echo "${VAR}")
two
$ echo "${VAR}"
one
command1 \
| command2 \
| command3 \
| command4 \
...
command1 | command2 | command3 | command4 | command5...
command1 \
&& command2 \
&& command3 \
|| command4 \
...
pure bash instead of spawning external processes
[[ -z $string ]] \
&& echo 'True if the length of string is zero.'
[[ -n $string ]] \
&& echo 'True if the length of string is non-zero.'
[[ $string1 == $string2 ]] \
&& echo "True if the strings are equal"
[[ -e $file ]] && echo 'True if file exists.'
[[ -x $file ]] \
&& echo 'True if file exists and is executable.'
[[ -L $file ]] \
&& echo 'True if file exists and is a symbolic link.'
[[ "$text" = 'some text' ]] && echo 'it works'
[[ $text = 'some text' ]] && echo 'this works to!'
[[ 1 > 2 ]] \
&& echo true \
|| echo false # finally some sane result
[ $file = "bar" ] # wrong quotes
[ 1 > 2 ] # wrong - it's not what you think it is!
function delete_temp_files() {
local path="$1"
local current_pid="$$"
find "${path}" -iname "*.${current_pid}.tmp" -delete
}
delete_temp_files "${path}"
cd $path
find . -iname "*.$$.tmp" -delete
Variables can be local
Code is easier to debug
Code is self documenting
Easier to understand code flow
$ echo "'$MEH'"
''
$ echo "'$BLAH'"
''
$ fun() { BLAH="nasty global"; local MEH="nice local"; }
$ fun
$ echo "'$MEH'"
''
$ echo "'$BLAH'"
'nasty global'
function fun() {
path="$1"
}
function fun() {
local path="$1"
}
$ ./test.sh
some value
$ cat test.sh
#!/bin/bash
function one() {
local local_variable="some value"
two
}
function two() {
echo $local_variable
}
one
#!/bin/bash
function main() {
for i in "$@"; do
echo "$i"
done
}
main "$@"
_________________________________________
/ You take the red pill — you stay in \
| Wonderland, and I show you how deep the |
\ rabbit hole goes. /
-----------------------------------------
\ ^__^
\ (@@)\_______
(__)\ )\/\
||----w |
|| ||
$ cowsay -p "blah blah blah..."
# Higher order functions
function stalosie() { echo "zgasla mi fajka mi zgasla"; }
function stao() { echo "lazienka jest zamknieta"; }
function cosie() {
local co="$($1)"
echo ">> ${co} <<"
}
$ cosie stalosie
>> zgasla mi fajka mi zgasla <<
$ cosie stao
>> lazienka jest zamknieta <<
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
function cleanup() {
# don't do this
# rm -rf --no-preserve-root /
for file in /my/cache/*; do
rm -v "${file}"
done
}
trap cleanup EXIT
Prints out every command before executing - expanded.
#!/bin/bash
set -x
$ bash -x /path/to/script.sh
#!/bin/bash
set -x
some_buggy_function
set +x
#!/bin/bash -x
$ cat test.sh
#!/bin/bash
function main() {
for i in "$@"; do
echo "$i"
done
}
main "$@"
$ bash -x test.sh one "2 three"
+ main one '2 three'
+ for i in "$@"
+ echo one
one
+ for i in "$@"
+ echo '2 three'
2 three
readonly LOG_FILE="/tmp/$(basename "$0").log"
info() { echo "[INFO] $*" | tee -a "$LOG_FILE" >&2 ; }
warning() { echo "[WARNING] $*" | tee -a "$LOG_FILE" >&2 ; }
error() { echo "[ERROR] $*" | tee -a "$LOG_FILE" >&2 ; }
fatal() { echo "[FATAL] $*" | tee -a "$LOG_FILE" >&2 ; exit 1 ; }
Shell script static analysis tool
Gives warnings and suggestions
Checks for incorrect command use
Can make suggestions to improve style
$ cat skrypcik.sh
#!/bin/bash
function main() {
local args=$@
local date=`date`
for $i in args; do
echo $date
done
}
main $*
$ shellcheck skrypcik.sh
In skrypcik.sh line 4:
local args=$@
^-- SC2034: args appears unused. Verify it or export it.
^-- SC2124: Assigning an array to a string! Assign as array, or use * instead of @ to concatenate.
In skrypcik.sh line 5:
local date=`date`
^-- SC2155: Declare and assign separately to avoid masking return values.
^-- SC2006: Use $(..) instead of legacy `..`.
In skrypcik.sh line 7:
for $i in args; do
^-- SC2034: i appears unused. Verify it or export it.
^-- SC1086: Don't use $ on the iterator name in for loops.
^-- SC2043: This loop will only ever run once for a constant value. Did you perhaps mean to loop over dir/*, $var or $(cmd)?
In skrypcik.sh line 8:
echo $date
^-- SC2086: Double quote to prevent globbing and word splitting.
In skrypcik.sh line 12:
main $*
^-- SC2048: Use "$@" (with quotes) to prevent whitespace problems.
^-- SC2086: Double quote to prevent globbing and word splitting.
rm -rf "${STEAMROOT:?}/"*
rm -rf "$STEAMROOT/"*
https://google.github.io/styleguide/shell.xml
It's battle tested
World is a better place if we have style conventions
Promotes good practises
#!/bin/bash
# file: equality_test.sh
testEquality() {
assertEquals 1 1
}
# Load shUnit2.
. ./shunit2
$ ./equality_test.sh
testEquality
Ran 1 test.
OK
The dos2unix converts line endings like a pro.
/bin/bash^M: bad interpreter: No such file or directory
$ [ whereis my brain?
bash: [: missing `]'
Marcin Stożek "Perk"
@marcinstozek / perk.pl