Skip to content

Instantly share code, notes, and snippets.

@gliech
Last active December 14, 2018 09:35
Show Gist options
  • Save gliech/184dc7566821442202f21dfe15e2b7ff to your computer and use it in GitHub Desktop.
Save gliech/184dc7566821442202f21dfe15e2b7ff to your computer and use it in GitHub Desktop.
Speed comparison of some common ways to check for truthy values in Bash and a snippet of my full implementation.
#!/usr/bin/env bash
# Preface:
# There is nothing wrong with using 'if [[ $VAR == yes ]]; then' for evaluating
# internal variables. The following is mainly intended for cases where you do
# not have full control over the variable in question, like user input or envi-
# ronment variables. I am of the belief however that no one should under any
# condition use something like this:
# | VAR=true
# | if $VAR; then
# | do_something
# | fi
# I could harp on about how calling an external command is much less effective
# than using a bash built-in (and believe me, it is over 3000 times slower). But
# I won't, because the time you have spent in your life waiting for
# /usr/bin/true to execute is probably less than it took to read this paragraph
# so far, and the main reason to use this is because it looks good. The real
# problem with this method is that if you use it, it will become a habit. And if
# it becomes a habit, at some point you will use it to evaluate some form of ex-
# ternal input. And when that happens, this:
# | SOME_ENV_VAR=true
# will work just fine, while this:
# | SOME_ENV_VAR=True
# will either evaluate to false or crash your script, depending on how errors
# are handled, and this:
# | SOME_ENV_VAR=yes
# will fuck you up. Don't believe me? Just execute 'yes &' in a terminal emula-
# tor and see what happens.
# Here is what I ended up doing:
# Bash 4 or bust
if (( BASH_VERSINFO[0] < 4 )); then
echo "Please get a newer version of Bash."
else
# Basic safe boolean evaluation. See this Gist for details:
# https://gist.github.com/gliech/184dc7566821442202f21dfe15e2b7ff
function truthy {
if [[ "${1,,}" == @(y|yes|on|true|1) ]]; then
return 0
fi
return 1
}
function falsy {
if [[ "${1,,}" == @(n|no|off|false|0) ]]; then
return 0
fi
return 1
}
# The following function is meant to be used like this:
# | true_false_default $VAR command -with -arguments
# The function will still fail if the default is reached. If a different
# behaviour is desired, it can be achieved like so:
# | true_false_default $VAR "{
# | command -with -arguments
# | another \"command with an argument\"
# | return 0
# | }"
# The encapsulation in a compound command is not strictly necessary here, but
# it makes the whole construct look a lot neater imo. Also make sure to use
# return not exit unless you really mean to.
function true_false_default {
case "${1,,}" in
y | yes | on | true | 1)
return 0 ;;
n | no | off | false | 0)
return 1 ;;
esac
shift
eval "$@"
return 1
}
# Uncomment this if you include these functions in your bashrc and want to use
# them abso-fucking-lutely everywhere.
# export -f truthy falsy true_false_default
fi
#!/usr/bin/env bash
# The version you will find in /etc/init.d/functions
function truthy_init {
case "$1" in
[tT] | [yY] | [yY][eE][sS] | [oO][nN] | [tT][rR][uU][eE] | 1)
return 0 ;;
esac
return 1
}
# What many people apperantly do
function truthy_shopt {
last_nocasematch=$(shopt -p nocasematch; true)
shopt -s nocasematch
case "$1" in
t | y | yes | on | true | 1)
code=0 ;;
* )
code=1 ;;
esac
$last_nocasematch
return $code
}
# What I would do
function truthy_stringop_match {
# @() supposedly requires extglob but it seems to work with shopt -u extglob
# for me
if [[ "${1,,}" == @(t|y|yes|on|true|1) ]]; then
return 0
fi
return 1
}
# For when you really don't want to use @(). ${,,} still requires Bash 4 though.
function truthy_stringop_case {
case "${1,,}" in
t | y | yes | on | true | 1)
return 0 ;;
esac
return 1
}
# Continue was added to measure the execution time of the test setup. The test
# will execute 'continue 1' which is what continue defaults to anyway.
function_array=(
truthy_init
truthy_shopt
truthy_stringop_match
truthy_stringop_case
continue
)
for func in "${function_array[@]}"; do
printf $func
time (for i in {1..2000}; do $func 1; done)
echo ' '
done
truthy_init
real 0m0,034s
user 0m0,034s
sys 0m0,000s
truthy_shopt
real 0m1,267s
user 0m0,757s
sys 0m0,614s
truthy_stringop_match
real 0m0,034s
user 0m0,033s
sys 0m0,000s
truthy_stringop_case
real 0m0,033s
user 0m0,032s
sys 0m0,001s
continue
real 0m0,007s
user 0m0,007s
sys 0m0,000s
variable=yes
printf "/usr/bin/true"
time (for i in {1..50000}; do /usr/bin/true ; done)
printf "\nCondtional Expression"
time (for i in {1..50000}; do [[ $variable == yes ]]; done)
printf "\nControl"
time (for i in {1..50000}; do :; done)
/usr/bin/true
real 0m51,519s
user 0m15,902s
sys 0m40,313s
Condtional Expression
real 0m0,113s
user 0m0,109s
sys 0m0,004s
Control
real 0m0,097s
user 0m0,093s
sys 0m0,004s
=> /usr/bin/true is (51.519s - 0.097s) / (0.113s - 0.097s) = 3,213.875 times slower than conditional expressions
‾‾‾‾‾‾‾‾‾
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment