Blog Infos
, ,
Posted by: Jean-Michel Fayard

Life is too short for Bash programming

Being able to write your own command-line tools is a great skill to have: automate all the things!

Unfortunately, writing CLI tools is still most often done with Bash.

My strong opinion — weakly held — is that there are only two good kinds of Bash scripts:

  • the ones that are five lines long or less
  • the ones that are written and maintained by others

Fortunately, retiring Bash is pretty simple: you just have to use your favorite programming language instead!

I’ve played around with Kotlin Multiplatform and not only built a cool CLI tool with it, but I’ve packaged it as a template project that you can leverage to get started faster!

Hier muss noch ein Link eingebaut werden
git-standup, Kotlin Multiplatform edition

git-standup is a smart little tool for people like me who often panic before the stand-up. What the hell did I do yesterday? That command refreshes my mind by browsing all git repositories and printing the commits that I made yesterday. Try it out, it may well become part of your workflow.

Clone the repo and install git-standup

git clone
cd kotlin-cli-starter
./gradlew install
git standup

./gradlew install will install the tool as a native executable.

There are two more options available:

  • ./gradlew run will run it on the JVM
  • ./gradlew jsNodeRun will run it on Node.js

Same common code, multiple platforms.

Modern Programming Practices

A traditional Bash script contains

  • a syntax where you can’t remember how to do an if/else of a for loop
  • unreadable code due to code golfing : ztf -P -c -D -a
  • no dependency management (apart from an error message: ztf not found)
  • one file that does everything
  • no build system
  • no IDE support (vim should be enough for everybody)
  • no unit tests
  • no CI/CD

By choosing a modern programming language like Kotlin instead, we get to have

  • a modern syntax
  • readable code
  • separation of concerns in multiple functions, files, class and packages
  • a modern build system like Gradle
  • dependency management (here with gradle refreshVersions)
  • a modern IDE like JetBrains IntelliJ
  • unit tests. Here, they are run on all platforms with ./gradlew allRun
  • CI-CD. Here, the unit tests are run on GitHub Actions. See workflow

That may sound obvious, yet people still write their CLI tools in Bash, so it’s worth repeating.

Why support multiple platforms?

The three platforms have different characteristics, and by making your code available on all platforms, you can decide which one works better for you:

  • The JVM has a plethora of available libraries, superb tooling support ; it lacks a package manager and is slow to start
  • A native stand-alone executable starts very fast, it can be distributed by Homebrew for example — I make a Homebrew recipe
  • There are also a plethora of libraries on Node.js, and packaging it is easy with npm-publish. My version of git-standup is available at and I find it fascinating that I could do so without writing a line of JavaScript.

But mostly it’s a good opportunity to learn Kotlin/Multiplatform, a fascinating technology, but one that can also be complex. By writing a CLI tool with Kotlin/Multiplatform, you start with something simple.

What is it like to write Kotlin Multiplatform code?

It feels like regular Kotlin once you have the abstractions you need in place, and all you need is to modify commonMain.

It gets interesting when you have to connect to platform-specific APIs with the expect/actual mechanism.

For example, I needed to check if a file exists:

// commonMain
expect fun fileIsReadable(filePath: String): Boolean
view raw expect.kt hosted with ❤ by GitHub

Implementing this on the JVM is straightforward

// jvmMain
actual fun fileIsReadable(filePath: String): Boolean =
view raw jvm.kt hosted with ❤ by GitHub

But how to implement this with Kotlin Native?

Since Kotlin Native gives us access to the underlying C APIs, the question is: how would they do that in C?

Let’s google “how to check a file is readable C”.

The first answer is about the access system call.

We read the friendly manual with man access:

Which leads us directly to this implementation:

// nativeMain
actual fun fileIsReadable(filePath: String): Boolean =
access(filePath, R_OK ) == 0
view raw native.kt hosted with ❤ by GitHub

Interesting, strange feeling that we are writing C in Kotlin.

It works, but wait, there is a better solution!

Job Offers

Job Offers

    Android Entwickler (m/w/d)

    Augsburg, Berlin, Düsseldorf, Essen, Frankfurt, Leipzig, München und Münster
    • Full Time
    apply now