How to write if statement in .tmux.conf to set different options for different tmux versions?

28,974

Solution 1

Based on @ericx's answer and @thiagowfx's answer I put the following together which covers many of the listed incompatibilties from version 2.0 onwards:

# Version-specific commands [grumble, grumble]
# See: https://github.com/tmux/tmux/blob/master/CHANGES
run-shell 'tmux setenv -g TMUX_VERSION $(tmux -V | \
                           sed -En "s/^tmux[^0-9]*([.0-9]+).*/\1/p")'


if-shell -b '[ "$(echo "$TMUX_VERSION < 2.1" | bc)" = 1 ]' " \
    set -g mouse-select-pane on; set -g mode-mouse on; \
    set -g mouse-resize-pane on; set -g mouse-select-window on; \
    set -g message-fg red; \
    set -g message-bg black; \
    set -g message-attr bright; \
    set -g window-status-bg default; \
    set -g window-status-fg default; \
    set -g window-status-current-attr bold; \
    set -g window-status-current-bg cyan; \
    set -g window-status-current-fg default; \
    set -g window-status-bell-fg red; \
    set -g window-status-bell-bg black; \
    set -g window-status-activity-fg white; \
    set -g window-status-activity-bg black"

# In version 2.1 "mouse" replaced the previous 4 mouse options
if-shell -b '[ "$(echo "$TMUX_VERSION >= 2.1" | bc)" = 1 ]' \
  "set -g mouse on"

# UTF8 is autodetected in 2.2 onwards, but errors if explicitly set
if-shell -b '[ "$(echo "$TMUX_VERSION < 2.2" | bc)" = 1 ]' \
  "set -g utf8 on; set -g status-utf8 on; set -g mouse-utf8 on"

# bind-key syntax changed in 2.4 -- selection / copy / paste
if-shell -b '[ "$(echo "$TMUX_VERSION < 2.4" | bc)" = 1 ]' " \
   bind-key -t vi-copy v   begin-selection; \
   bind-key -t vi-copy V   send -X select-line; \
   bind-key -t vi-copy C-v rectangle-toggle; \
   bind-key -t vi-copy y   copy-pipe 'xclip -selection clipboard -in'"

# Newer versions
if-shell -b '[ "$(echo "$TMUX_VERSION < 2.9" | bc)" = 1 ]' " \
   bind-key -T copy-mode-vi v   send -X begin-selection; \
   bind-key -T copy-mode-vi V   send -X select-line; \
   bind-key -T copy-mode-vi C-v send -X rectangle-toggle; \
   bind-key -T copy-mode-vi y   send -X copy-pipe-and-cancel 'xclip -selection clipboard -in'"

if-shell -b '[ "$(echo "$TMUX_VERSION >= 2.9" | bc)" = 1 ]' \
   "set -g message-style fg=red,bg=black; \
    set -g message-style bright; \
    set -g window-status-style          fg=default,bg=default; \
    set -g window-status-current-style  fg=default,bg=cyan,bold; \
    set -g window-status-bell-style     fg=red,bg=black; \
    set -g window-status-activity-style fg=white,bg=black"

I raised an issue about the problems with tmux's non-backward-compatibility here. The summary is that the tmux devs will not support backward compatibility, nor will they adopt a version numbering scheme which highlights which versions contain breaking changes. 😢

I raised an issue to support numeric comparators for %if which was implemented in v3.0.

Solution 2

if-shell doesn't always work. Instead, I use a shell script for loading the correct version of tmux.conf:

In .tmux.conf:

run-shell "bash ~/.tmux/verify_tmux_version.sh"

In verify_tmux_version.sh:

#!/bin/bash

verify_tmux_version () {
    tmux_home=~/.tmux
    tmux_version="$(tmux -V | cut -c 6-)"

    if [[ $(echo "$tmux_version >= 2.1" | bc) -eq 1 ]] ; then
        tmux source-file "$tmux_home/tmux_2.1_up.conf"
        exit
    elif [[ $(echo "$tmux_version >= 1.9" | bc) -eq 1 ]] ; then
        tmux source-file "$tmux_home/tmux_1.9_to_2.1.conf"
        exit
    else
        tmux source-file "$tmux_home/tmux_1.9_down.conf"
        exit
    fi
}

verify_tmux_version

For more details: https://gist.github.com/vincenthsu/6847a8f2a94e61735034e65d17ca0d66

Solution 3

This is kind of a hastle. The correct way to do this within tmux (not relying on an external shell script) combines features of both Vincent and jdloft's responses.

The if-shell command in tmux is used as

if-shell [-bF] [-t target-pane] shell-command command [command]
               (alias: if)
    Execute the first command if shell-command returns success or the second command otherwise.  Before
    being executed, shell-command is expanded using the rules specified in the FORMATS section, including
    those relevant to target-pane.  With -b, shell-command is run in the background.

    If -F is given, shell-command is not executed but considered success if neither empty nor zero (after
         formats are expanded).

Note that tmux shell-command expansion will expand variables of the form #{pane_current_path} but otherwise will leave the command alone.

More importantly, note that tmux uses /bin/sh -c to execute the shell command we specify. Thus, the command must be POSIX compliant, so tests of the form [[ are not guaranteed to be portable. Modern Ubuntu and Debian systems, for example, symlink /bin/sh to dash.

We want to run a POSIX compliant shell command that tests the tmux version and returns 0 (true) if the desired version is found.

if-shell '[ $(echo "$(tmux -V | cut -d" " -f2) >= 2.1" | bc) -eq 1 ]' \
    'command if true' \
    'command if false'

Example:

if-shell '[ $(echo "$(tmux -V | cut -d" " -f2) >= 2.1" | bc) -eq 1 ]' \
    'set -g mouse on; set -g mouse-utf8 on' \
    'set -g mode-mouse on; set -g mouse-resize-pane on; set -g mouse-select-pane on; set -g mouse-select-window on' 

This correctly deals with the fact that we are doing floating point arithmetic, so bc is required. Additionally, there is no need for an if/then/else/fi construct, as the [ operator produces a truthy value by itself.

A couple notes

  • Lines continuing onto the next line cannot have trailing comments or tmux will give an "unknown command" error message.
  • The "command if false" can be omitted.
  • Multiple commands for either true or false can be combined using ;
  • The command is run on the underlying shell using /bin/sh -c. Other approaches that use [[ or other non-POSIX syntax are not guaranteed to work.

EDIT: A previous version of this answer used [[, which doesn't work on systems that don't use bash. Replacing with [ solves this.

Solution 4

I also stumbled over configuration mismatches in different tmux versions. After reviewing all the solutions here and in this related question on SuperUser, I've implemented the following variant:

# Version-specific configuration can be placed in ~/.tmux/${TMUX_VERSION}/*.conf
run-shell "for conf in ~/.tmux/$(tmux -V | cut -d' ' -f2)/*.conf; do tmux source-file \"\$conf\"; done"

With this, version-specific configuration can be put in (multiple) configuration snippets for a particular version. This is similar to the solution of @VincentHsu, but:

  • It does not require an external shell script.
  • It does not segregate into fixed version ranges (... / 1.9 to 2.0 / 2.1 ...). Instead, one can use (sym)links to share a configuration snippet among multiple tmux versions.
  • It does not hardcode a single filename for a version. By allowing multiple configuration snippets for each version, parts can be shared among versions while others are kept version-specific. This should offer the utmost flexibility.
  • There's just a single configuration element in the original ~/.tmux.conf. Other solutions like the one from @TomHale duplicate the version test for each configuration element.

Solution 5

On some machines I was getting a false-positive result with the double bracket ('[[') syntax. So I came up with an alternative using awk:

# Enable mouse for different versions of tmux
# (If 'awk' exits with status 0, 'if-shell' evaluates to true)
# tmux < v2.1:
if-shell "tmux -V | awk '{exit !($2 < \"2.1\")}'" \
    "setw -g mode-mouse on ; set -g mouse-select-pane on ; set -g mouse-resize-pane on ; set -g mouse-select-window on ;"
# tmux >= v2.1:
if-shell "tmux -V | awk '{exit !($2 >= \"2.1\")}'" \
    "set -g mouse on ;"
Share:
28,974

Related videos on Youtube

mrt181
Author by

mrt181

I am software developer and like it that way.

Updated on January 23, 2022

Comments

  • mrt181
    mrt181 over 2 years

    I have a .tmux.conf which I use on different machines with different tmux versions installed.

    I want to set different mouse options, depending on the tmux version. On one machine I have version 2.0 on the other 2.1.

    I do not get his part right

    if "[[(( $(tmux -V | cut -c 6-) < 2.1 ))]]" \
      "set -g mode-mouse on;" \
      "set -g mouse-resize-pane on;" \
      "set -g select-pane on;" \
      "set -g select-window on" "set -g mouse on"
    

    When I source the file

    $ tmux source-file .tmux.conf

    I get this message

    .tmux.conf:12: unknown command: set -g mouse-resize-pane on

    The machine where I run it has version 2.1 so it shouldn't set the four options.

    I want to set the four options when running tmux 2.0 or less or the one option when running tmux 2.1.

    This bash statement works

    $ tmux -V
    tmux 2.1
    $ if [[(( $(tmux -V | cut -c 6-) < 2.1 ))]];then echo $?;else echo $?;fi
    1
    
    • Etan Reisner
      Etan Reisner about 8 years
      Why do you have both [[ ]] and (( )) in that test? I would think just (( )) would be enough assuming if (which is if-shell?) tests the return code.
    • mrt181
      mrt181 about 8 years
      fixing the if statement to if "(( $(tmux -V | cut -c 6-) < 2.1 ))" "set -g mode-mouse on; set -g mouse-resize-pane on; set -g select-pane on; set -g select-window on" fixes the problem, Can you post your comment as an answer.
    • Etan Reisner
      Etan Reisner about 8 years
      Actually (( )) can't handle the decimal numbers so if that works it is a different reason (or accidental). Does tmux have a way to test for features? (I assume you only want that turned on in versions that support it.)
    • mrt181
      mrt181 about 8 years
      I do not know if tmux can test for features, I am rather new to tmux.
    • Ciro Santilli OurBigBook.com
      Ciro Santilli OurBigBook.com almost 8 years
  • Jongwook Choi
    Jongwook Choi about 8 years
    The if-shell configuration for tmux <2.1 seems not working. Even with tmux 2.0, the bash shell says true for if [[ $(tmux -V | cut -d' ' -f2) -lt 2.1 ]]; then echo 'true'; true; else echo 'false'; false; fi, but with if-shell "if [[ $(tmux -V | cut -d' ' -f2) -lt 2.1 ]]; then true; else false; fi" "display true;" "display false;", tmux says that unknown command: display 2. It means the shell command is evaluated as false.
  • Ciro Santilli OurBigBook.com
    Ciro Santilli OurBigBook.com almost 8 years
    That bc hack is hardcore.
  • Wyntau
    Wyntau over 7 years
    I get the same problem. if-shell always get false return. anyone coule please me?
  • Micah Smith
    Micah Smith over 7 years
    This simply doesn't work. Just try running [[ `tmux -V | cut -d' ' -f2` -lt 2.1 ]] in bash. I get -bash: [[: 2.2: syntax error: invalid arithmetic operator (error token is ".2"). The reason is that these test operators don't handle floating points, for which you need bc.
  • jdloft
    jdloft over 7 years
    Sorry, I was testing in Z shell which allows the use of the "-lt" operator like that. Try replacing it with the '<' symbol (and the '>' symbol for greater than).
  • Shubham Chaudhary
    Shubham Chaudhary over 7 years
    A cat dies whenever somebody does boolean ? true : false instead of boolean only
  • Carl Patenaude Poulin
    Carl Patenaude Poulin over 7 years
    I'm wondering if there's a way to make this work with nested if statements.
  • Tom Hale
    Tom Hale over 7 years
    I'm sure you could @Carl, but your quoting of " characters would get hairy. @pana You'll need a ; between lines 2 and 3 in the last example. Also, you have mouse twice. See my answer for an example of a continuation within a continuation.
  • Tom Hale
    Tom Hale over 7 years
    The configuration management is already non-trivial managing 3 configuration files. The tmux devs introduce incompatibilities quite a bit and will probably continue to do so. I prefer a solution using only .tmux.conf which I based on this answer.
  • Vincent Hsu
    Vincent Hsu over 7 years
    I have tried the if-shell solution, but I can't find a way to detect the version numbers correctly on my 3 machines (Ubuntu 14.04 (tmux 1.8), Ubuntu 16.04 (tmux 2.1) and macOS Sierra (tmux 2.3)). So I choose to call bash directly. I would be happy if someone make "only one .tmux.conf" work.
  • joelparkerhenderson
    joelparkerhenderson about 7 years
    Thanks Tom, this is great. I've added a thank you to you on my repo github.com/sixarm/sixarm_tmux_dotfiles
  • Andrei Bârsan
    Andrei Bârsan almost 7 years
    As someone who uses the same dotfiles on OSX and 2-3 different linux boxes, I can't thank you enough! This answer seems to be the most versatile of them all.
  • Krisztian
    Krisztian over 6 years
    Brilliant solution, that should be the accepted answer! Thanks (I'd add head -1 | after tmux -V | in case they will include more lines into its description later.)
  • raimue
    raimue about 6 years
    @Krisztian Should not be necessary, as the exit already causes awk to stop processing on first line.
  • Krisztian
    Krisztian about 6 years
    @raimue, thanks, I didn't know that, it seems to work indeed: echo -e "tmux 2.0-master\ntmux 2.8" | awk '{exit !($2 < "2.1")}' && echo ok || echo no
  • Dave
    Dave almost 6 years
    Hey, has anyone ever got this to work? tmux 'run-shell' is hard-coded to run /bin/sh. None of the example above work in /bin/sh.
  • William Pursell
    William Pursell over 5 years
    Minor nit: [ is not an operator, it is a command. This is only an important distinction because many people mistake [ as being part of the shell grammar, which it is not.
  • Tom Hale
    Tom Hale over 5 years
    sh and bash don't allow for arithmetic comparison of floats. See this answer for a solution using the bench calculator bc
  • Tony Clifton
    Tony Clifton over 5 years
    this is pointed out below in Micah's answer, but I was having issues due to missing semicolons here before the back slashes (it was all being interpreted as a single long command). Thanks for the example!
  • Gerrit-K
    Gerrit-K over 5 years
    @TonyClifton Indeed, not only the missing semicolons cause problems, but also everything past the first comment char (#) was ignored in my case ...
  • pdoherty926
    pdoherty926 about 5 years
    This is an excellent answer. I was able to achieve what I was after and I learned a few things about tmux and Bash. Thanks!
  • Tom Hale
    Tom Hale almost 5 years
    @Griddo thanks for the edit. I've updated based on v2.9, but as I didn't encounter the issues you experienced, I'd appreciate your review. What version are you using which baulks at the # and lack of ;?
  • Gerrit-K
    Gerrit-K almost 5 years
    @TomHale I'm currently using v2.6 on linux mint and also something >v2.4 as well as v2.8 on WSL debian. See this minimal example which exactly reproduces the two errors.
  • danemacmillan
    danemacmillan almost 5 years
    It should be noted that these version checks will not work if there are letters and other characters in the release version. In the spirit of tmux's FU SEMVER, I offer up the last two releases on GitHub that defy these version checks: 2.9a and 3.0-rc3. github.com/tmux/tmux/releases
  • danemacmillan
    danemacmillan almost 5 years
    $(tmux -V | cut -c -8 | cut -d' ' -f2)
  • Tom Hale
    Tom Hale almost 5 years
    Thanks for the heads-up @danemacmillan. Their release management is FUBAR. I've updated with a single sed -n.
  • danemacmillan
    danemacmillan almost 5 years
    @TomHale That sed string is much better, but you forgot to pipe it. I'm taking it, thanks!
  • Tom Hale
    Tom Hale almost 5 years
    @danemacmillan Please feel free to use the edit link to improve any answer on this site! :)
  • raychi
    raychi almost 4 years
    @TomHale, the setenv line doesn't work for version tmux.orig 2.8 value. sed -En "s/^tmux.* ... does work.
  • philb
    philb almost 4 years
    careful though, sort -V does not always work, for example it does not work on macOS 10.11 which uses BSD sort
  • silico-biomancer
    silico-biomancer almost 4 years
    Drats, that's a good caveat. I usually put gnu sort on my OSX machines, so I missed that. I'll add the caveat to the answer.
  • Tom Hale
    Tom Hale almost 4 years
    @raychi Sorry, but I don't quite follow. Please suggest an edit.
  • raychi
    raychi almost 4 years
    @TomHale, My tmux -V returns the string tmux.orig 2.8. This value doesn't work with the run-shell sed command. Change the sed pattern to s/^tmux.* .... (add .* after "tmux"). Suggest edit didn't work since change needs to be 6 characters long and mine is just 2-chars :)
  • Matthew Strasiotto
    Matthew Strasiotto over 3 years
    The current head of master has the version string: tmux next-3.3 The pattern in the answer didn't work, an (other) appropriate pattern for sed is: bash sed -En "s/^tmux[^0-9]*([.0-9]+).*/\1/p" I read your linked issue, & I share your disappointment in the maintainers' attitude towards semver. As you pointed out, that 23 minutes of .conf fixup for one dev -> thousands of cumilative person-hours across the userbase.
  • Matthew Strasiotto
    Matthew Strasiotto over 3 years
    Hey, just regarding this, one way to clean-up your tmux.conf that implements this so it's a little more DRY: You can store your shell test strings in tmux env vars, (since 0.8 in 2008) and interpolate them @ run time for if-shell: V33_GTE='[ "$(echo "$TMUX_VERSION >= 3.3" | bc)" = 1 ]' V33_LT='[ "$(echo "$TMUX_VERSION < 3.3" | bc)" = 1 ]' if "$V33_GTE" bind -N "Mouse mode off" M
  • Matthew Strasiotto
    Matthew Strasiotto over 3 years
    your cut command isn't likely to be robust enough across different version strings. For example tmux -V at the HEAD of the repo is tmux next-3.3. tmux -V | cut -c 6- gives next-3.3. Also as others have pointed out [[ ]] test constructs are a bash feature that's not portable across systems, as the only guaranteed shell is the POSIX compliant sh.
  • Tom Hale
    Tom Hale over 3 years
    @MatthewStrasiotto Sorry, I don't follow, what repetition does this remove?
  • Matthew Strasiotto
    Matthew Strasiotto over 3 years
    @TomHale instead of copy-pasting & evaluating the same shell test, you wind up copy pasting an env-var that stores the test & evaluating that. The benefit is marginal really
  • wisnij
    wisnij over 3 years
    To avoid having to use bc (which isn't installed on some of the machines I often log into remotely) I've been doing this: run-shell "tmux setenv -g TMUX_VERSION $(tmux -V | sed -E 's/^[^0-9]*//; s/[^0-9]*$//' | awk -F. '{ printf \"%d.%02d\", $1, $2 }')" and then if-shell 'bash -c "if [[ $TMUX_VERSION < 1.09 ]]; then :; else false; fi"' '...'. That is, I format the version in a way that can be compared lexicographically, and use the regular bash < and > operators to compare them as strings. It works on tmux 1.8, 2.1 and 3.1b, but I haven't tested exhaustively.
  • Elliptical view
    Elliptical view over 3 years
    set -g mouse (without on or off) toggles all by itself. Try this: bind-key c-M set-option -g mouse \; display-message 'Mouse #{?mouse,on,off}'
  • Matthew Strasiotto
    Matthew Strasiotto over 3 years
    I can change the answer to reflect that different approach, but it's not relevant to the answer, it's an example of a binding that's conditional on an if-shell statement that evaluates an interpolated statement.
  • NeilG
    NeilG almost 2 years
    That bc hack is so hardcore it fails for me: my tmux version is "3.0a", @CiroSantilliПутлерКапут六四事 Can be fixed with tmux_version="$(tmux -V | cut -c 6-8)"
  • DJCrashdummy
    DJCrashdummy almost 2 years
    @NeilG that has nothing to do with the bc hack, just the cut part is pretty unreliable! - also your fix will break for other outputs like tmux next-3.3 or even if just one part uses more than one digit. || although it gets a little bit longer, i use sed instead of cut which works for all cases known: sed -E -e 's/[[:alpha:][:space:][:punct:]]*([[:digit:]]*\.?[[:digit:]]‌​+).*/\1/'