|
|
Log in / Subscribe / Register

Just: a command runner

By Joe Brockmeier
December 3, 2025

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" produces the following output on my system:

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

The recipes for tasks 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.



to post comments

just

Posted Dec 3, 2025 17:25 UTC (Wed) by andrewsh (subscriber, #71043) [Link]

Just what is it that you want to do?
We want to be free. And we want this binary loaded.

String substitution quoting

Posted Dec 3, 2025 17:41 UTC (Wed) by fishface60 (subscriber, #88700) [Link]

Does this improve over the flaw that make's interpolation semantics make it hard to handle whitespace in shell commands safely?

I feel like the {{foo}} interpolation syntax being demonstrated inside shell commands and the shell being configurable implies it just substitutes the text verbatim since effectively parsing the shell commands to identify the substitution context to determine how to safely quote the value would be quite difficult.

https://just.systems/man/en/functions.html lists a quote function that could be used to safely quote interpolations

e.g. the example in the article becomes:
os:
    @echo "This is a "{{quote(os())}}" system running on "{{quote(arch())}}" with "{{quote(num_cpus())}}" logical CPUs."
but the documentation doesn't suggest doing this, so either there's more subtlety to the substitution syntax that I'm unaware of or safely escaping inputs has room for improvement.

Sounds very useful

Posted Dec 3, 2025 17:51 UTC (Wed) by archaic (subscriber, #111970) [Link]

$ wc -l .bashrc*
  431 .bashrc
  507 .bashrc_aws
  237 .bashrc_docker
  180 .bashrc_git
   17 .bashrc_go
  101 .bashrc_libvirt
   25 .bashrc_misc
   70 .bashrc_puppet
   33 .bashrc_ruby
  101 .bashrc_vagrant
   86 .bashrc_vm
  442 .bashrc_work
 2230 total
Seems this might be quite handy for me. ;)

Learn all targets of any Makefile

Posted Dec 3, 2025 21:19 UTC (Wed) by alx.manpages (subscriber, #145117) [Link] (7 responses)

It's possible to learn all the available targets of a Makefile.
make -R -p -n \
	| grep '^\.PHONY:' \
	| tr ' ' '\n' \
	| grep -v '^\.PHONY:' \
	| sort;
If the makefile includes a dummy target that does nothing at all --something I include in my Makefiles, as it's very useful for debugging the Makefiles themselves--, you should run that target to avoid some spurious output. Such a target can be specified as:
.PHONY: nothing
nothing:;
and then use it as
make -R -p -n nothing \
	| grep '^\.PHONY:' \
	| tr ' ' '\n' \
	| grep -v '^\.PHONY:' \
	| sort;
In the Linux man-pages project, this prints:
$ make -R -p -n nothing \
	| grep '^\.PHONY:' \
	| tr ' ' '\n' \
	| grep -v '^\.PHONY:' \
	| sort;
make: warning: undefined variable 'GNUMAKEFLAGS'
all
build
build-all
build-catman
build-catman-eqn
build-catman-grotty
build-catman-troff
build-ex
build-ex-cc
build-ex-dir
build-ex-ld
build-ex-src
build-fonts
build-fonts-download
build-fonts-tinos
build-html
build-html-post-grohtml
build-html-troff
build-man
build-man-nonso
build-man-so
build-pdf
build-pdf-book
build-pdf-pages
build-pdf-pages-eqn
build-pdf-pages-gropdf
build-pdf-pages-troff
build-pre
build-pre-preconv
build-pre-tbl
build-ps
build-ps-eqn
build-ps-grops
build-ps-troff
check
check-catman
check-catman-col
check-catman-grep
clean
dist
dist-tar
dist-z
dist-z-bz2
dist-z-gz
dist-z-lz
dist-z-xz
distcheck
distcheck-diffoscope
distcheck-dist-tar
distcheck-tar
help
install
install-all
install-bin
install-html
install-man
install-man1
install-man2
install-man2const
install-man2type
install-man3
install-man3attr
install-man3const
install-man3head
install-man3type
install-man4
install-man5
install-man6
install-man7
install-man8
install-manintro
install-pdf
install-pdf-book
lint
lint-c
lint-c-checkpatch
lint-c-clang-tidy
lint-c-cppcheck
lint-c-cpplint
lint-c-iwyu
lint-man
lint-man-blank
lint-man-dash
lint-man-mandoc
lint-man-poems
lint-man-quote
lint-man-so
lint-man-tbl
lint-man-ws
lint-sh
lint-sh-shellcheck
nothing
uninstall
uninstall-bin
uninstall-html
uninstall-man
uninstall-man1
uninstall-man2
uninstall-man2const
uninstall-man2type
uninstall-man3
uninstall-man3attr
uninstall-man3const
uninstall-man3head
uninstall-man3type
uninstall-man4
uninstall-man5
uninstall-man6
uninstall-man7
uninstall-man8
uninstall-manintro
uninstall-pdf
uninstall-pdf-book
Admittedly, it's not as simple as 'just -l', but it can be useful for getting some help with arbitrary Makefiles when needed. Maybe make(1) could add an --list-phony-targets flag implementing this behavior...

Learn all targets of any Makefile

Posted Dec 3, 2025 23:44 UTC (Wed) by legoktm (subscriber, #111994) [Link] (6 responses)

We have a similar command implemented in awk in SecureDrop that we copy-paste around to all our repos: https://github.com/freedomofpress/securedrop/blob/6e3e49d...

But the just tooling seems much nicer, maybe it's finally time for us to switch over :)

Learn all targets of any Makefile

Posted Dec 4, 2025 0:03 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (4 responses)

> We have a similar command implemented in awk in SecureDrop that we copy-paste around to all our repos: https://github.com/freedomofpress/securedrop/blob/6e3e49d...

Nice! I decided to print the command, instead of running it in 'make help':

<https://git.kernel.org/pub/scm/docs/man-pages/man-pages.g...>

I think that helps users realize how they can do the same in their own projects.

> But the just tooling seems much nicer, maybe it's finally time for us to switch over :)

I suspect make(1) is more versatile/expressive than just(1), even if just(1) might be good enough for simple cases. I've had to make use of some modern make(1) features for certain things, and I'd probably miss them if I were to switch to some alternative. (I'm even waiting for the next release of make(1) for removing some temporary workaround at the moment.)

For example, the article says just(1) doesn't need to have .PHONY targets; but that probably means they either have different issues, or less features. Otherwise, I'm not sure how you can get rid of .PHONY without having new issues. I'm not sure I'd trust a less expressive language for the tasks of make(1).

Learn all targets of any Makefile

Posted Dec 4, 2025 0:58 UTC (Thu) by daroc (editor, #160859) [Link] (1 responses)

Just gets rid of .PHONY by unconditionally re-running tasks, even if a file with the same name already exists — which is why Joe calls it "not a replacement for a build system". This makes understanding what tasks are going to be run much simpler, but it also means that if you want to add some amount of caching/short-circuiting logic you need to write that out explicitly yourself.

As I said to Joe when we were reviewing this article before publication: I think that if you're already comfortable with make's advanced features, there isn't a pressing reason to switch. He called that a "load-bearing if" and pointed out that many users don't know about, or need, many of make's advanced features.

I think Just is a great fit for projects that need something slightly more structured than a directory full of shell scripts; I think it's a less good fit for projects with extensive custom Makefiles.

Learn all targets of any Makefile

Posted Dec 4, 2025 11:28 UTC (Thu) by alx.manpages (subscriber, #145117) [Link]

Yup, I think I agree with all of this.

In the past, I've replaced some scripts with simple Makefiles with only-.PHONY targets, which essentially is the same as this article is suggesting. I think Makefiles are quite undervalued in general, while they are indeed quite powerful and useful. :)

Learn all targets of any Makefile

Posted Dec 4, 2025 10:06 UTC (Thu) by pbonzini (subscriber, #60935) [Link] (1 responses)

> Nice! I decided to print the command, instead of running it in 'make help':

Why not wrap it in 'make list-targets', etc.?

Learn all targets of any Makefile

Posted Dec 4, 2025 11:32 UTC (Thu) by alx.manpages (subscriber, #145117) [Link]

Hmmmm, so, I originally did print the list within 'make help' (among other things). That list was rather huge, so it was a bit noisy when a user wanted some initial help, which is why I decided to just print the command and let the user run it if it wants to. That had the side-effect of teaching users that they could run the same script with other makefiles to learn about their targets.

However, I think it would make sense to do both. I could show the script in 'make help', and then actually run it with some more specific target, such as maybe 'make help-list-targets'.

Thanks for the suggestion!

Learn all targets of any Makefile

Posted Dec 4, 2025 9:28 UTC (Thu) by mgedmin (guest, #34497) [Link]

I learned about this trick after seeing it in https://marmelab.com/blog/2016/02/29/auto-documented-make..., and now I copy around a help.mk (https://github.com/mgedmin/python-project-skel/blob/maste...) into my every project that uses a Makefile, so I can have a nice pretty colorful 'make help' extracted from documentation comments in the Makefile itself.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 12:02 UTC (Thu) by Nikratio (subscriber, #71966) [Link] (21 responses)

>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.

It seems to me that the only way to make this work on a case-sensitive filesystem is to either stat() every permutation of upper/lowercase characters, or to fully read the contents of every directory just to figure out what file to open. Both strikes me as a terrible way to write software, so I wonder if there's a third method that I missed, or what motivated this feature (given the awkwardness of its implementation).

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 12:16 UTC (Thu) by jaseemabid (subscriber, #119335) [Link] (20 responses)

> It seems to me that the only way to make this work on a case-sensitive filesystem is to either stat() every permutation of upper/lowercase characters, or to fully read the contents of every directory just to figure out what file to open. Both strikes me as a terrible way to write software, so I wonder if there's a third method that I missed, or what motivated this feature (given the awkwardness of its implementation).

For a task runner, running `ls | grep --ignore-case Justfile` once in each parent directory seems like a very reasonable thing to do. It's not that performance critical.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 12:22 UTC (Thu) by Nikratio (subscriber, #71966) [Link] (16 responses)

>> It seems to me that the only way to make this work on a case-sensitive filesystem is to either stat() every permutation of upper/lowercase characters, or to fully read the contents of every directory just to figure out what file to open. Both strikes me as a terrible way to write software, so I wonder if there's a third method that I missed, or what motivated this feature (given the awkwardness of its implementation).

> For a task runner, running `ls | grep --ignore-case Justfile` once in each parent directory seems like a very reasonable thing to do. It's not that performance critical.

That method reads the full directory contents (an unbounded number) in multiple directories (also unbounded in number), just to open a file with a known name. In my opinion, this is absolutely not a reasonable way to do things, no matter if it's performance critical or not.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 12:52 UTC (Thu) by AntiISO (guest, #179626) [Link] (4 responses)

I checked the code, and it does indeed use `std::fs::read_dir()` to find candidates. This is problematic for me not just because of the *unbounded* performance considerations you pointed out, but for the fact that the order of matching is nondeterministic!

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 14:53 UTC (Thu) by marcH (subscriber, #57642) [Link] (3 responses)

> > 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.

> I checked the code, and it does indeed use `std::fs::read_dir()` to find candidates. This is problematic for me not just because of the *unbounded* performance considerations you pointed out, but for the fact that the order of matching is nondeterministic!

The non-determinism part is unquestionably a bug. A minor one but still a bug. The alternatives should either be sorted in any well-defined order, or the tool could just error when more than one is found. The latter is probably the safer and better choice.

How many more case-insensitivity bugs do we need before people understand it is always more complex than you think and generally a bad idea?

In a graphical interface that is in direct contact with the user: sure, why not. But not in a command line, filesystem itself or when programming. The former has a lot of interaction and feedback options, whereas problems in the latter are generally buried under layers of indirections, abstractions and wrapper scripts. To be fair "just" seems located somewhere in the middle. But it's still on the side of "advanced" users who generally don't need and don't ask for case insensitivity.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 17:33 UTC (Thu) by zdzichu (subscriber, #17118) [Link] (2 responses)

Seriously, if you have `Justfile` and `justfilE`, just clean up your project directory. Wondering which file will be used is a waste of time and mental energy.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 17:43 UTC (Thu) by jzb (editor, #7867) [Link] (1 responses)

FTR, if you have two competing files in a directory then just throws a "Multiple candidate justfiles found" error. So if that happens, you'll have to clean things up.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 18:39 UTC (Thu) by AntiISO (guest, #179626) [Link]

Actually, looking at the code again, I was wrong in any case. Since in local dir searches, the candidates are stored in a BTreeSet. So even if multiple matches didn't trigger an error (they do as you pointed out), the order is not nondeterministic anyway.

Global search is also done path by path:
{xdg_config_dir}/just/justfile
~/.config/just/justfile
~/justfile
~/.justfile

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 15:24 UTC (Thu) by Wol (subscriber, #4433) [Link] (10 responses)

> That method reads the full directory contents (an unbounded number) in multiple directories (also unbounded in number), just to open a file with a known name. In my opinion, this is absolutely not a reasonable way to do things, no matter if it's performance critical or not.

It can also depend on the file system. There's a whole bunch of optimisations the file system can perform behind your back if the structure is treed or hashed or whatever. For example, treat JustFile and justfile as two separate files, but tree/hash them both as JUSTFILE and treat the resultant collision as exactly that - just another collision. So after the scan has scanned past JUSTFILE, you know you're not going to find anything else.

Cheers,
Wol

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 15:49 UTC (Thu) by Nikratio (subscriber, #71966) [Link] (9 responses)

>> That method reads the full directory contents (an unbounded number) in multiple directories (also unbounded in number), just to open a file with a known name. In my opinion, this is absolutely not a reasonable way to do things, no matter if it's performance critical or not.

> It can also depend on the file system. There's a whole bunch of optimisations the file system can perform behind your back if the structure is treed or hashed or whatever. For example, treat JustFile and justfile as two separate files, but tree/hash them both as JUSTFILE and treat the resultant collision as exactly that - just another collision. So after the scan has scanned past JUSTFILE, you know you're not going to find anything else.

I'm not sure I follow. The filesystem does not know you are looking for something called `justfile` (and variations), because `just` simply requests a full list of directory contents. So no optimizations there.

You could encode filesystem-specific knowledge into `just` (e.g. "if looking at filesystem X, assume that getdir() returns values with specific ordering guarantees"), but I think that's simply going even further into the wrong direction (the assumption may be wrong in the future, there can be behaviour changes depending on what filesystem is in use).

And I'm still not sure what the advantages of case-insensitivity are here. Does anyone really need the flexibility to use a `jUsTFilE` instead of `justfile`?

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 7, 2025 2:56 UTC (Sun) by NYKevin (subscriber, #129325) [Link] (8 responses)

> I'm not sure I follow. The filesystem does not know you are looking for something called `justfile` (and variations), because `just` simply requests a full list of directory contents. So no optimizations there.

Ironically, it is possible to directly ask the filesystem to perform such a search on Windows... but then this functionality is unnecessary because NTFS is case-insensitive in the first place.

(See the FindFirstFile/FindNextFile/etc. functions for details.)

Presumably, the WIne folks implemented all of that using exactly the same "search the whole directory one file at a time" strategy. There does not appear to be much alternative on Unix-ish OS's.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 11, 2025 10:40 UTC (Thu) by nye (subscriber, #51576) [Link] (7 responses)

> NTFS is case-insensitive in the first place

No it isn't. Maybe this is why that functionality exists?

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 11, 2025 11:59 UTC (Thu) by knewt (subscriber, #32124) [Link] (4 responses)

>> NTFS is case-insensitive in the first place
> No it isn't. Maybe this is why that functionality exists?

NTFS is...... an interesting case. NTFS itself has always been able to do case sensitivity. To do so it has to be requested though. Even without, NTFS is case preserving mind you.

And the Windows subsystem, in theory, can indeed do case sensitivity, with a 'POSIX_SEMANTICS' flag when opening a file. However, there is a global registry key that controls if that flag works, and that flag by default forces case insensitivity within the Windows subsystem (ever since XP iirc).

Then came along WSL1. Within this, file access from Linux processes did not go via the regular Windows subsystem. There were NT drivers that implemented Linux syscalls and either translated to an equivalent NT kernel call, or reimplemented directly. And case sensitivity was requested on all file operations.

This causes interop issues however. It would allow you to create files on regular Windows folders (through the drive mounts) that differed only by case, preventing you from properly accessing them on the Windows side of things.

To solve this, in time they added a new per-directory case sensitivity feature. This aiui is implemented at the NTFS level itself. If set for a directory, all operations within that directory (but only that directory, it's not inherited) become case sensitive. At the same time, WSL1 was changed to set this flag on any directories it created, and stop forcing case sensitivity otherwise.

Of course, WSL1 has now been replaced with the virt-based WSL2 which works completely differently, but iirc WSL1 is still available if needed.

[all this largely from memory, if I've got anything wrong happy to be corrected]

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 11, 2025 13:28 UTC (Thu) by nye (subscriber, #51576) [Link] (3 responses)

> And the Windows subsystem, in theory, can indeed do case sensitivity, with a 'POSIX_SEMANTICS' flag when opening a file. However, there is a global registry key that controls if that flag works, and that flag by default forces case insensitivity within the Windows subsystem (ever since XP iirc).

Yes, the FILE_FLAG_POSIX_SEMANTICS flag to CreateFile only works when HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\kernel\ObCaseInsensitive is set to 0.

That's only about creating files though. Regardless of that setting, an NTFS filesystem might have preexisting files whose names differ only in case - maybe they were created on a system with that set, or on Linux, or using SFU/Interix, or WS1, or something else.

What I don't know is how exactly the high-level win32 APIs work when interacting with files created in the POSIX namespace. I had a look for options to "directly ask the filesystem to perform [a case insensitive] search", but all I can find is the opposite: FIND_FIRST_EX_CASE_SENSITIVE. So I think this might have been a mistake? Certainly *that* flag makes sense even on say FAT32 which AFAIK cannot store two files whose name differs only in case.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 12, 2025 0:10 UTC (Fri) by Fowl (subscriber, #65667) [Link] (2 responses)

CreateFile on Windows is also used for opening files (ie. think of it as CreateFile*Handle*), so FILE_FLAG_POSIX_SEMANTICS also applies to opening files case sensitively.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 12, 2025 0:36 UTC (Fri) by dskoll (subscriber, #1630) [Link] (1 responses)

I'm not familiar with Windows, but can you create two files named file.txt and FILE.TXT in the same directory? If so, what happens if you try to open one of them for reading, but using the case-insensitive form of open?

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 12, 2025 19:25 UTC (Fri) by mathstuf (subscriber, #69389) [Link]

I've done a similar experiment with SMB mounts from Unix machines years ago (Windows 7, before Windows 8). You can create files named things like `COM1` which are otherwise reserved in Windows. At least in Explorer, this mangles the name to some string, say "8x8wkd" (it was meaningless gibberish to me, but it was stable). If you also create a file named "8x8wkd", you could select them and such in Explorer (I wonder what Properties showed though…) However, whichever one you selected and deleted, the one actually named "8x8wkd" would go away first. Didn't test renaming either. I imagine one would get similar behaviors with files differing by case: renders OK, but file ops can get very confused.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 11, 2025 12:02 UTC (Thu) by Wol (subscriber, #4433) [Link] (1 responses)

In what sense is it case sensitive? AIUI it's "case insensitive and case preserving" - it's the devil's own job to fix it if you got the case wrong when saving as I remember it.

IME it doesn't care what case you use to *refer* to the file, but as far it is concerned, the correct case is whatever you originally saved it as.

Cheers,
Wol

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 11, 2025 22:07 UTC (Thu) by rschroev (subscriber, #4164) [Link]

> it's the devil's own job to fix it if you got the case wrong when saving as I remember it.

That used to be the case. You first had to rename to something that differs in more than just the case (e.g. add an extra letter), than rename to what you actually wanted.

But that's not the case anymore. In Windows 10, and possibly earlier, a simple rename works as you expect.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 14:33 UTC (Thu) by Karellen (subscriber, #67644) [Link] (2 responses)

Still, it's probably worth using `ls -U` there, especially if there might be some highly-populated directories in any of the places it's likely to ever be run.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 4, 2025 14:54 UTC (Thu) by dskoll (subscriber, #1630) [Link] (1 responses)

So if a directory contains justfile and JustFiLe, then which one gets used with -U? It seems to me allowing the file to be case-insensitive is a terrible idea.

At least GNU make only checks for a limited number of possibilities: GNUmakefile, makefile and Makefile in that order.

Case Insensitive filename lookup sounds like a bad idea

Posted Dec 5, 2025 8:53 UTC (Fri) by taladar (subscriber, #68407) [Link]

Not only that but now every other tool that might interact with a Just config file needs to check all those names too.

Is it possible to white-list set of directories for the auto-discovery?

Posted Dec 5, 2025 11:49 UTC (Fri) by gray_-_wolf (subscriber, #131074) [Link] (1 responses)

If some (malicious) project provides Justfile, and I happen to run the mentioned "ujust benchmark", will it pick up the "benchmark" command from the repository? If I read the article right that does seem to be the case. It seems convenient, but also somewhat risky. Is it possible to white-list Justfiles and subtrees from which to allow loading of Justfiles?

Is it possible to white-list set of directories for the auto-discovery?

Posted Dec 5, 2025 14:12 UTC (Fri) by daroc (editor, #160859) [Link]

You can specify which justfile to use with the -f option. ujust is aliased to "just -f /etc/something", so it won't pick up any justfiles from the rest of the filesystem.

Like prepending "." and ".." to the PATH?

Posted Dec 10, 2025 22:59 UTC (Wed) by janfrode (subscriber, #244) [Link]

Doesn't it seem a bit insecure to pick up .justfile from just about anywhere up the file system? Put a trap in /tmp/.justfile and wait for someone to run "just" somewhere below /tmp/. Has this been considered ?


Copyright © 2025, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds