Drop #554 (2024-11-14): What The Shell?

up; BATS; cmdx

I’m back from a lovely visit to , and am slightly jealous of folks who live in that part of Nevada.

It’s been a minute since we focused on some good ol’ shell topics, so today we have three resources that all help machinate shell ops in some way, shape, or form.


TL;DR

(This is an AI-generated summary of today’s Drop using Ollama + llama 3.2 and a custom Modelfile.)

  • The up utility enables efficient directory navigation in Bash and Zsh by allowing direct jumps to parent directories with shell completion support (https://github.com/helpermethod/up)
  • BATS (Bash Automated Testing System) is a testing framework for Bash scripts that provides isolated test execution, parallel testing capabilities, and multiple output formats (https://github.com/bats-core/bats-core)
  • cmdx is a new Golang module that streamlines command-line operations management with structured error handling and flexible configuration options (https://github.com/kgs19/cmdx)

up

Photo by David Besh on Pexels.com

The up utility offers an spiffy solution for efficient directory navigation in both Bash and Zsh. Rather than repeatedly typing cd .. or maintaining janky aliases for parent directory traversal, up enables direct jumping to parent directories by name with shell completion support.

For Bash users on Linux, clone the repository to ~/.up and add these lines to .bashrc:

. "$HOME/.up/up"
. "$HOME/.up/completion/bash/up"

macOS users brave enough to still run system Bash should add the same lines to .bash_profile instead.

Zsh users need to add the following to .zshrc after cloning:

. "$HOME/.up/up"
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
. "$HOME/.up/completion/bash/up"

The utility provides tab completion to display available parent directories in your current path. You can jump directly to a parent directory by typing its name after the up command:

up completion

The completion system also supports partial matches, allowing you to type a prefix and use tab completion to reach your target directory. It’s also a small enough completion function that compactly showcases how to write a minimal completion function.


BATS

Photo by Clément Falize on Unsplash

The up resource uses a versatile testing framework that I haven’t seen in many projects/repos, so I thought this would be a good opportunity to showcase it.

BATS (GH) (Bash Automated Testing System) is a testing framework specifically designed for Bash scripts that follows the Test Anything Protocol (TAP). It lets us write test cases in a familiar Bash syntax while providing a structured way to validate shell script behavior.

The framework executes each test file as a discrete unit, with individual test cases running in their own isolated subshell. This isolation prevents test interference and ensures reliable results. Test files use the .bats extension and contain one or more test cases defined using the @test annotation.

Test cases follow a straightforward pattern. Each test begins with the @test annotation followed by a descriptive name in quotes. The test body contains standard Bash commands and assertions using the run helper and various assertion functions like assertassert_equal, and assert_output.

@test "validate config parsing" {
  run ./parse_config.sh test.conf
  assert_success
  assert_output "Configuration loaded"
}

BATS provides parallel test execution capabilities through the --jobs parameter, which can significantly reduce test suite runtime on multi-core systems. The framework captures both stdout and stderr, making it possible to verify both successful output and error conditions.

The built-in assertion system offers precise control over test validation:

assert_equal "$result" "expected"
assert_success
assert_failure
assert_output --partial "string"

Test files can include setup() and teardown() functions that run before and after each test case respectively. This enables proper test isolation and resource cleanup:

setup() {
  export TEMP_DIR="$(mktemp -d)"
}

teardown() {
  rm -rf "$TEMP_DIR"
}

BATS supports multiple output formats including TAP (default), pretty output for human readability, and JUnit XML for CI/CD integration. The output format can be specified using the --formatter parameter during test execution.

We used this extensively in my previous role, and it doesn’t take too much work to get used to the syntax and workflow.

Make sure to check out the entire BATS-verse for add-ons, such as a GitHub actions runner.


cmdx

Photo by RDNE Stock project on Pexels.com

cmdx is a super new Golang module for command-line operations management, focusing on streamlined command execution and comprehensive error handling.

The library has functions to execute shell commands and handle the output systematically, whether it be printing it back out, or storing it in a variable or file. The library implements structured error handling through the CommandError type, giving us detailed information about command execution failures, including exit codes and error messages.

The library implements a flexible configuration system through its Config struct, which supports two primary settings:

  • PrintCommandEnabled: Controls command echo functionality before execution, configurable via the CMDX_PRINT_COMMAND_ENABLED environment variable.
  • CommandDir: Determines the working directory for command execution, settable through the CMDX_COMMAND_DIR environment variable.

It’s super easy to use. This is an example in the repo which stores the output of a date command run to a Go string.

package main

import (
	"github.com/kgs19/cmdx"
	"log"
)

func main() {
	command := "date"
	args := []string{"+%H:%M"}
	out, err := cmdx.RunCommandReturnOutput(command, args...)
	if err != nil {
		log.Fatalf("Error executing 'date' command: %v", err)
	}
	println("cmd output: " + out)
}

I try to never shell out from compiled code anymore, but the need does come up, and this is one of the more streamlined libraries I’ve seen for doing so.


FIN

We all will need to get much, much better at sensitive comms, and Signal is one of the only ways to do that in modern times. You should absolutely use that if you are doing any kind of community organizing (etc.). Ping me on Mastodon or Bluesky with a “🦇?” request (public or faux-private) and I’ll provide a one-time use link to connect us on Signal.

Remember, you can follow and interact with the full text of The Daily Drop’s free posts on Mastodon via @dailydrop.hrbrmstr.dev@dailydrop.hrbrmstr.dev ☮️

Leave a comment

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