在 tmux 窗格中显示在后台运行的命令

在 tmux 窗格中显示在后台运行的命令

我经常使用+将nvimranger其他应用程序发送到后台,然后忘记它在哪个窗格中打开 - 强制数十个窗格是一项非常繁琐的任务。Ctrlz

有没有类似于${pane_current_command}但用于后台任务的东西?

#(echo $(jobs -l))不起作用可能是因为总是连接到不同的会话。

答案1

有没有类似于${pane_current_command}但用于后台任务的东西?

不,这并不像你想象的那么容易。

#(echo $(jobs -l))不起作用可能是因为总是连接到不同的会话。

jobs是 shell 的内置命令。该命令将显示运行它的 shell 的作业。对于您来说,一切都取决于您如何运行代码片段以及如何引用它。

  • 如果您tmux …在想要了解其作业的 shell 中直接运行使用此代码片段,并且您不使用单引号$(),因此它在可执行文件运行之前就被 shell 扩展tmux,那么它会起作用(“有点”,因为建议使用正确的引用,但这echo不是这里最好的工具;考虑printf '%s\n')。

  • 如果您在tmux …不是您想要了解其作业的 shell 中运行使用此代码片段的 shell,并且您没有用单引号引起来$(),它将被错误的 shell 扩展(即将jobs运行)。

  • 如果你

    • 运行tmux …使用此代码片段并单引号$()
    • 或者在可执行文件运行$()之前没有可以展开的外壳tmux
    • 或者直接在 tmux 中使用该代码片段(prefix:或通过键绑定)

    然后$()将由 生成的 shell 展开,以tmux处理 的内部#()。请注意,在这种情况下echo或者printf是不需要的,甚至是有害的。这没什么大不了的,因为处理其中内容的 shell#()无论如何都不是您想要的。

我不知道任何 shell 中有任何方法(接口)可以让你查询 shell 中的作业从外部


注入命令(不要)

您可以做的一件麻烦事是将其注入jobs -lEnter到窗格中运行的实际 shell 中,就像您输入它一样;tmux 肯定可以做到这一点(send-keys)。然后您需要捕获并隔离输出;tmux 可能可以做到这一点。但是:

  • 窗格可能处于复制模式或者其他模式;
  • 窗格中的进程可能不是shell;
  • 或者它可能是一个忙于运行某些东西的 shell(键将转到子程序);
  • 或者当你开始注入时它的命令行可能不为空;
  • 即使注入顺利,也可能有其他(后台)进程输出到控制台。

这种方法非常不可靠。您可以通过让命令本身请求 tmux 打印某些内容来改进它。示例:

tmux run-shell "printf '%s\n' '$(jobs -l)'"

如果你在想要知道其作业的 shell 中运行它,则双引号$()(双引号)将由该 shell 扩展,因此jobs将返回您想要的内容。最后printf将在其他jobsshell 以字符串形式获取输出(因此不适用)。

但在printf运行之前,tmux将解释字符串,内壳将解释字符串。如果jobs输出任何可以扩展的内容tmux run-shell(例如#{pane_id}),那么它将被扩展。如果它输出单引号,那么它将过早关闭我们的单引号。可能会发生语法错误,但这不是最糟糕的情况。可能会发生代码注入。清理输出可能会有所jobs帮助,但要正确做到这一点并不容易。

为了实现自动化,您需要将命令注入到 shell(实际上:许多 shell)。正如我们所见,通过发送密钥进行注入很麻烦。还有另一种方法。


贝壳陷阱(没什么用)

如果你在 tmux 打开的每个 shell 中都定义了此函数:

_tmux_show_jobs () { tmux run-shell -t "$TMUX_PANE" "printf '%s\n' '$(jobs -l)'"}

然后在每个 shell 中发送trap一些信号,以便触发该函数,然后让 tmux 遍历可用窗格并发送信号#{pane_pid}– 然后您可以让每个 shell 将的输出传递jobs -l给 tmux,以显示在其窗格中。但是:

  • 仍然会发生不必要的解析和替换。您可以通过重定向jobs -l到临时文件(每个 shell 独立)并cat执行其中的文件来修复此问题tmux run-shell
  • 无论你选择什么信号,你都不想盲目地将它发送到每个窗格。可以创建一个运行任何东西的窗格(我的意思不一定是 shell)。可以exec从 shell 中执行任何操作。任意工具都会以自己的方式对信号做出反应,你不能假设任何信号都是安全的。特别SIGUSR1SIGUSR2 默认终止进程. 其他信号是有意义的,捕获它们可能会在您不想要的时候触发该功能。
  • 即使你设法让 tmux 只向 shell 发送信号并在每个 shell 中捕获所选信号,在许多情况下 shell 也不会立即对信号做出反应。我认为这使整个基于陷阱的方法失效。

没有jobs(终于有用了)

我们可以利用你并不想知道的事实全部作业或后台任务。您想要查找停止。这些可以从外部查询。考虑这个基本代码:

#!/bin/sh

unset IFS
for p in $(
   tmux list-panes -F '#{pane_id}'
)
do
   tmux run-shell -t "$p" 'ps -o s,pid,args -g #{pane_pid} | sed -e "/^T/!d" -e "s/. //"'
done

笔记:

  • for p in $( … ); do …总体来说错误但这里的格式定义得很好,语法将使用 default IFS。此外,如果你从 tmux 内部运行代码,那么将至少有一个窗格,因此$()永远不会生成完全零个单词(这会导致语法错误)。
  • 相反,-g #{pane_pid}它可能是--ppid #{pane_pid},但是
    • 它不是 POSIX;
    • -g包括孙子,所以如果你exec在原始 shell 中运行(不是)一个 shell,或者sudo su -,然后运行另一个进程并停止它,代码仍然会找到它;我认为这样更好。
  • list-panes默认情况下列出当前窗口中的所有窗格。如果您想要遍历当前会话中的所有窗格,请使用list-panes -s

不过,这也有一个缺点。每个窗格的结果(如果不为空)将以复制模式显示在窗格中。但如果窗格已经处于某种非默认模式,结果可能永远不会显示或附加到显示的任何内容中。特别是如果结果可见并且您第二次运行代码,则会出现重复的行。

我发现确保在打印结果之前清除窗格并且始终显示结果的唯一方法是退出窗格所处的任何非默认模式。以下代码可以做到这一点。如果结果为空,它会故意不更改模式。

#!/bin/sh

unset IFS
tmux list-panes -F '#{pane_id} #{pane_pid}' | while
   read -r id pid
do
   output="$(ps -o s,pid,args -g "$pid" | sed -e '/^T/!d' -e 's/. //')"
   [ "$output" ] && {
      [ "$(tmux display-message -p -t "$id" "#{pane_in_mode}")" -eq 1 ] && tmux send-keys -t "$id" q
      tmux run-shell -t "$id" "printf 'Stopped processes:\n \n%s\n' '$output'"
   }
done
exit 0

我将其保存为_tmux-find-stopped我的指向的目录中$PATH。我将这行添加到我的~/.tmux.conf

bind -T prefix j run-shell '_tmux-find-stopped'

在我之后prefix:source-file ~/.tmux.confEnter,我可以使用 找到已停止的进程prefixj。新的 tmux 服务器将默认提供该文件。

相关内容