Just: a command runner

10 min read Original article ↗
Benefits for LWN subscribers

The primary benefit from subscribing to LWN is helping to keep us publishing, but, beyond that, subscribers get immediate access to all site content and access to a number of extra site features. Please sign up today!

Over time, many Linux users wind up with a collection of aliases, shell scripts, and makefiles to run simple commands (or a series of commands) that are often used, but challenging to remember and annoying to type out at length. The just command runner is a Rust-based utility that just does one thing and does it well: it reads recipes from a text file (aptly called a "justfile"), and runs the commands from an invoked recipe. Rather than accumulating a library of one-off shell scripts over time, just provides a cross-platform tool with a framework and well-documented syntax for collecting and documenting tasks that makes it useful for solo users and collaborative projects.

just what it is

Using just has a few advantages over a collection of scripts or bending make to uses it may not be intended for. It's certainly easier to get started with than make for newer Linux users who haven't had the need to learn make previously. Generally, just is more ergonomic for what it does; it isn't a complete replacement for shell scripts or scripting, but it provides a better framework, organization, and user experience.

A task is defined by a recipe. A recipe is, basically a series of commands that are to be run by just. This is a simple recipe that tells me a bit about the operating system just is running on:

    os:
        @echo "This is a {{os()}} system running on {{arch()}} with {{num_cpus()}} logical CPUs."

Running "just os" produce the following output on my system:

    This is a linux system running on x86_64 with 16 logical CPUs.

The recipes for task are collected in a justfile and stored in the user's home directory or in the root directory of a project. Technically, these can be broken out into multiple files and imported into the main justfile, but let's not go down that rabbit hole just now.

Say an open-source project includes a justfile in its Git repository for doing common tasks, such as updating the changelog, running tests, or publishing a release; all a new contributor needs to do is run "just -l" to discover the tasks that are available, along with their documentation. Assuming, of course, someone has written the documentation—just is useful, but it's not magic.

Another advantage is that just is expressly designed for running tasks across multiple platforms and for collaborative use. For instance, it has built-in functions for determining and acting on system information (such as os() for the operating system), environment variables, error handling, and much more. A task can be written to use sh on Linux, ksh on macOS, and PowerShell on Windows, if needed. Recipes can have shared variables and settings, so one need not redefine $TMPDIR in each shell script for a project. A recipe can also have dependencies, specified similarly to makefile dependencies, which can be reused for multiple recipes as needed.

Users can invoke multiple recipes at once, and a recipe's dependencies can be skipped by using the --no-deps option when a user just wants to execute the primary task. For example, perhaps a task has a dependency on downloading an ISO image or other large file; that can be skipped if it has been run recently and the user knows there is not a newer one.

Project history

Casey Rodarmor created just in 2016. The project is inspired by make; in fact it was originally just a shell script that called make. But just is not meant to be a replacement for make or a build system in general; it is just a command runner. It is licensed, somewhat unusually, under the Creative Commons Zero (CC0) 1.0 Universal deed: essentially, it is public-domain code. Rodarmor wrote that he chose CC0 because he had "no desire to impose any restrictions whatsoever on anyone who wants to use my code." Nearly 200 people have made small contributions to just, but the bulk of development is Rodarmor's work.

Like many maintainers of open-source projects these days, Rodarmor does not tend to do major releases with many new features. Instead, there is a frequent stream of minor releases with a few new features and miscellaneous improvements, along with point releases with bug fixes. The most recent minor release, 1.43.0, came out in September, with the 1.43.1 bug fix release on November 12. According to the Just Programmer's Manual, there will never be a 2.0 release. Any backward-incompatible changes, "will be opt-in on a per-justfile basis, so users may migrate at their leisure". Any recipes written for a version of just since 1.0 should continue to work without breakage indefinitely.

Most popular Linux distributions provide just packages, though the version in a distribution's repository may be somewhat out of date given how frequently the project is released. Users can also snag the latest version using "cargo install just", or use one of the project's pre-built binaries.

Justfiles

The just command looks for a file named justfile with recipes in the current directory or those above it. The idea is that a user might include a justfile with each project, as well as a justfile in their home directory for miscellaneous recipes. So, if the present working directory is ~/src/project, the "just foo" command will tell the utility to look in ~/src/project for a justfile. If it is not found there, then just will look in ~/src, and ~ for a justfile. Technically it will keep looking in /home and such, but one hopes users are not placing their recipe files there. One of the features added in 1.43.0 is a --ceiling option; this lets the user specify the "top" directory where just should stop looking for a justfile.

Note that the name is case-insensitive; just will accept Justfile, justfile, JUSTFILE, and .justfile if one prefers to hide configuration files. Even JuStFiLe will work, though it would be an affront to good taste. Naturally, there is a justfile in the just repository that has recipes for building project documentation, running demos, creating the just man page, updating Rust, and more.

Once just finds a justfile, it stops looking; this means that, for example, if one has a justfile in ~/ and another in ~/project/foo, running just in the ~/project/foo directory will usually not find any recipes above that directory. This can be changed by adding the set fallback directive in a recipe file. That way, if a user runs "just foo" and there is no recipe foo to be found in the first justfile, it will keep looking in the directories above it.

The syntax for just is inspired by make, but Rodarmor wanted to avoid what he calls the idiosyncrasies of make: such as the need to have .PHONY targets when there is a file with the same name as a make target (e.g., "clean") in the directory. Recipe lines must be indented at least one level from the recipe name, but users have a choice of spaces or tabs. Note that each recipe does have to be consistent: just does not allow indenting one line of a recipe with spaces, and another line with tabs.

Here is a simple recipe, called pub I use to update my Hugo-based static site:

    # update dissociatedpress.net
    [no-cd]
    pub:
        hugo
        rsync -avh --progress -e ssh /home/user/src/dissociatedpress/public/ \
        user@dissociatedpress.net:/path/to/www/html
        w3m https://dissociatedpress.net/

When I run "just pub", just steps through the recipe and runs each line in a separate shell. It runs hugo in the directory where the justfile is invoked; the default is for just to run a recipe in the directory where the justfile is stored, but [no-cd] instructs it to run the recipe in the current directory instead. It will rsync the contents to the server, and then open the site in w3m so I can verify that the site was updated.

Running "just -l" will list all of the available recipes in a justfile. If there is a comment above the recipe, it will be displayed:

    $ just -l
    pub #update dissociatedpress.net

The "just --choose" command will display an interactive menu that allows the user to choose the just recipe to run. This makes use of the fzf command-line fuzzy finder by default; it will work without it, but just prints a warning if it cannot find fzf. Most distributions should have fzf available.

Polyglot

Typically, just uses "sh -cu" to execute the commands in a recipe, unless configured otherwise. That can be changed with the "set shell" setting in a recipe, such as this:

    set shell := ["fish", "-c"]

One of just's more interesting features is its support for writing recipes in many programming languages, so users are not limited to shell interpreters; these are called shebang recipes. Basically, a user can write a recipe using JavaScript, Perl, Python, Ruby, and just about any other scripting language. A shebang recipe is saved to a temporary file by just and then run using the interpreter specified:

    python:
        #!/usr/bin/env python3
        print('GNU Terry Pratchett')

Running "just python" saves the Python script to /tmp to be executed by python3. The temporary directory can be specified in several ways if one wants to avoid placing scripts in a world-readable directory. Users can, of course, substitute their favorite languages; a justfile can contain shebang recipes in multiple languages with no problem.

Recipes can use conditional expressions, take variables from the command line, and much more. The programmer's manual does a fine job of laying out all of just's features and how to use them. There is also a cheatsheet for just syntax and grammar; it is a useful thing to have bookmarked if one starts to use just.

Simple and useful

The project has a Discord channel for discussion and support. There is a GitHub issue that Rodarmor uses to solicit user feedback when he is considering major new features, such as new recipe types. He suggests that users subscribe to that issue if they would like to provide input on features in the future. Sadly, GitHub does not offer RSS feeds for issues, so the only way to keep up with that is to have a GitHub account and use its subscribe feature.

I had heard of just but had not bothered to try it out until I started testing the Project Bluefin distribution in 2023. The project uses just, aliased to ujust, to use the system-wide justfiles for a number of administrative tasks or to toggle user features on and off. For example, Bluefin has "ujust benchmark" to run a one-minute benchmark test with the stress-ng utility, and "ujust setup-luks-tpm-unlock" to enable automatic disk unlocking. The justfiles for the distribution can be found on GitHub and the project also maintains a justfile with recipes for building the distribution images. Those files provide some interesting examples of just's capabilities.

Since then, I've been using just rather than shell scripts and such for automating little things (or medium-sized things...), and converting old scripts to recipes. It's one of the first tools I install on a new system, and it makes everything just a bit easier.