speedie.site

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!

Source

Comment

To post a comment, you must be logged in.