When I was first starting out in software engineering, it felt like there was a never-ending barrage of tooling to learn. After more than a decade in CLI environments, I still find myself constantly learning new features and fun facts - but it's fairly rare that I learn something new that I end up using day-to-day.

I wanted to share some things I learned at relatively late stages in the game that ended up being significant productivity boosters for me - perhaps some of them are well-known, but in the spirit of this XCKD, I hope that someone reading this might pick up something new.

Lucky 10,000

1. Use command line editing

Perhaps this type of experience is familiar to you:

Manually moving around a long CLI command is no fun - even with keyboard shortcuts. But editing the command in your editor of choice can be much more pleasant:

Command line editing is a powerful feature that allows you to pop into your $EDITOR to craft your command. It's made the CLI much less frustrating for me whenever I have to deal with long commands that I need to edit.

2. tmux scripting

If you've ever found yourself:

  • Waiting for something to happen in your terminal before reacting
  • Recreating similar sessions/windows/layouts (e.g. identical sessions for different servers)
  • Transferring output between terminal panes

Then tmux scripting is for you! tmux scripting allows you to program your terminal - you can programmatically read terminal output, send input into the terminal, or control your terminal's layout and contents.

I once had to work with a physical device that had a cumbersome development flow - you had to manually reset it over a serial connection, interrupt its autoboot at a precise point in its flow, modify its configuration, remember to wait at least one second because of some hardware idiosyncrasy, input a "boot" command, and then wait for the rest of the flow. This was impossible to traditionally script, but tmux scripting allowed me to automate this process and was a force multiplier for this work.

To illustrate how powerful this can be - I've prepared a small program that outputs random numbers, and you need to type them back into the program for the program to continue. The program is running in the left pane, and the right pane is running a tmux script that's capturing the output of the left pane, parsing it for the current number, and then sending that number back to the left pane:

And here's another script that creates a session with a specific layout and then prepopulates some of the session's panes with commands:

3. Use fzf liberally in custom scripts

fzf is a "general-purpose command-line fuzzy finder" - but this dry description can really hide away just how useful fzf can be if you incorporate it in your custom scripts. fzf is generally known for its nice Ctrl+R and other integrations, but focusing on just its integrations can miss the potential of using it in other workflows.

To illustrate, here are two custom scripts I wrote that I use in my day-to-day - the first lets me interactively git checkout branches:

And the second lets me spin up EC2 instances - when running the command I choose the instance type, region, and distribution:

In both cases, I can alternatively type out a full command (git checkout branch1 or ec2 create -r ap-east-1 -i t3.micro -d ubuntu) but in practice the interactivity is usually a quicker and nicer experience. fzf can be used to supercharge any script that involves selecting something out of a list of options - whether predefined or dynamically populated.

4. Use /dev/stdin as a replacement for heredocs

Heredocs are a fairly well-known powerful feature that allow you to pass multiple lines of input into a command:

But they have two drawbacks for me - first, I find the syntax a bit clunky and often trip up on it, and more importantly, they are not supported in fish which is my shell of choice.

/dev/stdin can be used to accomplish the same functionality in a cross-shell way, and personally since it makes use of well-known Linux primitives and not shell-specific functionality I find it much more intuitive to use and remember than heredocs:

There are some drawbacks to this /dev/stdin trick - for instance, it can't replace heredocs in scripts, and it doesn't keep the contents in your shell history. But so long as you keep its drawbacks in mind it can be a powerful tool.

5. Use SSH multiplexing

SSH multiplexing lets you seamlessly use the same SSH connection for multiple sessions. Enabling it is as easy as placing these lines:

Host *
    ControlPath /tmp/ssh-%r@%h:%p
    ControlMaster auto
    ControlPersist 10m

in your ~/.ssh/config. If your workflow involves frequently SSHing into remote machines, this is a lifechanger.

Bonus Tips

Here are some other tips that I think deserve an honorable shoutout:

  1. Use git checkout - for checking out the previous active branch (similar to cd -).
  2. Use either $_ or !$ in bash to get the last argument of the last command.
  3. Similarly, in both fish and bash you can type in Alt+. to directly paste in the last argument of the last command.
  4. Prepend a space before a command to exclude it from your history.
  5. Use pushd and popd to keep state of your directory traversal. If you're in fish you can just directly use nextd and prevd instead.
  6. Use locate for indexing and instantaneously searching your files.
  7. When you do something like sudo command > file, the command runs as privileged but the redirection doesn't, so if file is a protected file you won't be able to write to it - use command | sudo tee file as a workaround.
  8. Similar to heredocs are herestrings - <<< - which can be used to pass a single string as input, e.g. cat - <<< "Hello, world!"
  9. You can edit remote files directly by writing vim scp://<user>@<host>/<absolute-path>. If you need to specify a private key then create a configuration for the host in your ~/.ssh/config and use that.
  10. You can pipe the output of commands into vim - (similar to the /dev/stdin tip above, but neater) for powerful searchability/editability. In practice I almost never pipe into less, only into vim -.