< Index

Everyone should learn POSIX shell scripting

Every once in awhile I'm exposed to a Windows installation somewhere - like when I was still going to university, or when I'm made to use a work computer. One of the games I like to play in my head while suffering through this tramatic experience is trying to see what particular, nameable things I don't like about Windows compared to my bog-standard KDE Plasma systems on Linux. Now... this isn't really hard to do, since you're staring at a maze of dark patterns doing any basic task on Windows. Some of them are so evil I don't know how anyone thinks this is okay, like how when Microsoft asks you to backup your system, the selection in the dropdown you have to pick to get Windows to leave you alone for a month about doing backups hovers directly over the button to immediately start backing up.

Anyways, the one thing I always, always miss when I'm using a Windows system instead of a Linux system is the ability to easily script out long sequences of commands, especially administrative commands such as system updating or resetting. If you're using basically any system that's not completely locked down and isn't Windows, you have access to POSIX shell scripts. The POSIX shell is not an actual piece of software so much as it is a publicly available standard that all conforming shells must be compatible with. Any system that calls itself UNIX or Unix-like is going to have some level of compatible POSIX shell. For Linux systems in particular, most people use zsh or bash which are just feature supersets over the standard POSIX shell. Even macOS is technically a compliant UNIX system, and thus has fully certified POSIX shell compatibility.

For the POSIX shell, one of the capabilities that it has is to read shell script files in regular UTF-8 encoded text and interpret them on the fly with their own simple but effective programming language. Basically, you can set the executable bit on a text file and pass it to the shell and it'll interpret it as if it's a whole programming language, and to the user running the script there's no indication they're not using any other terminal program. This gives you a way to write simple programs that are extremely portable to other systems, not requiring system or architecture specific compiling and also not inherently depending on any particular system's behavior. This capability to write standalone executable scripts is shared by other shells like bash and zsh, but writing in those shells inherently limits your portability to only other users of those particular shells. Some people argue that you should target bash since most Linux systems and all GNU systems use bash, but I don't see any reason to single out the *BSD users, and I also haven't yet found any killer feature in bash that made me really need to write a script in it. So, I write POSIX scripts for everything and keep my options open.

A shell script doesn't have to be some horribly difficult thing. In fact, the language of the POSIX shell is best for short, sweet calls to other programs, but it is also capable of more than that. One of the things I've used all the time is shell scripts that are effectively just aliases for really long commands. It comes up a lot (especially on distros where you spend a lot of time in the terminal, like e.g. Gentoo) that you'll have these really long commands you have to run every once in awhile and you wish you could just shortcut it to something else. Shells do have the ability to directly alias a command, first of all, but this can get confusing really quickly when running a command does something you're not expecting, so I try to keep these to a minimum. Instead, I just copy paste the long command into a regular ASCII text file in any text editor (let's call it shortcut.sh), put #!/usr/bin/env sh above it as the first line of the file, change it to executable using the command chmod +x shortcut.sh, then put the file shortcut.sh somewhere that I can easily reach it on the terminal, like within my PATH. Now I can just call shortcut.sh on the terminal to access that long command. Do I need to pass arguments to it after the long text I put? Then, just put $@ after the command in the file and you'll get all the arguments passed through.

So, for example, you could have a file like this named echoalias.sh:

#!/usr/bin/env sh

echo $@

Then you can run echoalias.sh hello world these are arguments, and the program will print hello world these are arguments. It seems a little complicated this first time to set the alias up, but all these individual steps are basic and also very general Unix system tasks that come up all the time in other contexts when using these systems.

Under this website's Files index, I currently have a Fedora update shell script that automates all of the different processes in a normal system update for a Fedora Linux system. It goes through and updates all your packages, removes unneeded packages, rebuilds your kernel modules, updates your grub configuration, and then updates flatpaks and removes unneeded ones. Basically, it does everything that KDE Discover does except firmware updates (I have this in a separate shell script that's not currently public). The question I think people might have is, why do this whole thing this way using shell scripts instead of just using a graphical package manager to update your system, like KDE Discover or whatever else? The answer is that what those graphical update tools are doing basically boils down to a long chain of individual smaller commands, and when you have it all broken down and strung together manually in a shell script, you're able to go back and comment out specific lines, or give richer output and error handling on certain steps, or do whatever you need to do to make your system update go as smoothly as possible. It can be clearer and easier than trying to run the closed off tool with just one mysterious "Update" button and then debugging afterwards if something goes wrong, not knowing at what step the update crashed on because you're not able to easily set debug messages.

That's really the difference here. Let me go on a bit of an anecdote. At my last job I had to setup recycled laptops for sale with Windows 11, and the process involved setting up a ton of software and then running the horribly slow and terrible Windows Update like 8 full times until it eventually stops updating again. All of this stuff I had to do manually, clicking icons, unchecking options, bypassing the Windows online account restrictions, ... and I'm just sitting here, a UNIX fanatic, just wishing, praying I could write all this stuff into a script and just string together some commands to run each installation. Scripting might seem complicated or annoying, but it's oftentimes the easiest way to solve an administrator level problem, both in terms of time to set up and time to execute repeatedly. Instead, using and especially setting up Windows systems without this freedom is this gruelling horrible task that I have to repeat over, and over again. Shell scripts fix this. They just give you everything you need: success or fail state for any command, easy printing capabilities, early returns, and calling other smaller POSIX utilities on your system when you need to. If you're targetting a POSIX shell, you might as well also throw in POSIX tools like cat, grep, or make which really extends your portable scripting potential as well.

I just wanted this to be a little kick in the pants for people on non-Windows systems to try shell scripting. It's easy, it's fun... it's like writing Python. Loosey-goosey, but works everywhere. (And, I'm pretty sure POSIX shell scripts are interpreted faster than Python 3, depending on the system shell that's reading them.) My shell scripts here and on Codeberg in various projects are rather simple, I barely ever use an if-else construction and just use stuff like &&, ||, exit 0, and rudimentary functions and I'm more than happy with the results. Give the next .sh file you see a look inside, and see if you learn something from it.

June 30, 2026. Updated August 1, 2026.