Shell script tricks - Get better at shell scripting
2023-07-16
Today I'm going to talk about some dumb things people do in shell scripting, and I'm also going to tell you a better way to do it, as well as a cool trick.
Don't use echo
echo
seems simple right? echo Hello World!
will print out
'Hello World!' to standard output. Despite being such a simple
command and likely one of the first UNIX programs you interact
with, echo is garbage.
The main reason you should not be using echo is because the way
it works will differ depending on your operating system. For instance,
echo -e
on Arch Linux will interpret backslash escapes such as newlines,
however on other distributions like Gentoo, -e
is intepreted as text.
So on some systems, echo -e 'Install Gentoo\n'
will result in 'Install Gentoo'
being printed and on others it will result in ' -e Install Gentoo' being
printed. In short, the reason echo sucks is because it does not work the same
on all operating systems.
What should I use then, you might say. The answer is use printf
. printf
is
designed to print more complex text, and can do way more than echo
can.
The first difference you're likely going to notice if you've been using echo
is printf
does not append a \n
newline character. This means
printf "a"
printf "b"
printf "c"
will result in 'abc' being printed. Of course, the solution is to just append
\n
to the text you're printing. So printf 'Hello world\!\n
will result
in the same thing as echo 'Hello world\!'
.
If you're a C or maybe C++ programmer, you're likely already very familiar
with printf
and related functions, and the core utility works almost exactly
the same.
Do not do something like printf "$MyVar"
though. That's incorrect usage of
printf
even though it does technically work. The correct way to use printf
with variables is printf 'MyVar1: %s\nMyVar2: %s\n' "$MyVar1" "$MyVar2"
.
%s
here means string, but if you have an integer, you can use %d
. It is
pretty safe to use %s
for everything though, because you don't really have
different data types in shell scripting.
Mostly useless Bash-isms
The Bash shell provides notable useful features, such as arrays. This is a valid use case for Bash. Sometimes using Bash will increase speed because of these features.
But when people are new to shell scripting, they tend to overcomplicate things, and that can result in big problems. I'm guilty of this as well, which is why I'm telling you this. Let's say you want to check if $DISPLAY is defined or not. You know, to check if X or Wayland is used. The brainlet way to do this would be something like this:
if [[ -n "$DISPLAY" ]]; then
x=true
fi
This does work in case you are using Bash, but it is really ugly. You should write POSIX compliant shell scripts unless you depend on a Bash feature which simply cannot be had with POSIX sh. This is the most useless use of Bash I can think of. In order to make this POSIX compliant, you could do this:
if [ -n "$DISPLAY" ]; then
x = true
fi
Yep, it's that simple. Using an if
statement here at all is dumb too, though
but not as bad. The good way to do this is simply [ -n "$DISPLAY" ] && x=true
.
The worst I've seen (and done) is when people use Bash-isms and then proceed to
put #!/bin/sh
or #!/usr/bin/env sh
at the top. At first glance, it may
appear to work just fine. This is likely because /bin/bash is a symlink to
/usr/bin/bash and so Bash is used anyway. If the user is using a strictly
POSIX compliant shell like Dash instead, the script will not run properly.
If you write bash scripts, you should put #!/bin/bash
or #!/usr/bin/env bash
at the top, so that Bash specifically interprets the script.
Another almost completely useless Bash feature is source
. It is nearly identical
to the POSIX compliant .
which simply loads functions and variables from a file.
Refrain from using source
even when writing Bash scripts though, because it's
completely unnecessary and a bad habit.
Loading in functions when necessary
This is not something bad people do but rather something I've found very
useful. If you have a big script, having to read a bunch of functions every time
the script is being interpreted by a shell might waste a lot of time.
If you find that this is the case, you could move some functions to a
separate script and use .
to load that script in when you actually
need to use those functions.
spmenu_run and packr both do this because it results in a pretty big speed improvement. spmenu_run is almost 1000 lines of Bash, and splitting the script like this cut the time it took for stuff to be printed in half.
In the case of packr, if you're installing a program you might not need to have any functions for removing programs. Why not load in the functions you need and no more? Well, there are some notable issues with this. It requires keeping track of more scripts, and if those scripts happen to be missing, your main script will not work. I think this is worth it though but only if you have really big scripts. For a 100 line shell script, this just isn't worth it because the slowdown isn't noticeable.
Conclusion
Shell scripting is basically magic, but you need to use it properly. The reason people aren't being taught to use the right tools for the job is because most articles regarding shell scripting are written for Bash specifically and not POSIX compliant scripts. This teaches new hackers to write Bash scripts when Bash is not required to get the job done.
In any case, hope this blog post was informative, have a good day!