Bonus Drop #34 (2023-12-03): Cautionary Tales

Where Am I?; Set It And Forget It?; What The Shell?

I switch between Bash and Zsh quite a bit as I traipse across work and personal projects. So, for today’s Bonus Drop, I thought I’d cover some of the more interesting “gotchas” and/or areas to be aware of.

Even if you don’t move between them too often (or, at all), you might still find some useful tidbits about your default shell that you weren’t aware of.

TL;DR

This is an AI-generated summary of today’s Drop.

Not very concise but 100% accurate. Perplexity even picked up the backticks and links without requiring any touch-up.

  • The blog post begins with a deep dive into how Unix shells and the current directory are managed. The author, Chris Siebenmann, explains how the Unix kernel keeps track of the current directory as a reference to a kernel object, typically the directory’s inode. He also discusses the development of kernel caches of the mappings between names and inodes, particularly in Linux, and how the kernel uses this information. The article further explores the role of shells in keeping track of the current directory and the differences between the kernel’s view and the shell’s view of the current directory, especially in handling symbolic links. The primary resource for this section is Unix shells and the current directory.

  • The second part of the blog post focuses on shell script best practices, as outlined by Shrikant Sharat Kandula. The author highlights three practices involving the set command that are worth noting: set -o errexit, set -o nounset, and set -o pipefail. The author also mentions other set-it-and-forget-it habits to consider. The primary resource for this section is Shell Script Best Practices.

  • The final part of the blog post discusses the differences and potential issues when writing scripts for different POSIX shells, such as Bash and Zsh. The author highlights differences in how pipes (|) and reads are handled in Bash and Zsh. The author also mentions that Apple may remove Bash from an upcoming OS release, suggesting a migration to Zsh. The primary resources for this section include an oddly lengthy and useful SO thread, a similar post by GeeksForGeeks, Apple’s basic guide, and Armin Briegel’s series of resources, including a book.


Where Am I?

person wearing black leather shoes

In Unix shells and the current directory Chris Siebenmann takes us on a deep dive into how Unix shells and the current directory are managed. He discusses how the Unix kernel keeps track of the current directory as a reference to a kernel object, typically the directory’s inode. Chris also explains the development of kernel caches of the mappings between names and inodes, particularly in Linux, and how the kernel uses this information. The article further delves into the role of shells in keeping track of the current directory and the differences between the kernel’s view and the shell’s view of the current directory, particularly in handling symbolic links.

If you do give it a go, you’ll come away with an in-depth understanding of the technical aspects of managing the current directory in Unix systems, including the differences between the kernel’s perspective and the shell’s perspective. It also highlights the potential implications of these differences, such as the behavior of the cd command and the management of symbolic links.

Until today, I had never used the -P and -L options of pwd, but will be doing so moving forward. man pwd or hit Chris’ blog for what those do.

Thankfully, I’ve avoided use of $PWD over the years, so have not been bitten by the potential bugs doing so might introduce.

Set It And Forget It?

a close up of a control panel with buttons and switches

I’m a sucker for “do X this way” lists, as it gives one a peek into the baseline mental model that the author uses in that list’s context. Most of them (generally) have at least a couple of new good ideas, or more explanation/a different take on some existing ones.

Shrikant Sharat Kandula’s “Shell Script Best Practices”, is a great (if, incomplete, IMO) template to follow if you do not already have your own curated list of things to do when you write bash/ksh/POSIX shell scripts.

Three of them involving the set command are worth noting here, in conjunction with today’s theme:

  • Use set -o errexit at the start of your script: this makes it so that when a command fails, the shell exits instead of continuing with the rest of the script.

  • Prefer to use set -o nounset: this will make the script fail when accessing an unset variable. It’s a great typo catcher. When you want to access a variable that may or may not have been set, use "\${VARNAME-}" instead of "\$VARNAME".

  • Always invoke set -o pipefail: this ensures that a pipeline command is treated as failed, even if just one command in the pipeline fails.

I remember as a junior hrbrmstr failing to set -o nounset in a $DAYJOB context for something and boy howdy was that an unpleasant day.

Definitely check out Shrikant’s entire list.

There are some other set-it-and-forget-it habits you may want to consider getting into.

  • -f: disables filename expansion (globbing) if you don’t really need it.

  • -m enables job control. All processes run in a separate process group.

  • -C: disallows the existing regular files to be overwritten.

What The Shell?

a close up of a shell with a white background

There is an excellent chance Apple will fully remove Bash from an upcoming OS release. Sure, one could just Homebrew-in a modern Bash (many folks already do), but that doesn’t help anyone if Apple initially, silently does the removal; and, you are likely better off considering migrating to Zsh. That comes with some “gotchas”, as there are many subtle differences between the two shells, and you may need to revisit your scripts.

There are several “gotchas” that you should be aware of when writing scripts for different POSIX shells, as some features and behaviors may not be consistent across shells like Bash, Zsh, and others. Here are some major differences and potential issues:

Two interesting ones are the way pipes (|) and reads are handled.

In Bash, the right-hand side of a pipeline (|) runs in a subshell. A subshell is a separate instance of the shell that is a child of the current shell. When a command in a pipeline runs in a subshell, it cannot change the environment or variables of the parent shell. For example, if you set a variable in the right-hand side of a pipeline, that change won’t be visible in the parent shell after the pipeline completes.

In Zsh, it runs in the parent shell. This means that any changes to the environment or variables made by the right-hand command in a pipeline will persist in the current shell session after the pipeline completes. Unfortunately, this can lead to different behaviors when using pipelines in scripts.

The read command is used to read a line of input from standard input (i.e., the keyboard). It, too, has some key differences between shells.

For instance, Bash doesn’t split the input into an array directly using read. You typically need to use a workaround, like reading into a string and then splitting it into an array.

read -r line
array=($line)

Whereas Zsh allows direct reading into an array using read -A:

read -r -A array

In both shells, read‘s’ -t option can be used to set a timeout for the read operation. Unfortunately, Zsh interprets the value as tenths of a second.

There are a few additional Zsh/Bash read differences you may want to spend some time poking at. One place you can do this poking is in this oddly lengthy and useful SO thread. GeeksForGeeks also has a similar post.

Apple has a basic guide as well, and Armin Briegel has a series of resources, including a book, to help you get settled into the Zsh universe.

FIN

What’s your most frustrating difference between Bash & Zsh?

Continued gratitude for your support! ☮️

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.