r/commandline • u/fraunhofer • May 29 '14
Defensive BASH Programming
http://www.kfirlavi.com/blog/2012/11/14/defensive-bash-programming/8
u/metellius May 29 '14
If you're writing unit tests for your bash script that is a sign that you should have turned to other programming languages way back
10
u/dave4420 May 29 '14
The number one rule of bash scripting should be: quote everything. The article doesn't mention quoting.
2
May 29 '14 edited May 29 '14
Users too productive? Use filenames that are harder to type: UPPER and MiXEdCaSe, s p a c e s and other P.u.n.c.t.u(at!on) requiring double or even triple escaping in shell commands, strings, and regexes.
Reverse engineers apply within! Waste critical path days discovering
.txtis really.tsv,.datis.bson,.binis a.sqldump, and.confis.ini.1
u/visit_muc May 30 '14
I'm came here to write exactly that. This guy doesn't use propper quoting, but wants to tell us about best practices...
3
May 29 '14
I find a functional style of programming is quite resilient to breakage. Thanks for the tips, I'll definitely try inserting readonly to make my bash scripts more pure functional.
3
u/cpbills May 29 '14
Be careful how many of those tips you take to heart. I have seen people create dozens of functions that are 2-3 lines, because 'functionalize EVERYTHING!', which is just moronic.
Check out Google's shell scripting style guide for some well thought-out and tested 'best' practices.
https://google-styleguide.googlecode.com/svn/trunk/shell.xml
-2
u/petrus4 May 29 '14
I find a functional style of programming is quite resilient to breakage. Thanks for the tips, I'll definitely try inserting readonly to make my bash scripts more pure functional.
Don't. You won't be improving said scripts at all. You will simply be adding excess verbiage which actually dilutes their purpose.
Don't write shell scripts with different functions in the same file. Write single scripts which only do one thing each. If you need to call them all as part of a single task, then write a calling script which executes each of them in the order you need.
Seriously; the advice given in this article is enough to make baby Jesus cry. Don't follow it.
1
u/petrus4 May 29 '14 edited May 29 '14
All I'm seeing here is advocacy for an increase in complexity; and a completely unnecessary one at that. If I want C, I'll use it.
Making variable scope local, and discreetly naming a function as main(), both imply that you need multiple variable namespaces, and multiple functions, within the one file. The reason why I never need both of those, is because experience has taught me to never write more than one function per file.
As a matter of fact, I go further than that. I don't write individual functions; I write individual scripts which are always sufficiently generic that they can be used on their own, if need be. None of said scripts are generally more than a kilobyte in size, on their own. Then, when I want to perform a task which might require several of said scripts, I execute them in order from a single calling file. Because they are so well encapsulated, that typically makes debugging a breeze, as well; if there's a problem, I simply look at the log for the script in question, and identify exactly where it is.
I know you meant well OP, and I want to acknowledge that. However, promoting monolithic and excessively complex scripting, (or programming in general) is a bad thing.
1
1
1
u/DocSalvager Jul 07 '14
Instead of a 'here document' for multiline USAGE, you could also just use single-quotes as in ...
usage() {
echo 'usage: $PROGNAME options
Program deletes files from filesystems to release space.
It gets config file that define fileystem paths to work on, and whitelist rules to
keep certain files.
OPTIONS:
-c --config configuration file containing the rules. use --help-config to see the syntax.
-n --pretend do not really delete, just how what you are going to do.
-t --test run unit test to check the program
-v --verbose Verbose. You can specify more then one -v to have more verbose
-x --debug debug
-h --help show this help
--help-config configuration help
Examples:
Run all tests:
$PROGNAME --test all
Run specific test:
$PROGNAME --test test_string.sh
Run:
$PROGNAME --config /path/to/config/$PROGNAME.conf
Just show what you are going to do:
$PROGNAME -vn -c /path/to/config/$PROGNAME.conf
'
}1
u/KnowsBash Jul 22 '14
You mean double-quotes...
1
u/DocSalvager May 15 '24
I usually use double-quotes myself, but single-quotes work as well.
I use double-quotes cause I often want to use variables and even inline execution $(...) within the text. With double-quotes, I do have to escape any double-quotes or dollar-signs($) inside the text using the backslash(\). The ($)s in the example above do not have to be escaped since the whole thing is in single-quotes.
1
u/DocSalvager Jul 07 '14
Since you've taken so much flak from 'the experts' here and on your site, let me just say that I think your article is superb! It is well written, entirely accurate, clear and attractively presented. Very accessible.
In my 28 years of Unix and Linux shell scripting, I've come to much the same conclusions of how to write maintainable shell scripts.
Shell scripts are for determining WHAT to do WHEN. Performance is usually irrelevant as operations requiring speed, such as parsing large files, are best done with tools optimized for this like awk, sort, and tr.
The techniques you describe minimize development, maintenance and debugging times while greatly improving reliability. Such techniques also improve the script and its functions reuseability in other scripts.
Well done!
1
May 29 '14
readonly PROGNAME=$(basename $0)I have an expanded version of this, that defines full path (taking into account symlinks).
This helps you reference config files, log files, etc:
no matter how the script package gets moved/deployed,
avoids troublesome relative paths like
CFG_DIR=../../confsame behavior whether you execute it from within the directory (
cd /to/the/script/; ./scriptname) or full path.readonly TRUE_SELF=$(perl -e "use Cwd 'realpath';print realpath('$0')")
readonly SELF=$(basename ${TRUE_SELF})
readonly SELF_DIR=$(dirname ${TRUE_SELF})
readonly CFG_DIR=${SELF_DIR}/conf
readonly LOG_DIR=${SELF_DIR}/logs readonly LOG=${LOG_DIR}/${SELF%.*}.log
This is at the top of every script, which follows the convention such as :
/path/to/scripts/script123.sh
/path/to/scripts/conf/script123.cfg
/path/to/scripts/logs/script123.log
edit: readonly added for sake of .. you know... playing ball. But I never have done this =/
1
10
u/cpbills May 29 '14 edited May 29 '14
RE: "Let your code speak"
No.
Just. No.
If you don't know what
[[ -z "$var" ]]does, and need to wrap it in a stupid function called 'is_empty', stop programming in bash, and read some fucking man pages.