Published on 2026-03-04.
Super short article today.
At work we maintain an OpenAPI (formerly known as Swagger) specification of our APIs. Then we use generators we produce client code in various languages.
I noticed that Swift was missing from the list so I wanted to add it.
Just to be sure that the generated code works, we have a step in CI that builds it.
Thus I wrote, or rather extended, a shell script like this:
Shell#!/bin/sh swift () { echo "Building Swift..." (cd "./swift" && swift build) } swift
Now dear reader, take a guess at what is wrong here. Any ideas? Just a hint: the (marvelous) shellcheck linter does not bat an eye here. Everything is fine...
Now to my surprise, the script got stuck in infinite recursion, until it finally errors when trying to enter the non-existent directory swift. If you go deep enough in the file system, ultimately you reach the end and cd fails.
That's because when the shell sees swift build, it interprets it as: call the shell function called swift with the argument build. Whereas I intended to call the CLI command swift.
The fix is either to rename the shell function to something else, like run_swift, or to use the exec builtin to disambiguate:
Diff--- test.sh 2026-03-04 17:50:38 +++ test_fixed.sh 2026-03-04 17:50:34 @@ -4,7 +4,7 @@ swift () { echo "Running Swift..." - (cd "my-swift-project" && swift build) + (cd "my-swift-project" && exec swift build) } swift
Now, any language worth its salt will warn you that this is infinite recursion:
Rustfn main() { println!("hello"); main(); [...] }
The compiler warns us:
Shell$ cargo c warning: function cannot return without recursing --> src/main.rs:1279:1 | 1279 | fn main() { | ^^^^^^^^^ cannot return without recursing ... 1282 | main(); | ------ recursive call site | = help: a `loop` may express intention better if this is on purpose = note: `#[warn(unconditional_recursion)]` on by default
Ahem... While writing this article and testing with a few languages, I noticed Go does not warn us in this case... However third-party linters like staticheck do:
Shell$ go build /tmp/main.go $ staticcheck /tmp/main.go /tmp/main.go:7:2: infinite recursive call (SA5007)
So that will be my advice: the shell is fine for one-liners. Anything else, just use your favorite general purpose programming language (and accompanying linters), it'll be simpler, better, faster, stronger.
If you enjoy what you're reading, you want to support me, and can afford it: Support me. That allows me to write more cool articles!
This blog is open-source! If you find a problem, please open a Github issue. The content of this blog as well as the code snippets are under the BSD-3 License which I also usually use for all my personal projects. It's basically free for every use but you have to mention me as the original author.