Command Line Interface Guidelines
clig.dev163 points by subset 5 days ago
163 points by subset 5 days ago
> Use a pager (e.g. less) if you are outputting a lot of text.
If I wanted to use a pager I'd pipe the output to a pager, no pipe to pager means I want it all dumped to STDOUT. So annoying.
This is a common approach, CLI tools often use isatty [1] to check if the output fd is a TTY or not. Try running "git log" for example; if you have many commits, it will page through "less" or $PAGER only if it sees that you're on a real TTY; but if not, it will not. Try this:
git log # PAGER undefined; uses less
PAGER=/usr/bin/head git log # pages through head, you get 10 lines
PAGER= git log # PAGER is empty; does not page
git log | cat # output is not a TTY; does not page
(also, notice that git uses colors if the output is going to a terminal, and does not if not)I don't think I was ever bothered by the automatic paging through "less".
I'm bothered by it because whatever git command that I'm executing will put its output in a pager and when I quit the pager it's not there anymore. I want the output on the screen when I start typing the next command. Oh my God, this is such a frustrating pattern.
This is indeed an annoying, but solvable, less default. You can eg use `export LESS="-XF"` to change behaviour.
It took me way too many years before I set this as a default in my profile files.
The funny thing is. I kept missing this detail every time I read through git. Feels like I was blind to the whole concept of it
That's useful, although I don't know if I will enable it for generally or specifically for git.
I had the same issue specifically with git, for the same reason. You can configure git to not use a pager by setting core.pager to an empty string.
Then export PAGER= in your shell profile should help!
Unfortunately not all programs respect an empty PAGER vs an unset PAGER. It's more reliable to use PAGER=cat
Another example, which I find very annoying, is `systemctl status`; when the service has... either enough log lines or wide enough lines, idk, it will helpfully page them. So checking the status of a service randomly may or may not block your terminal. I cannot stress enough that I do not want to have to look at the screen and decide interactively whether or not I need to hit q before I can run another command.
It also annoys me very much, especially horizontally. Wrapped lines are much easier to copy, for example if you want to do a Web search.
Yeah no. If I wanted to use a pager, I'd use a pager.
I want the output visible on screen after I exit the pager. If I didn't want it on screen, I'd pipe it to a file and open that in a text editor. That will disappear when I close the file. As a bonus: I can then use grep without having to repeat whatever command I used.
Even more importantly: I want the command to behave identically regardless of whether its stdin or stdout is /dev/null or a terminal or a socket or a serial port or a pipe or anything else that it can read or write with. Try writing a script when the tools themselves change how they behave whether they're running in a script or in a terminal or elsewhere. It kinda sucks.
You make a reasonable point so I don't want to oppose it but just to offer more tricks in case some of you don't know them:
_less_ has a nice "&" command that is like "/" (search) but that filters instead like grep.
You can also pipe to an external command from less
> Try writing a script when the tools themselves change how they behave whether they're running in a script or in a terminal
But we smart coders. We think everything. Remind me later.
Right. IMO this is bad behavior. I have `pager=cat` in my git config to deal with it there and several other aliases (include --no-pager) for systemd related stuff that do it to.
100% - I want to use whatever pager I might prefer at the moment, this advice violates their core principle "you make programs that are modular enough to be recombined as needed"
I find it far more convenient for tools to take options to use a pager, rather than to having to manually type "| less" for every command that I want to behave nicely. Just behave nicely by default; if I want you to behave awkwardly, I'll let you know!
Define "nicely".
For example, many pagers don't work well when stdin is line-buffered (they assume they'll see individual key presses as they happen)
Pagers can also conflict with keys that the terminal emulator is using for a different purpose. This is especially annoying when the terminal emulator is using those keys for pager-like functionality in the first place!
An example of both of these is Emacs `shell-mode`.
(I personally set `PAGER=cat` in my Bash profile, which avoids most of these shenanigans)
For comfortable reading, I just had to figure out what on this webpage causes Chromium to drop subpixel rendering, and it's not this:
html { -webkit-font-smoothing: antialiased; }
it's this: #TableOfContents { backdrop-filter: blur(3px); }
A panel that overlaps content only on narrow screens disables subpixel rendering for the entire text. Thanks Chromium. Not an issue on Firefox, btw.> Whatever software you’re building, you can be absolutely certain that people will use it in ways you didn’t anticipate. Your software will become a part in a larger system—your only choice is over whether it will be a well-behaved part.
good advice
It is, but then why does the "guide" contradict it tens of times?
I'm in a charitable mood so I will just say that we rarely live up to our ideals.
Geez, what a lot of vague self-contradictory self-regarding opinionated advice.
Is terminal a world of “pure information’ or “a mess”? Both, it seems. Don’t dump “pages and pages of debug” text? Check. But do use boldface fonts “in a terminal independent way” and “try” to provide man pages. But because “some people” don’t know about them and “some platforms” don’t support them, provide help text by default.
Those “some” by the way are one: Windows. Do please enlighten us on the terminal-independent font control system that includes CMD.EXE.
How much is too much? Their showcase examples are more than 25 lines, more than the default size of a terminal window. The authors don’t give much weight to context, to the ability to look back on over the history of the interactive session. No, much better to splat the screen with 50 lines the user didn’t request, because helpful.
Oh, and don’t write a filter like cat(1). How the system is supposed to distinguish between reading from standard input and reading from standard input by mistake, they don’t say. Just quit and print a help message.
I have different advice: please don’t help. Please be quiet. Don’t suggest, don’t guess, dont offer, don’t assume. Just do. Do what the user said. If he wants something else, he’ll say so in time.
If you want —help to do something useful because you think your user is too dense to find the manual (condescension much?) then invoke it for him. Just maybe put a hint somewhere to press “q” to quit.
Virtually all the “help” I get on the command line is unasked-for and unwanted and unhelpful. If I fat-finger “ls”, I don’t need a suggestion to install lsof. The very last thing I want is 50 lines of noise obscuring what I was looking at before you so helpfully interrupted.
Do they really not use the following patterns, which I find quite common?
cat > file
cat >> file
But really, if you feel that you're waiting too long, can't you just ^C? Isn't it better to teach people rather than making the programs "helpful" (i.e. sometimes helpful, but more often annoying)? Display output on success, but keep it brief. Traditionally, when nothing is wrong, UNIX commands display no output to the user. This makes sense when they’re being used in scripts, but can make commands appear to be hanging or broken when used by humans. For example, cp will not print anything, even if it takes a long time.
It starts with talking about no indication of success, then switches to no indication of progress. I think they are pretty different.Also, what kind of "responsive" font sizing does this website have?
> A command is saying too little when it hangs for several minutes and the user starts to wonder if it’s broken.
Nope. For debugging the program, it is nice to have some debug tracing option, but if it's not the program's specification to produce output, it should not produce any. If the program's specification is that it produces a certain output after a certain calculation, then it shall not produce any other output, even if that calculation takes three weeks.
"cp -r from to" is supposed to be quiet until it's done or hits an error.
It would be nice to have an agreed-upon protocol for progress reporting. For instance, imagine of we had file descriptor 3 as "stdprogress". Programs could dump specially formatted messages there which the parent job control shell could intercept and turn into a progress display (with multiple rows for pipeline elements and such). It's not bad; but fluff like this can't be in band with the output, or at least not by default.
> It would be nice to have an agreed-upon protocol for progress reporting. For instance, imagine of we had file descriptor 3 as "stdprogress". Programs could dump specially formatted messages there which the parent job control shell could intercept and turn into a progress display (with multiple rows for pipeline elements and such).
Different thing, but I am reminded of the BSDs having SIGINFO.
> It's not bad; but fluff like this can't be in band with the output, or at least not by default.
FWIW, I would describe stderr as being out of band. IIRC that's how e.g. pv works; `<file.img pv | dd of=/dev/foo` sends data along stdout while giving status info to the user since stderr isn't being piped.
Isn't this basically how more modern "chatty" CLIs use stderr? Put all the nice progress bars and emojis behind `if isatty(2)`? I thought so anyway, but I'll admit I've never actually looked at what npm, uv, etc. do.
That's the right approach. Output goes to standard output, and user facing messages go to standard error. That way output can still be piped or redirected while the program talks to the user, and messages can also be suppressed by redirecting to the null device.
I've always been annoyed by the fact file descriptor 2 is called the "error" stream. Should have been called the "user" stream.
If a tool unconditionally produces diagnostics on standard error, whether there is an exceptional situation or not, I consider that poor behavior, contrary to the quiet tool philosophy from Unix.
Standard error is special output that the user should be able to somehow see (e.g. on a terminal) even if the regular output is redirected (as you note above).
That doesn't mean standard error is above the quiet tool guideline.
Or the "status" stream (you can still redirect it and it is useful sometimes).
Maybe the PowerShell folks were onto something when they implemented seven different output streams [1]. But then again most users are utterly confused by this and just dump everything to the information stream when they should be using the output stream, making their code near impossible to reuse in an idiomatic way.
[1] https://learn.microsoft.com/en-us/powershell/module/microsof...
> It would be nice to have an agreed-upon protocol for progress reporting.
That was initially USR1 and USR2 as process signals, well, at least across binutils and coreutils.
I just wish that UNIX architecture or POSIX would have been modernized since then, like with JSONL based process communication or similar things.
In Go I usually end up building my own JSONL protocol to marshal/unmarshal states between long running processes. Wish that could've been a POSIX standard.
> The concise help text should only include:
> - A description of what your program does.
> - One or two example invocations.
> - Descriptions of flags, unless there are lots of them.
> - An instruction to pass the --help flag for more information.
I disagree with this. The first 3 points should all unconditionally be in the man page of the program. In my ideal scenario, the jq short help message would omit everything after the "Usage" section except for the message to run the command with --help.
Yup, I agree. There seems to have been a trend recently to output a LOT on `cmd` (poor), even more on `cmd --help` (unpaged too: bad), and to not have a man page (terrible!). Every program should have a man page, a bare command should give usage (ideally, a single line), and --help should give a decent short overview (ideally less than a screen height or, failing that, automatically paged).
The Nix commands have a decent approach: passing `--help` will invoke `man` on the relevant manual (sub-commands tend to have individual man pages).
This is my design as well, didn’t realize Nix arrived at it. Help renders the man page.
Indeed, and the jq example invocation is way too trivial. It is better not to put it there.
It's awful UX for a program to know that you used it wrong, to be able to print the help there and then, and instead to tell you off and instruct you to ask politely (with --help). People who think that's okay in CLI world hate that kind of uppity-C3PO-servant behaviour from an LLM.
$ thing
usage: -lscekm4urytmvjpq9i8u3o54ernymctpwi8eruymgv
ah ah ah, <finger wag>, ask nicely for: --help
$ thing -h
ERROR! There is no option -h
What could you possibly want?
It's a mystery!
What if -h was rm -rf / did you even think of that?
Could have printed actual help here,
Filling up your screen with text anyway
and then you ask for --help and get five screens starting with "-a initialize line feed motor on electromechanical teletype output device from 1964" and on and on with options nobody uses mixed with the same priority amongst parameters everyone uses.This is yet another area where GUI programs on Windows 98[1] were better than today's Linux state of the art: pressing F1 opening a separate window with HTML-based help with contents, colours, fonts, text styles, hyperlinks, tables, pictures, an index, a search, is so vastly superior that it's unbelievable[2] that here we are in 2026 and Linux world is still infighting over whether a pager is a sign of weakness and low status, or whether showing help by default is a sign of a low quality programmer, for a problem that was completely solved during the Clinton Administration! (And presumably solved in a similar way earlier by classic MacOS, earlier by Amiga OS, and earlier at Xerox PARC).
Having a single output stream and trying to do everything on it is just bad design. Having the program guess what you want, or a toggle to change between working and being informative, or an environment variable to trigger the toggle, are downstream problems that can never please everybody, to avoid facing the bad design and dealing with it.
[1] https://en.wikipedia.org/wiki/Microsoft_Compiled_HTML_Help
[2] it's completely believable
You are comparing CLIs to GUIs. Anyways, there are GNU/Linux GUIs with the help manual (often still F1), but I would attribute the lack of manuals in some newer GUIs to a general trend against manuals, which can be seen well in Android/iOS apps, where everything has to be "engagement-optimised" and "flow-based" and with lots of blob illustrations, because otherwise people are too lazy to read even a page about how to use the app.
> You are comparing CLIs to GUIs.
Yes, and GUIs are winning again as they so often do, but also no I'm not; you can be in PowerShell CLI and run `Get-Help thing -Online` and have it launch a GUI web browser to the URI that `thing` declared as its help page. You can run `gci | out-gridview` and get a PowerShell object stream input to a GUI item picker. And on other machines you can run `ls | gvim -` and get a GUI that's reading from command line stdin.
But also no I'm not: look in this thread, part of the distinction is composability, part is interactive vs non-interactive use, part is whether the workflow gets paused, part is whether the output is desired by default or should be requested on demand, and part is whether the output stays on screen or is cleared. There can be no answer from all of these differing desires which pleases everyone, and that's a total failure of imagination.
CLIs can have jobs and background jobs and there's no official standard or de-facto standard for having the running job go into the background to display the help or paged output in the foreground. CLIs can have multiplexors, screen and tmux, and editors EMACS and VIM which have buffers and windows, and there's no official or de-facto standards for different output streams to open in either different buffers or split-windows, or in tiling window manager windows. There's options to export PAGER= but there's no standard way to indicate interactive vs non-interactive use, only trying to divine intent on a tool-by-tool basis by checking if a TTY is attached. Rustc and Elm and Odin and other language compilers have stretched to provide helpful error messages which make pretty good guesses what the user was trying to do, explain why it won't work, and suggest what to do instead, but shells and CLI tools in general have none of that.
Even without going to Alan Kay and Brett Victor extremes, Linux/Unix world has this widespread lack of imagination for what computers could be and do, and only this fruitless status bickering where computers are just another way to compete in the Suffering/Purity Olympics.
Colour addendum: If the user hasn't configured anything, don't use a colour other than red without checking whether the terminal has a light or dark background. Don't hardcode colours.
If you use the 14 standard colours (the plain old colours minus black and white), shouldn't they be set to something readable in the terminal emulator configuration? Of course, the 256-colour mode should not be used. And how do you check the background?
No, not simultaneously usable as foreground and background colours. For instance, on a light background, the default yellow is a good ‘highlighter’ background. If yellow is reconfigured to be dark enough to be usable as a foreground colour (i.e. brown), it's no longer usable as a background. You can't have both, and you can't assume the user has picked one or the other.
> And how do you check the background?
OSC 11
Why is this high x-height serif-based typography taking over SF aesthetics now?
I have used click/typer packages to build some python-based CLI programs. It's easy and user-friendly way to build, but the amount of time it takes for python to start the environment followed by the actual program to do anything is annoying.
Tangent, but anyone who's a fan of Typer, I'd massively recommend Cyclopts! It's essentially the same design, but takes advantage of some of the type features python introduced post Typer being written, to make things a little more consise.
I use python click too, and it helps me to focus on design before implementing.
I mean before asking AI to implement :)