bind source-file in tmux on startup from bash - bash

I have a .tmux.conf file which binds a set of further tmux instructions when I press the correct keys (in this case, Ctrl + b followed by 'k'):
bind k source-file ~/.tmux/myfile
When I go into my tmux terminal and press Ctrl + b followed by k, it works fine, and the script in myfile runs as expected. I'm now trying to launch this all using a command in my .bashrc file by sending the keys to the tmux session once it has launched:
launch_things() {
tmux new-session -d -s mysession
tmux send-keys -t mysession C-b k
tmux a -t mysession
}
However, this doesn't work. It's starting the tmux session, apparently benting the keys to it, then attaching to the session. I'm not sure if it's because I've done something wrong with the sending of the keys, or if it's not possible to send combinations of keys like this. Note that I can see the letter k on the screen when I attach, so the send-keys functionality is doing something.
Can anyone either tell me what I'm doing wrong, or suggest another way to launch my bound source-file (~/.tmux/myfile) from my bash script please?
P.S. I'm running Ubuntu 16.04 if that makes a difference.

It seems that send-keys when sending strings does not try to interpret them for the prefix character. There is the command send-prefix but that behaves in the same way.
So the simple answer is to do tmux source-file ~/.tmux/myfile or perhaps the longer
tmux attach-session -t mysession\; source-file ~/.tmux/myfile\; detach-client

Related

How can I write a text file, and then read from it in the same Bash script?

I have a hotkey in Vim that takes me into command mode and calls a Bash script. The Bash script attempts the following steps:
Select text between two tokens
Send selected text block to a .py file
Load the .py file into IPython
The script works if I type everything in by hand, but if I run the script, the text block is not saved as a file until after the script is finished, causing an error when IPython tries to load the file later in the script.
Here are the steps I've tried:
In the event my file is being held in the buffer, I tried syncing and flushing
In case the file needed more time to be written, I tried sleep and wait
I also tried asynchronous shell commands to see if the Bash script was getting priority over Vim writing the file.
#!/bin/bash
# Text to be written has been selected in Vim
tmux send-keys ':w jtemp.py'
tmux send-keys 'Enter'
# Load code selection in IPython
tmux select-pane -t 1
tmux send-keys '%load jtemp.py'
tmux send-keys 'Enter'
tmux send-keys 'Enter'
How can I get the file to be saved to disk while the Bash script is still in process?
The problem is most likely (I don't know this for certain however) that Vim is blocking and not processing the key presses until the script finishes. I don't know if it is possible to make Vim run the script in the background, but you could if you bind the hotkey in tmux instead using run-shell -b, something like:
bind F1 run -b "bash /path/to/my/script"
In fact, if doing it in tmux you probably won't need the script to run in the background, because the two affected applications (Vim and IPython) will not block and it doesn't matter if tmux does. So you could just leave the -b out.
You will still almost certainly need a sleep of a second or half a second ("sleep 1" or "sleep 0.5", experiment) between the Enter and the select-pane or there will be a race between Vim writing the file and IPython reading it.
I am not sure of the overhead of calling tmux in a shell script, but I would recommend you script your workflow process without it—you can use sed to parse for text between two tokens in a file, pipe that to a new .py file, and then load that .py file into IPython.

how to scroll to beginning/end of scroll buffer in tmux?

^b+page up/down scrolls up/down one page of scroll buffer, but how do we scroll to beginning ?
like wise with end (besides pressing ^C to kill scrolling)
this depends on the binding of "mode-keys". If you "set-option -g mode-keys emacs" (actually, this is the default settings), then you can go to the beginning and the end of the buffer using corresponding emacs keys.
Enter the copy mode using: ctrl-b + [
Go to the beginning using: Alt + shift + , (or, in emacs' notation: M-<)
Similarly, going to the end is achieved by M->
HOW TO TEST
Please note that configuration in ~/.tmux.conf only takes effect after the tmux server restarts. That is when you kill all sessions and then restart tmux.
In fact, however, there is simpler way to test: just run the following command at command line:
tmux set-option -g mode-keys emacs
I don't think there is a way change only one key. But you do have the choice to
tmux set-option -g mode-keys vi
You should be able move around in copy mode using h j k l etc.
You can use like:
bind-key -T copy-mode C-S-Home send -X history-top # Ctrl+Shift+Home
bind-key -T copy-mode C-S-End send -X history-bottom # Ctrl+Shift+End
This way when you are in copy mode Ctrl+Shift+Home will take you to the beginning of scroll buffer and Ctrl+Shift+End will take you to the end of scroll buffer.

How to clear bell state from all tmux windows

I help maintain a large number of Unix-like servers, and so keep a script called tmux-rebuild that I use to rebuild all the tmux sessions and windows with SSH links to each server.
I have tmux configured to show the window's name in red with an exclamation mark in its status bar when a terminal bell character is printed in that window. This is very handy for programs like irssi alerting me to when I have messages in another window.
I also have my $PS1 set up on every server to print a terminal bell at the end of every prompt. This is useful because if I run a long job in one window and switch to another, I can immediately see when it's finished because when my prompt is written to the screen after the job is done, tmux makes the window name come up in red with an exclamation mark. This is great for my workflow.
However it causes a slight problem with the rebuild script mentioned above, because when I start up tmux after running it, every window in every session is flagged in red, due to the first prompt being printed to the screen. This makes the feature useless until I visit every window, and there are something like 40-50 of them.
Is there something I can add to my script that will clear all alerts from sessions and windows after they are created? I don't mind using a kludge if necessary.
From the tmux man page, specifically the last sentence here:
kill-session [-aC] [-t target-session]
Destroy the given session, closing any windows linked to it
and no other sessions, and detaching all clients attached
to it. If -a is given, all sessions but the specified one is
killed. The -C flag clears alerts (bell, activity, or
silence) in all windows linked to the session.
So, simply:
tmux kill-session -C
Figured out an acceptable workaround; I redefined the next/previous bindings to allow repeats:
# Allow repeats for next/prev window
bind-key -r n next-window
bind-key -r p previous-window
This allows me to quickly sweep up the alerts for all windows in a session by pressing my prefix key and tapping "n" until they're all clear, and I'm back in my original window.
With tmux 1.6 (and later), list-windows can generate customizable output, so it is fairly simple to read the output lines and make a loop that runs select-window for each window.
Add list-session (to loop over all sessions, optionally), and display-message (to parse session specifiers, and to record the current/“last” windows so they can be properly restored), and you might end up with something like this:
#!/bin/sh
# usage: tmux-select-each [session [...]]
#
# Select every window in specified session(s). If no sessions are
# specified, process all windows in all sessions.
#
# This can be handy for clearing the activity flags of windows in
# freshly spawned sessions.
if test $# -gt 0; then
for session; do
tmux display-message -p -t "$session:" '#S'
done
else
tmux list-sessions -F '#{session_name}'
fi |
while read -r session; do
active_window=$(tmux display-message -p -t "$session:" '#S:#I')
last_window=$(tmux display-message -p -t "$session:"\! '#S:#I' 2>/dev/null)
tmux list-windows -t "$session" -F '#{session_name}:#{window_index}' |
while read -r window; do
if test "$window" = "$active_window" ||
test "$window" = "$last_window"; then
continue
fi
tmux select-window -t "$window"
done
if [ -n "$last_window" ]; then
tmux select-window -t "$last_window"
fi
tmux select-window -t "$active_window"
done

GNU screen: Launch command in session without changing window to it

We have an attended-upgrade script which launches apt-get update && apt-get upgrade simultaneously on all our administered systems. Ideally, we'd want to launch them all inside a screen session. When I do it like this:
File: upgrade.sh
for host in $ALLHOSTS
do
some_commands_which_take_considerable_time
screen -X screen sh -c "ssh $host \"apt-get update && apt-get upgrade\""
done
$ screen ./upgrade.sh
, it works, but as there are new windows arriving on the session, they are automatically being switched to. Instead, I'd rather have a version where the active window is fixed unless contained process quits or I switch manually using ^A n.
Bonus points if there is a possibility to preserve windows with exited processes, but keeping them separate from windows with active processes.
You can do this with tmux. For example:
# Start a session named "apt-get" and leave it running in the background.
tmux session-new -d -s apt-get
# Preserve windows with inactive processes.
tmux set-option -t apt-get set-remain-on-exit on
# Start a new window without switching to it. You can also do this from
# within a running tmux session, not just outside of it.
tmux new-window -d -t apt-get -n upgrade-$host \
"ssh $host 'apt-get update && apt-get upgrade'"
Note that you can have multiple windows with the same name, or modify the argument to the -n flag for unique names. The tmux application doesn't care.
As an example, you could name each window "upgrade," but that would make it difficult to identify your SSH sessions at a glance. Instead, this example appends the value of the host variable (populated by your for-loop) to each window name. This makes it easier to navigate, or to programmatically close windows you are no longer interested in. This is especially valuable when you have a lot of unclosed windows displaying terminated processes.
Overall, the syntax is a little cleaner and more intuitive than GNU screen's for this sort of task, but your mileage may vary.
W/r/t preserving windows after a subprocess exits, one possibility is to invoke the zombie command in your screen config file(s), which requires that you specify two keyboard characters that kill or resurrect the window, respectively. E.g.:
zombie KR
Then, K would kill a window whose subprocess has terminated, while R would attempt to relaunch the subprocess in the same window. Note that, in a zombie window, those keys are captured at the top level (i.e., do not precede them with your normal screen control character prefix sequence).
In order to prevent automatic switching to a newly created window, try altering your invocation of screen to something like the following:
screen -X eval 'screen sh -c "ssh $host \"apt-get update && apt-get upgrade\""' 'other'
Thanks to CodeGnome, I'll probably go with tmux as I believe there is no way to do it with screen (that's unfortunate, really!).
To give a better sketch of how it can be used:
#!/bin/sh
tmux new-session -d -s active
tmux new-session -d -s inactive
tmux set-option -t active set-remain-on-exit on
for host in $ALLHOSTS
do
tmux new-window -d -t active: -n upgrade-$host "
./do_upgrade_stuff.sh $host
tmux move-window -s active:upgrade-$host -t inactive:
"
done

How to send commands when opening a tmux session inside another tmux session?

A typical situation may be:
$ tmux
[0] $ ssh example.com
$ tmux attach
[0] $
I open a tmux session, then ssh in to a server and attach to an existing tmux session. At this point I have one tmux session inside another. How do I send commands to the inner tmux session?
Note: Both tmux sessions have the same key bindings.
The send-prefix command can be used to send your prefix keystroke to (the process running in) the active pane. By default, the prefix is C-b and C-b is bound to send-prefix (so that hitting it twice sends a single C-b to the active pane). This is just what we need to access the bindings of the inner tmux instance.
The first C-b is captured by the “outer” tmux instance as its prefix key. The second one is captured by the “outer” tmux instance and triggers its C-b binding (send-prefix). This sends a C-b to the outer instance’s active pane. The process running in this pane is (ultimately, through an ssh instance) the “inner” tmux instance. It captures the C-b as its prefix key. Now your next keystroke will be passed through the outer tmux instance and captured by the inner one to trigger a binding.
To trigger the c binding (new-window) in a second-level instance of tmux, you would type C-b C-b c. For a third-level instance of tmux you would type C-b C-b C-b C-b c.
This doubling for each level can be annoying if you are commonly dealing with multiple layers of tmux. If you can spare some other key, you could make a non-prefixed binding to make things (possibly) easier to type:
bind-key -n C-\ send-prefix
bind-key -n C-^ send-prefix \; send-prefix
Create new window in second-level tmux: C-\ c
Create new window in third-level tmux: C-^ c (or C-\ C-\ c)
If you have a limited number of tmux commands that you want to (easily) send to the lower-level tmux instances, you might instead use send-keys to create some specific bindings (possibly just in your top-level tmux instance):
bind-key C-c send-keys C-b c
bind-key C send-keys C-b C-b c
Create new window in second-level tmux: C-b C-c
Create new window in third-level tmux: C-b C
To access the inner, hold control and hit B twice.
EDIT:
I do NOT recommend use C-q as a bind-key, as it is a default control-key command for
un-freezes the screen and lets screen display continue
A situation happens here, and #Paschalis provides a solution:
if it happens to be twice unlucky (a remote tmux session with C-q as prefix): Type Cltr-q, then :, and enter in tmux: send-keys C-q
Below it is the answer:
To make it simple, add the below line in your ~/.tmux.conf
bind-key -n C-q send-prefix
Then you can directly use C-q as bind-key for your remote tmux.

Resources