Why
Recently I had to do some tasks that required me to fallowing a strict checklist. It wasn’t anything complex, no creativity required - just do as list tells you to do. I was also one of the creators of the checklist.
I had to do it a few times, almost regularly, sometimes in short notice but nothing extreme.
So obviously I made errors because I knew the checklist so well I wasn’t paying attention.
No more errors
After a few failures that were the result of my (looking for excuses) being bored with the checklist I decided to write some shell scripts that would prevent me from doing almost anything - so prevent me from making a human error. I’m not an expert but during this process I’ve learned a few things, here they are:
Add Help
Even when your script doesn’t have any switches or flags, doesn’t accept any parameters, etc. it’s really good to add simple help under -h
flag.
Yes, you can keep scripts in a repository as part of the project they are used with, or separately, and add README chapter but trust me, nobody will look there.
If you are using the terminal you are already used to use this flag to get help about command or tool.
Here is one of ways to achieve this:
There is a method Help()
that prints info about your script. You can use echo
for printing line by line, but using cat << "EOF"
makes it way faster to edit and more readable.
Then the switch-case is looking for arguments, for now, we are interested in -h
which will run Help()
and exit the script
Handling arguments
You can already see some of it in previous Gist, here some more cases:
Note that we have a few types of arguments passed here:
- simple flag like
-a
can be used to set the in-script variable that can be then used inIF
statement - flag with a value like
-p
or--path
can take string (and only string) argument - all other arguments will be sent to
OTHER_ARGUMENTS
array
shift
after each case is handled removes argument from further processing. For the flag with the value, it needs to be doubled because you want to remove the flag itself and value from processing.
It’s not necessary to have both short -p
and long --path
flag names covered, but it’s a good habit.
Adding some colors
I love colors in terminal :) and your scripts can also make use of them
It’s important to add ${NC}
at the end, otherwise, next printed line will be in last used color
Handle user input
To handle simple yes/no from user you can do:
IF|ELSE
General idea of IF looks like this:
if [ <statement> ] # <-- beginning of statement
then # <-- IF something THEN do something
<do some work> # <-- actuall work
fi # <-- closing IF statement
Indenting doesn’t matter, but it’s worth keeping it consistent - remember you are doing this to avoid silly mistakes.
A bit more comple example:
In this gist there are few examples of IF statement:
; then
is moved to the same line with the statement, it looks more like we are used to in “normal” code- negation with
!
inside condition block - nested IF
elsif
- combined conditions with AND
&&
but could be also with OR||
List of typical operators shamelessly borrowed from here:
Operator | Description |
---|---|
! EXPRESSION | The EXPRESSION is false. |
-n STRING | The length of STRING is greater than zero. |
-z STRING | The length of STRING is zero (ie it is empty). |
STRING1 = STRING2 | STRING1 is equal to STRING2 |
STRING1 != STRING2 | STRING1 is not equal to STRING2 |
INTEGER1 -eq INTEGER2 | INTEGER1 is numerically equal to INTEGER2 |
INTEGER1 -gt INTEGER2 | INTEGER1 is numerically greater than INTEGER2 |
INTEGER1 -lt INTEGER2 | INTEGER1 is numerically less than INTEGER2 |
-d FILE | FILE exists and is a directory. |
-e FILE | FILE exists. |
-r FILE | FILE exists and the read permission is granted. |
-s FILE | FILE exists and its size is greater than zero (ie. it is not empty). |
-w FILE | FILE exists and the write permission is granted. |
-x FILE | FILE exists and the execute permission is granted. |
Run commands
My favorite part of using bash scripts is how easy it runs commands. If you can run it in the terminal - you can as easily run it in the script.
git pull || exit 1
eval "./gradlew" 'clean :app:assembleDebug --info'
open "app/build/outputs/apk/app/debug/"
(cd $ARG_PATH &&
git checkout feature/awersome_feature || exit 1 &&
./scripts/awersome_release_script.sh)
What we have here is:
- using git,
|| exit 1
means the whole script will fail if there was a non-successful result of the command, which is pretty useful eval
takes a few strings and combines it into one command that is run after, it comes in handy when you want to pass some dynamic argumentsopen
tries to open the thing that was passed to it, in my case its a directory so on OSX it will open this directory in finder- the last example is few bounded commands - first, it goes to provided directory with
cd
, then runsgit
command in this directory, and then runs script - also from the perspective of the directory fromcd
. After it’s done root directory is back to from where the script was running in the first place.
Print ASCII header
This one is the most important thing in this post, if you can remember only 1 thing from it please make it this one. All cool scripts have ASCII header displayed after you run it. There might be very good scripts that don’t have it, but for sure they are not cool.
I like to generate my headers in this tool. It has many fonts and some additional options.
Then just copy-paste it like this:
cat << "EOF"
__ __ _____ _ _ _ _____ _ _
| \/ | / ____| | | | | / ____| (_) | |
| \ / |_ _ | (___ | |__ ___| | | | (___ ___ _ __ _ _ __ | |_
| |\/| | | | | \___ \| '_ \ / _ \ | | \___ \ / __| '__| | '_ \| __|
| | | | |_| | ____) | | | | __/ | | ____) | (__| | | | |_) | |_
|_| |_|\__, | |_____/|_| |_|\___|_|_| |_____/ \___|_| |_| .__/ \__|
__/ | | |
|___/ |_|
EOF
Enough basics
This post is just about basics, enough to start writing scripts that do some work for you. If you need some more info about IF statements, file handling, or running commands please do it yourself :) or it might appear in some future posts.