Can ZSH's ZLE input into a child process? - zsh

I am trying to create a keyboard shortcut to automate upgrading my shell to a fully interactive TTY for reverse shells.
Currently, I have a shortcut configured in Konsole to add this to my stdin: python3 -c "import pty;pty.spawn('/bin/bash');". I then need to press ctrl-z twice, once to suspend the running process and once more to execute the following shortcut (adapted from Jonathan Hodgson's blogpost):
## Upgrade shells with keyboard shortcut (also configured in Konsole settings)
function fg-bg() {
if [[ $#BUFFER -eq 0 ]]; then
local backgroundProgram="$(jobs | tail -n 1 | awk '{print $4}')"
case "$backgroundProgram" in
"nc"|"ncat"|"netcat")
# Make sure that /dev/tty is given to the stty command by doing </dev/tty
local columns=$(stty -a < /dev/tty | grep -oE 'columns [0-9]+' | cut -d' ' -f2)
local rows=$(stty -a < /dev/tty | grep -oE 'rows [0-9]+' | cut -d' ' -f2)
notify-send "Terminal dimensions" "Rows: $rows\nColumns: $columns\nstty command on clipboard"
stty raw -echo < /dev/tty; fg; zle -U "stty rows $rows cols $columns
export TERM=\"xterm-256color\""
;;
*)
fg
;;
esac
fi
}
zle -N fg-bg
bindkey '^Z' fg-bg
This works OK, but I'd like to make it better by removing the need to have three shortcuts pressed in quick succession. I thought it might be possible to change Konsole's shortcut to make the process suspend, for example by adding \r\n^Z\r\nzle fg-bg\r\n to the python3 shortcut, but that just adds the text literally (except for carriage returns).

While running a foreground job (in this case, after you submit your command line), the ZLE is no longer active and thus cannot handle your inputs. It’s active only while editing the command line.
When you press ^Z during a foreground job, this causes the terminal driver (not the ZLE) to send the TSTP signal to the it. This causes it to be suspended.
If you want your ^Z to be handled differently for foreground jobs, your best bet might be to configure your terminal to send ^Z^Z when you press ^Z. However, then it will also send this while the ZLE is active. You’re probably better off just pressing ^Z twice yourself.
As for the shortcut you configured in Konsole: Trying to run zle fg-bg from the command line is futile, because, again, when you execute a command line, the ZLE is no longer active.

Related

How to show current command execution time in tmux status line

I'm wondering if I can get a current command execution time (while command is executing) shown somewhere in the tmux's status line?
The question is a bit old but as I came across this while researching this, here's the answer:
for pid in $(tmux list-panes -a -F '#{pane_pid}'); do
for child in $(pgrep -P $pid); do
ps -p $child -ho etime,comm,args;
done;
done
Breaking it down:
First, we get the "first PIDs" for every TMUX pane, change this to narrow down which panels you want to include in your results:
tmux list-panes -a -F '#{pane_pid}'
"First PIDs" mean the PID of the shell, so it'll most likely be your shell (Fish, ZSH, Bash etc). So now we use a for loop to iterate over those PIDs, and for each PID we get the PIDs of processes spawned by that PID, i.e. their children, which is done, inside the loop, by:
pgrep -P $pid
Now we have the child PIDs for each pane, which we can pass to ps to get data on the process. etime is the elapsed time since the process started, comm is the command, and args are the actual arguments, including the actual path of the command being run:
ps -p $child -ho etime,comm,args
The -h flag tells ps to not print the header, while the -o flag sets the output, in this case it's three columns, etime, comm and args.
It's a bit messy as a one-liner, but works:
for pid in $(tmux list-panes -a -F '#{pane_pid}'); do for child in $(pgrep -P $pid); do ps -p $child -ho etime,comm,args; done; done
Or, if you use fish-shell like me, it gets a bit better, though it's still not as clean as I'd like:
for pid in (tmux list-panes -a -F '#{pane_pid}'); for child in (pgrep -P $pid); ps -p $child -ho etime,comm,args; end; end
Still, this does the job, although a bit hackishly. Though, keep in mind, this doesn't check whether the process is running in the foreground, so if you daemonize stuff (&, using Ctrl+Z etc) you will get data for those too, filtering daemons, pipes or whatever may be possible but I wanted all the data so I didn't look into any of those.

Can tmux query physical terminal? (Maybe iTerm2 only)

I'm trying to detect the presence of iTerm2 and if I run the following in iTerm2 (echo -n $'\e[5n'; read -s -t 0.1 line; printf '%q\n' "$line") the terminal responds with $'\033'\[ITERM2\ 3.2.1n$'\033'\[0n
However, if I am running a tmux session in the terminal, then tmux responds and gives me nothing.
Any idea how I can ask tmux to query the physical terminal to report its status?
Footnotes
Here is a description of [5n in the tmux source: https://github.com/tmux/tmux/blob/486ce9b09855ae30a2bf5e576cb6f7ad37792699/tools/ansicode.txt#L577
This might be iTerm2 only, since I haven't seen a response on any other terminal
According to ft in freenode's #tmux (and as seen in this Super User answer), you can use:
'\ePtmux;\e" STUFF_FOR_THE_TERMINAL_HERE "\e\\'
So, it would be something like:
echo -n $'\ePtmux;\e\e[5n\e\\'

Keyboard shortcuts in Tmux deactivated after using xclip

I am using the following configuration in my .tmux.conf to copy text to-and fro from xclip
bind C-c run "tmux save-buffer - | xclip -i -sel clipboard"
bind C-v run "tmux set-buffer \"$(xclip -o -sel clipboard)\"; tmux paste-buffer"
If I run C-prefix C-c for e.g, the text is pasted into another application but after that none of the tmux commands work in the tmux terminal (e.g. C-prefix [ to go into copy-mode etc.)
What is wrong in my config?
According to https://wiki.archlinux.org/index.php/Tmux#X_clipboard_integration:
It seems xclip does not close STDOUT after it has read from tmux's buffer. As such, tmux doesn't know that the copy task has completed, and continues to /await xclip's termination, thereby rendering the window manager unresponsive. To work around this, you can execute the command via run-shell -b instead of run, you can redirect STDOUT of xclip to /dev/null, or you can use an alternative command like xsel.
Updating the PREFIX C-c binding to the following fixed it for me:
bind C-c run "tmux save-buffer - | xclip -i -sel clipboard > /dev/null"
For me, a switch to xsel instead of xclip did the trick.

How to send a command to all panes in tmux?

I like to call :clear-history on panes with a huge scrollback. However, I want to script a way to send this command to all the panes in the various windows.
I know how to send a command to all the windows, courtesy of this question, but how do I send a command to all the panes of which window as well?
send-keys and synchronize-panes from the tmux manpage come to mind, but I'm not sure how to marry them together. But maybe there is a simpler way to do this.
Extra Observations:
Thinking about this a little bit, tmux list-panes -a seems to list all the panes in the current session. Pretty useful to start off with. Where do I go from here?
Have you tried following in tmux window with multiple panes
Ctrl-B :
setw synchronize-panes on
clear history
A bit late to the party but I didn't want to set and unset synchronize-panes just to send one command so I created a wrapper function around tmux and added a custom function called send-keys-all-panes.
_tmux_send_keys_all_panes_ () {
for _pane in $(tmux list-panes -F '#P'); do
tmux send-keys -t ${_pane} "$#"
done
}
I also create a wrapper around the tmux command to simplify calling this function (for convenience). The wrapper and the above code are all here.
This allows me to run tmux send-keys-all-panes <command> or tmux skap <command to send <command> to all panes.
Note that tmux is aliased to my wrapper function tmux_pp.
Update June 2019
Quick illustration on how to configure your own binding for synchronize panes.
Added the following into my tmux.conf (the comments certainly apply to my overall configuration):
# synchronize all panes in a window
# don't use control S, too easily confused
# with navigation key sequences in tmux (show sessions)
unbind C-S
bind C-Y set-window-option synchronize-panes
Now, I can toggle the ability to synchronize commands across multiple panes with <C-a><C-y>.
(Yes, I remapped the bind key to Ctrl a).
my tmux version is 1.9a, and this works for me, one key is enough for both on and off
bind-key X set-window-option synchronize-panes\; display-message "synchronize-panes is now #{?pane_synchronized,on,off}"
None of the above answers worked for me (tmux v2.3), but this did, from the bash command line:
for _pane in $(tmux list-panes -a -F '#{pane_id}'); do \
tmux clear-history -t ${_pane} ; done
A more generalized script, for tmux commands other than 'clear-history' would just replace that element with a parameter, eg. $1. Do be careful if you intend to write a script to handle a series of tmux commands, as "-t ${_pane}" will need to be applied to each.
Note that the -a parameter to tmux list-panes is required to cover all panes in all windows in all sessions. Without that, only panes in your current tmux window will be affected. If you have more than one tmux session open and only want to apply the command to panes within the current session, replace -a with -s (It's all in the tmux man page).
I haven't the mod points to comment directly on each of the above answers, so here's why they weren't working for me:
The problem that I had with #shailesh-garg 's answer was that the sync affected only commands issued within the panes, not tmux commands issued using Ctrl-B : which are outside the panes.
The three problems that I had with #kshenoy 's answer were that:
it sends keystrokes to within a pane, not to the tmux operation
of that pane, so for instance, if one had a bash shell running in
the pane and one used the script to send "clear-history", those
would be the keystrokes that would appear in the bash command-line.
A work-around would be to send "tmux clear-history" or to pre-pend
"tmux " to "$#", but I haven't edited the answer because of my other
problems with the answer;
I couldn't figure out how to send a
new-line character without literally breaking the line;
Even when I did that, sending "tmux clear-history" had no effect.
If you want to send your command to every pane in every window in every session, add this to your .bashrc:
send_command_to_every_pane() {
for session in `tmux list-sessions -F '#S'`; do
for window in `tmux list-windows -t $session -F '#P' | sort`; do
for pane in `tmux list-panes -t $session:$window -F '#P' | sort`; do
tmux send-keys -t "$session:$window.$pane" "$*" C-m
done
done
done
}
You can then use it like this:
send_command_to_every_pane source ~/.bash_profile
Change "$*" to "$#" if you want that behavior, but in my experience this is what you want.
tmux send-keys -t <session id> <command> C-m
Replace the "session id" and "command" accordingly.
This is my utility function to do it, only executing the command when there there is nothing running in the pane.
#!/bin/bash
_send_bash_command_to_session() {
if [[ $# -eq 0 || "$1" = "--help" ]] ; then
echo 'Usage: _send_bash_command_to_session $session_name what ever command you want: '
return
fi
input_session="$1"
input_command="${#:2}"
for _pane in $(tmux list-panes -s -t ${input_session} -F '#{window_index}.#{pane_index}'); do
# only apply the command in bash or zsh panes.
_current_command=$(tmux display-message -p -t ${input_session}:${_pane} '#{pane_current_command}')
if [ ${_current_command} = zsh ] || [ ${_current_command} = bash ] ; then
tmux send-keys -t ${_pane} "${input_command}" Enter
fi
done
}
tmux_set_venv() {
_current_session=$(tmux display-message -p '#{session_name}')
_send_bash_command_to_session ${_current_session} workon $1
}
Example targeting a session called dev, enabling a python virtualenv in all panes that are in bash or zsh, avoiding executing the command in panes with vim or any other executable:
_send_bash_command_to_session dev workon myvirtualenv
or easier to remember: to do it in the current session:
tmux_set_venv myvirtualenv
Find my configuration file with this function.
You can combine synchronize-panes and send-keys in a single shortcut to send commands to all the panes:
Predefined tmux command clear-history:
bind-key C set-option -w synchronize-panes on\; clear-history \; set-option -w synchronize-panes off
Prompt an arbitrary tmux command:
bind-key p command-prompt -p "Panes command: " "set-option -w synchronize-panes on; %% ; set-option -w -u synchronize-panes"
Prompt an arbitrary shell command:
bind-key p command-prompt -p "Panes command: " "set-option -w synchronize-panes on; send-keys %%\\n ; set-option -w -u synchronize-panes"
By default, byobu uses tmux as backend. It's a wrapper that make things much easier:
Shift+F9:
Ctrl+F9:
Shift+F1
Admittedly only semi-related, I found I could make the status background red when I toggle synchronize-panes so it's obvious when I switch back to a window with an unknown synchronize-panes state:
bind-key C-x setw synchronize-panes on \; set-window-option status-bg red \; display-message "pane sync on"
bind-key M-x setw synchronize-panes off \; set-window-option status-bg default \; display-message "pane sync off"

How do I make tmux reorder windows when one is deleted?

I have three windows:
1:zsh 2:vim* 3:htop
When I delete the current window (#2), I have these windows left:
1:zsh 3:htop
How can I make it so that it automatically renumbers them as
1:zsh 2:htop
If I recall correctly, this is the default behavior of GNU Screen. I know I could always :swap-window, but I would like to know if this is possible automatically.
Let's do it more simply.
If you are using tmux below version 1.7, append next line to ~/.tmux.conf:
bind-key C-s run "for i in $(tmux lsw|awk -F: '{print $1}'); do tmux movew -s \$i; done"
You could sort all windows, by typing PREFIX-KEY, then Ctrl + s.
Else, if you are using tmux version 1.7 or above, as already everybody says, append next line to ~/.tmux.conf:
set-option -g renumber-windows on
Since tmux 1.7, you can type just one command to do so:
tmux movew -r
This has now been implemented in C and submitted to tmux CVS on OpenBSD. Will hit the sourceforge portable release soon.
https://github.com/ThomasAdam/tmux-obsd/commit/c42e9b038dcdd36944e76954258a484387bd988f
The bash script below (updated version of [1] to reflect changes in tmux API) reorders tmux sessions. I suggest adding this as a bash function which you can call from any shell.
# re-number tmux sessions
for session in $(tmux ls | awk -F: '{print $1}') ;do
inum=0
for window in $(tmux lsw -t 0 | awk -F: '/^[0-9*]/ {print $1}') ;do
if [ ${window} -gt ${inum} ] ;then
echo "${session}:${window} -> ${session}:${inum}"
tmux movew -d -s ${session}:${window} -t ${session}:${inum}
fi
inum=$((${inum}+1))
done
done
[1] http://brainscraps.wikidot.com/tmux-renum

Resources