• by neilv on 12/12/2024, 10:35:08 PM

    Additional suggestions:

    * Respect the user's default foreground and background color. Don't change them without good reason.

    * If you use colors, make them legible regardless of what the default background and foreground colors are, and regardless of the terminal's color map.

    * Don't use color as the only indication of something. The user's terminal might not display it, and it probably won't be preserved in copy&paste into notes.

    * Use emoji only judiciously, if at all. Similar with gratuitous non-ASCII characters. It doesn't display everywhere, it doesn't paste well everywhere, and emoji can be a bit much when copy&pasted into some notes.

    * In a scrolling (non-full-screen) stdout-ish output, don't delete important information that you showed temporarily. For example, hiding warnings or filenames compiled, to display a green checkmark for done. For another example, clearing the screen of Web app build information (including package security warnings!), to display a message that it's running in dev mode, is also not wanted. People might want to see that information, or copy&paste it into notes.

    * If you went full angry fruit salad with your command line program, because it's your baby, and you're having fun hamming it up, that's fine, but please provide an easy preference setting for people to opt out of that. Your program is probably only one of many things on user's workstation display, where other programs might be using color and visuals more meaningfully, so animated throbbing red explosions for the code reformatter is a bit much.

  • by bobbiechen on 12/12/2024, 7:40:14 PM

    Nice writeup. Since she mentioned how hard it is to learn these conventions, I'll plug my preferred reference when thinking about CLIs specifically (rather than TUIs and REPLs) - the Command Line Interface Guidelines https://clig.dev

    It does include the blog post's rules on exiting on Ctrl-C, accepting `-` for stdin, disabling color in pipes, and much more.

  • by matheusmoreira on 12/12/2024, 7:37:52 PM

    > programs should print “regular” output to stdout and errors to stderr

    This is really important. I'd like to expand on this.

    Standard output is for the data the program was asked to produce, no more and no less. If user asked for some JSON data, standard output should contain that exact JSON object and absolutely nothing else.

    Standard "error" is actually a misnomer. It should have been called the standard user stream. Anything meant for the user to read on the terminal is supposed to go there. Error messages are of course included in that set but so are status messages and verbose output.

    This ensures the output of programs can be piped into other programs seamlessly. Non-output data still gets sent to the terminal or redirected somewhere else.

    Would have been great if programs were able to easily create new terminal-connected file descriptors for specific purposes. They could document those numbers in their manuals just like they document exit codes. Then users would get "ports" for every output. Could cut down on parsing significantly.

    For compatibility, they could all redirect to either standard output or standard error by default... I think I'm gonna experiment with this a bit.

  • by jez on 12/12/2024, 11:58:28 PM

    Some more notes:

    - If this is your first time hearing about the readline/emacs keybindings like Ctrl-E and Ctrl-W, you'll be pleased to know that most macOS input sources use these keybindings. If you're on macOS, feel free to try Ctrl-E, Ctrl-W, or Ctrl-U in your browser's address bar right now

    - If you're using a command line program that doesn't support _any_ line editing (e.g. no readline keybindings, and no other keybindings), you can install the `rlwrap` program and launch the REPL under rlwrap. For example Standard ML of New Jersey has a REPL but no line editing functionality, but you can recover that via `rlwrap smlnj`

    - "don’t use more than 16 colours" — I would go so far as to say "don't use more than 8 colors, or at least make your colors configurable." Many popular color schemes, including Solaraized and the default Base 16 color scheme, use the "bright" colors to hold various shades of gray. What you think is "bright green" might actually be the same shade of gray that normal text is colored.

  • by chriswarbo on 12/12/2024, 10:09:01 PM

    Ctrl-D for REPLs always bites me with GHCi. My usual approach to quitting GHCi is:

    - Press Ctrl-D, like normal

    - Get confused when nothing happens

    - Remember that it doesn't work in GHCi, so run `:q` instead

    - Get an error message about "lexical error at character '\EOT'", due to Ctrl-D inserting an invisible char at the start of the input

    - Try `:q` again, without any invisible prefix

    - GHCi successfully quits

  • by jrockway on 12/12/2024, 11:51:16 PM

    One that's missing is treating ~ as the home directory. This appears to be a shell thing and not a POSIX API thing. For example, this doesn't work:

      func main() {
            if _, err := os.ReadFile("~/.bashrc"); err != nil {
                    log.Fatal(err)
            }
            fmt.Println("ok")
      }
    
    Meanwhile over in shell land:

      $ echo ~/~/~
      /home/jrockway/~/~
    
    The behavior is actually kind of amazing.

    I mention it because while "yourprogram ~/path/to/file" always works, having a repl that asks for a filename might not work. I've seen a lot of software where this DOES work, so I think it counts as a "most TUI programs do this" thing.

  • by lieks on 12/12/2024, 11:16:07 PM

    The main reason I enjoy CLIs so much more than GUIs (or eves TUIs sometimes) is that it feels so consistent.

    There are conventions, but following all the conventions in a CLI is a lot easier than designing a good GUI. So they tend to be higher quality as a result.

    I spend a lot of time thinking how to bring this property to GUIs, but my best answers are still "lots of effort" or "lower your expectations".

  • by lyxell on 12/12/2024, 9:45:24 PM

    I’d like to add: Programs should not add files to your home directory and should respect XDG_CONFIG_HOME and friends.

  • by Sesse__ on 12/12/2024, 8:43:18 PM

    “rule 5.1: Ctrl-W should delete the last word […] I can’t think of any exceptions to this other than text editors but if there are I’d love to hear about them!”

    mysql(1) only links to editline instead of readline, where Ctrl-W by default deletes everything to the start of the line, not just the last word. It drove me mad in the period where I had to use it; you just see your entire nice query disappear. :-)

  • by felixhummel on 12/12/2024, 8:06:50 PM

    I'd add "long-running processes should reload their configuration on SIGHUP". :)

  • by eschaton on 12/13/2024, 8:09:40 PM

    Other rules for command line tools:

    1. Don’t assume a terminal type. Look at `TERM` and use termcap/terminfo or a library built atop them for anything beyond line-oriented plain text output, or least assume a plain teletype unless you specifically recognize the user’s terminal type.

    2. Don’t assume the presence of a terminal at all. Check `isatty()` before doing anything fancy, and be sure to work without a terminal so your tool can be used in pipelines and called by other programs via `exec()`.

    3. Follow the common conventions in your arguments and output structure. For example, if your tool takes an open-ended set of arguments, support specifying a response file with `@path/to/file` to avoid argument limits. If your tool supports record-oriented output, support a `-0` argument to `NUL`-separate the output for use with `xargs` in pipelines.

    4. Use the standard `<sysexits.h>` exit codes. They exist for a reason and they make use of your tool within pipelines and programs more straightforward because they make it easier to trace why a failure occurred.

    5. Include both in-binary `--help`/usage information and a man page. Often a user will just need a quick refresher on argument syntax, which is what the built-in help text is for; the man page should be a comprehensive reference with examples. It should *never* defer to a web page or GNU `info`—it’s fine if those exist too and are pointed out, but they should not be the primary user reference.

    Lots of Linux-oriented tools have one or more of these failure modes, and behave poorly on real terminals that aren’t VT100 derivatives or are awkward to use in anything but an interactive setting.

  • by Sophira on 12/13/2024, 4:25:09 AM

    One thing I'm wondering about is what text encoding the program should use to output. I tend to write scripts that output exclusively in UTF-8, but I realise this might not be a given. (And, of course, the user's terminal expected encoding setting doesn't necessarily mean that files should be written in that encoding.)

    Presumably, you would ideally output text in whatever encoding is specified by the LANG environment variable, but this seems like something that only comes with full i18n/l10n support, since it also specifies the actual language to use.

    Are there any actual established guidelines on this?

  • by jeffrallen on 12/12/2024, 10:25:57 PM

    If you are a young sysadmin, take the time to.learn Emacs. Not because Emacs is good (but it is) but because deadline keys are Emacs keys, so once you know Emacs you know shell, MySQL, etc.

    Play your terminal like a piano . Your livelihood depends on it.

  • by yencabulator on 12/13/2024, 3:52:41 PM

    > then the operating system will return an EOF when you press Ctrl-D on an empty line.

    This is akshully not correct. Control-D makes the read(2) return with the data currently in the input buffer. If there's no data in the buffer, that results in a 0-length read, which is how EOF is signaled.

    Try this: run cat, type foo, press control-D. "foo" will be echoed, without any newline.

  • by sysread on 12/12/2024, 8:54:23 PM

    Non-interactive programs that emit informational output should only do so to stderr or a log file so that they may be used in a pipe line.

  • by xg15 on 12/14/2024, 12:25:33 PM

    It's also important to know which rules are "hard", i.e. implemented or enforced by the first three (or implemented by the program, but would be a complete nonstarter to break) and which rules are just "conventions", like the "short options/long options" rules in POSIX.

    In particular, those can change between OSes: On all linuxes, programs already get a pre-parsed array of command line args on start, so parsing and quoting behavior depend on the shell, not individual programs.

    On Windows, programs get passed the entire command line invocation as a string and have to do the parsing themselves. While all "well-behaved" programs let the libc do this, it's perfectly possible for a program to break the rules here.

  • by adiabatty on 12/13/2024, 12:11:37 AM

    One thing that I’ve noticed:

    On UNIX, expanding globs (*.txt) is the shell’s job.

    On Windows, expanding globs is the program’s job.

    I used to have a bunch of four-line Python programs to, essentially, run `flac --best --replay-gain *.wav`.

  • by shmerl on 12/12/2024, 11:58:38 PM

    > don’t use more than 16 colours

    We aren't in the '80s. Use true color if you want to, modern terminals should support it (built in Linux tty is a weird outlier that should have supported true color years ago).

    But that also depends on the context. For example if something implements its own TUI with a lot of elements - it makes more sense to use more colors than the barebones set.

    Most programs that do care about colors, check what terminal capabilities are before using them.

  • by mattofak on 12/12/2024, 11:08:38 PM

    In the same category of command line program guidelines: https://clig.dev/

  • by model-15-DAV on 12/12/2024, 8:59:38 PM

    As an addendum to Rule 7, `cd -` takes you to the last opened directory. Or is `cd` considered part of the terminal emulator's job, as a built-in?

  • by xuhu on 12/13/2024, 1:42:58 AM

    How did copy-pasting text between programs fit in the initial shell design ? You can't paste from a remote tmux to a local one, you can't mouse-copy multiple lines from a vim vertical split, etc. Where's the terminal clipboard that works across programs ?

  • by cpif on 12/12/2024, 11:49:10 PM

    I can use Ctrl-A, Ctrl-E, and Ctrl-U in text fields in the lynx browser, but not Ctrl-W.

    I just checked to see if Ctrl-F and Ctrl-B work, and found that the former kills one word forward and the latter acts like Ctrl-W ought to?

  • by cbhl on 12/12/2024, 11:26:51 PM

    > rule 3: REPLs should quit when you press Ctrl-D on an empty line

    If memory serves, this behavior depends on the OS. On Windows I believe the norm there is to type "<Ctrl-Z><Enter>"

  • by ucarion on 12/12/2024, 10:52:34 PM

    What does "cooked" mode mean in the context of this article?

  • by noisycarlos on 12/16/2024, 6:27:33 AM

    I can't be the only one that accidentally closes tabs in my browser when I want to delete a word with Ctrl+W

  • by ruricolist on 12/12/2024, 11:19:22 PM

    For forcing colorized output: I personally prefer pipetty (from the colorized-logs package on Debian) to unbuffer, since unbuffer merges stdout and stdin.

  • by EasyMark on 12/13/2024, 10:41:29 PM

    Another one that comes in really handy for ssh connections:

    hit -> [return] ~ .

    to break a jammed up ssh session. A lot of people don't seem to know that one.

  • by anthk on 12/12/2024, 8:38:52 PM

    Don't hardcode readline keybindings.

  • by eviks on 12/13/2024, 3:32:48 AM

    Some of the rules codify many bad practices, from poor color support to unergonomic keybindings. Given that the support isn't universal and breaks in parts anyway, it's better to break it competely and use something more ergonomic

  • by teddyh on 12/13/2024, 12:40:39 AM

    See also The Art of Unix Programming: <http://www.catb.org/~esr/writings/taoup/html/>