小技巧(tips and tricks) {#tips-and-tricks}

Read the tmux manual in style

$ man tmux is the command to load up the man page for tmux. You can do the same to find instructions for any command or entity with a manpage entry; here are some fun ones:

  1. $ man less
  2. $ man man
  3. $ man strftime

most(1), a solid PAGER, drastically improves readability of manual pages by acting as a syntax highlighter.

left: man, version 1.6c on macOS Sierra. right: MOST v5.0.0

To get this working, you need to set your PAGER environmental variable to point to the MOST binary. You can test it like this:

  1. $ PAGER=most man ls

If you found you like most, you’ll probably want to make it your default manpage reader. You can do this by setting an environmental variable in your “rc” (Run Commands) for your shell. The location of the file depends on your shell. You can use $ echo $SHELL to find it on most shells). In Bash and zsh, these are kept in ~/.bashrc or ~/.zshrc, respectively:

  1. export PAGER="most"

I often reuse my configurations across machines, and some of them may not have most installed, so I will have my scripting only set PAGER if most is found:

  1. #!/bin/sh
  2. if command -v most > /dev/null 2>&1; then
  3. export PAGER="most"
  4. fi

Save this in a file, for example, to ~/.dot-config/most.sh.

Then you can source) it in via your main rc file.

  1. source $HOME/.dot-config/most.sh

Patterns like these help make your dot-configs portable, cross-platform, and modular. For inspiration, you can fork, copy, and paste from my permissively- licensed config at https://github.com/tony/.dot-config.

Log tailing

Not tmux specific, but powerful when used in tandem with it, you can run a follow (-f) using tail(1). More modern versions of tail have the -F (capitalized), which checks for file renames and rotation.

On OS X, you can do:

  1. $ tail -F /var/log/system.log

and keep it running in a pane while log messages come in. It’s like Facebook newsfeed for your system, except for programmers and system administrators.

For monitoring logs, multitail provides a terminal-friendly solution. It’d be an Inception moment, because you’d be using a log multiplexer in a terminal multiplexer.

File watching {#file-watching}

In my never-ending conquest to get software projects working in symphony with code changes, I’ve come to test many file watching applications and patterns. Pursuing the holy grail feedback loop upon file changes, I’ve gradually become the internet’s unofficial connoisseur on them.

File watcher applications wait for a file to be updated, then execute a custom command, such as restarting a server, rebuilding an application, running tests, linters, and so on. It gives you, as a developer, instant feedback in the terminal, empowering a tmux workspace to have IDE-like features, without the bloat, memory, and CPU fans roaring.

I eventually settled on entr(1), which works superbly across Linux distros, BSDs and OS X / macOS.

The trick to make entr work is to pipe) a list of files into it to watch.

Let’s search for all .go) files in a directory and run tests on file change:

  1. $ ls -d *.go | entr -c go test ./...

Sometimes, we may want to watch files recursively, but we need it to run reliably across systems. We can’t depend on ** existing to grab files recursively, since it’s not portable. Something more POSIX-friendly would be find . -print | grep -i '.*[.]go':

  1. $ find . -print | grep -i '.*[.]go' | entr -c go test ./...

To only run file watcher if entr is installed, let’s wrap in a conditional command -v test:

  1. $ if command -v entr > /dev/null; then find . -print | grep -i '.*[.]go' | \
  2. entr -c go test ./...; fi

And have it fallback to go test in the event entr isn’t installed. This allows your command to degrade gracefully. You’ll thank me when you use this snippet in conjunction with a session manager:

  1. $ if command -v entr > /dev/null; then find . -print | grep -i '.*[.]go' | \
  2. entr -c go test ./...; else go test ./...; fi

If the project is a team or open source project, where a user never used the command before and could be missing a required software package, we can give a helpful message. This shows a notice to the user to install entr if not installed on the system:

  1. $ if command -v entr > /dev/null; then find . -print | grep -i '.*[.]go' | \
  2. entr -c go test ./...; else go test ./...; echo "\nInstall entr(1) to \"
  3. echo "run tasks when files change. \nSee http://entrproject.org/"; fi

Here’s why you want patterns like above: You can put it into a Makefile and commit it to your project’s VCS, so you and other developers can have access to this reusable command across different UNIX-like systems, with and without certain programs installed.

Note: You may have to convert the indentation within the Makefiles from spaces to tabs.

Let’s see what a Makefile with this looks like:

  1. watch_test:
  2. if command -v entr > /dev/null; then find . -print | grep -i '.*[.]go' | entr -c go test ./...; else go test ./...; echo "\nInstall entr(1) to run tasks when files change. \nSee http://entrproject.org/"; fi

To run this, do $ make watch_test in the same directory as the Makefile.

But it’s still a tad bloated and hard to read. We have a couple tricks at our disposal. One would be to add continuation to the next line with a trailing backslash (\):

  1. watch_test:
  2. if command -v entr > /dev/null; then find . -print | \
  3. grep -i '.*[.]go' | entr -c go test ./...; \
  4. else go test ./...; \
  5. echo "\nInstall entr(1) to run tasks on file change. \n"; \
  6. echo "See http://entrproject.org/"; fi

Another would be to break the command into variables and make subcommands. So:

  1. WATCH_FILES= find . -type f -not -path '*/\.*' | \
  2. grep -i '.*[.]go$$' 2> /dev/null
  3. test:
  4. go test $(test) ./...
  5. entr_warn:
  6. @echo "-------------------------------------------------"
  7. @echo " ! File watching functionality non-operational ! "
  8. @echo " "
  9. @echo " Install entr(1) to run tasks on file change. "
  10. @echo " See http://entrproject.org/ "
  11. @echo "-------------------------------------------------"
  12. watch_test:
  13. if command -v entr > /dev/null; then ${WATCH_FILES} | \
  14. entr -c $(MAKE) test; else $(MAKE) test entr_warn; fi

$(MAKE) is used for portability. One reason is recursive calls, such as here. On BSD systems, you may try invoking make via gmake (to call GNU Make specifically). This happened to me, while building PDFs for the book AlgoXY. I had to write a patch to make it properly use $(MAKE) for recursive calls.

The $(test) after go test allows passing a shell variable with arguments in it. So, you could do make watch_test test='-i'. For examples of a similar Makefile in action, see the one in my tmuxp project. The project is licensed BSD (permissive), so you can grab code and use it in compliance with the LICENSE.

One more thing, let’s say you’re running a server, like Gin, Iris, or Echo. entr -c likely won’t be restarting the server for you. Try entering the -r flag to send a SIGTERM to the process before restarting it. Combining the current -c flag with the new -r will give you entr -rc:

  1. run:
  2. go run main.go
  3. watch_run:
  4. if command -v entr > /dev/null; then ${WATCH_FILES} | \
  5. entr -c $(MAKE) run; else $(MAKE) run entr_warn; fi

Session Managers {#session-manager}

For those who use tmux regularly to perform repetitive tasks, such as opening the same software project, viewing the same logs, etc., frequent tasks will often lead to the creation of tmux scripts.

A user can use plain shell scripting to build their tmux sessions. However, scripting is error prone, hard to debug, and requires tmux to split windows into panes in a certain order. In addition, there’s the burden of assuring the shell scripts are portable.

A declarative configuration in YAML or JSON configuration abstracts out the commands, layout, and options of tmux. It prevents the mistakes and repetition scripting entails. These applications are called tmux session managers, and in different ways, they programmatically create tmux workspaces by running a series of commands based on a config.

Teamocil and Tmuxinator are the first ones I tried. By far, the most popular one is tmuxinator. They are both programmed in Ruby. There’s also tmuxomatic, where you can “draw” your tmux sessions in text and have tmuxomatic build the layout.

I sort of have a home team advantage here, as I’m author of tmuxp. Already having used teamocil and tmuxinator, I wrote my own in python instead of ruby, with many more features. For one, it builds on top of libtmux, a library which abstracts tmux server, sessions, windows and panes to build the state of tmux sessions. In addition, it has a naive form of session freezing, support for JSON, more flexible configuration options, and it will even offer to attach exiting sessions, instead of redundantly running script commands against the session if it’s already running.

So, in tmuxp, we’ll hollow out a tmuxp config directory with $ mkdir ~/.tmuxp then create a YAML file at ~/.tmuxp/test.yaml:

  1. session_name: 4-pane-split
  2. windows:
  3. - window_name: dev window
  4. layout: tiled
  5. shell_command_before:
  6. - cd ~/ # run as a first command in all panes
  7. panes:
  8. - shell_command: # pane no. 1
  9. - cd /var/log # run multiple commands in this pane
  10. - ls -al | grep \.log
  11. - echo second pane # pane no. 2
  12. - echo third pane # pane no. 3
  13. - echo forth pane # pane no. 4

gives a session titled 4-pane-split, with one window titled dev window with 4 panes in it. 3 in the home directory; the other is in /var/log and is printing a list of all files ending with .log.

To launch it, install tmuxp and load the configuration:

  1. $ pip install --user tmuxp
  2. $ tmuxp -V # verify tmuxp is installed, if not you need to fix your `PATH`
  3. # to point to your python bin folder. More help below.
  4. $ tmuxp load ~/.tmuxp/test.yaml

If tmuxp isn’t found, there is a troubleshooting entry on fixing your paths in the appendix.

More code and examples {#example-projects}

I’ve dusted off a C++ space shooter and a new go webapp I’ve been playing with. They’re licensed under MIT so, you can use them, copy and paste from them, etc:

Both support tmuxp load . within the project directory to load up the project.

Make sure to install entr(1) beforehand!

tmux-plugins and tpm

tmux-plugins and tmux package manager are a suite of tools dedicated to enhancing the experience of tmux users.

  • tmux-resurrect: Persists tmux environment across system restarts.
  • tmux-continuum: Continuous saving of tmux environment. Automatic restore when tmux is started. Automatic tmux start when computer is turned on.
  • tmux-yank: Tmux plugin for copying to system clipboard. Works on OSX, Linux and Cygwin.
  • tmux-battery: Plug and play battery percentage and icon indicator for Tmux.