I don't think you can do anything like that as tmux
will close all fds above 2 it received (and 0, 1, 2 as well when detached).
See close_range(3, 4294967295, 0)
or equivalent in strace
(or equivalent) output or the closefrom(STDERR_FILENO + 1)
calls in the source.
So you'll need to pass the data some other way, either via a named pipe like you do or environment variable (but can't contain NUL characters) or embedded in the shell code run by tmux
or a socket, temporary file, shared memory, none of which is really going to be simpler or more reliable.
There are a number of issues in your approach though:
- you're using a fixed session name which means the script cannot be used reliably unless you can guarantee no two invocations of it are run at the same time. You could let
tmux
pick the session name and retrieve it via the -P
option of new-session
.
- you don't do any synchronisation: when you run
capture-pane
, there's no guarantee that cat
will have finished or even started. You're doing that loop+kill instead of telling the tmux session to exit when you've retrieved the pane's contents.
- you're embedding the contents of
$fifo
inside the shell code run in the new session. Better to pass as an environment variable.
- you're not checking the exit status of
mktemp
or mkfifo
.
- tmux will read the user's
~/.tmux.conf
which may interfere with the processing
- you'll want to pass
TERM=tmux
to the environment of tput
(or zsh's builtin echoti
equivalent) as that's a tmux
terminal emulator that will end-up interpreting rather than the host terminal you run the script in, so you need to send it the escape sequences that it rather than the host terminal will understand¹.
- with
-S0 -E1
, you're only capturing the first 2 visible rows of the pane
- you could tell tmux to create a pane the same size as that of the host terminal window
- you may want to get the contents of the scrollback buffer as well in case the output doesn't fit in one screen.
So, maybe something like:
#! /bin/zsh -
tmpdir=$(mktemp -d) || exit
trap 'rm -rf -- $tmpdir' EXIT INT TERM HUP QUIT
in=$tmpdir/in out=$tmpdir/out
mkfifo -- $in $out || exit
session=$(
IN=$in OUT=$out tmux -f /dev/null new-session -PEd -x ${COLUMNS:-80} -y ${LINES:-24} '
cat -- "$IN"
echo done > "$OUT"
read may_I_exit < "$IN"
'
) || exit
cat "$@" > $in || exit
read can_I_retrieve_the_output < $out || exit
tmux capture-pane -t $session -pS-
echo you may exit > $in
Then for instance:
$ (TERM=tmux; print -rln foo bar$terminfo[home]b) | ./capture | hexdump -C
00000000 62 6f 6f 0a 62 61 72 0a 0a 0a 0a 0a 0a 0a 0a 0a |boo.bar.........|
00000010 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a |................|
*
00000040 0a 0a |..|
00000042
(note all the 0x0a (LF) bytes as my terminal window is 60 rows high).
(disclaimer: I'm not a tmux user myself, so there might very well be smarter or more correct ways to do it there)
¹ Though in the case of the home
capability, it's unlikely to make a difference in practice as it's unlikely that you'd be using a terminal for which the home
escape sequence is different from that of tmux
' own emulation (\e[H
).