bash:在 Tmux 中执行阻止脚本

bash:在 Tmux 中执行阻止脚本

我尝试从一个 bash 文件在两个不同的 TMUX 窗格中运行两个脚本。问题是它们都是阻塞的,因此一旦我从一个窗格执行一个进程,我就无法移动到另一个窗格来执行另一项作业。

我该如何解决这个问题?

我将发布代码示例。

#! /bin/bash
tmux split-window -v
tmux select-pane -t 0
./blocking_script_1
tmux select-pane -t 1 #doesnt happen
./blocking-script_2  #doesnt happen

谢谢

利亚姆

答案1

Tmux 不会神奇地改变你的脚本流程

我正在尝试从一个 bash 文件在两个不同的 TMUX 窗格中运行两个脚本。问题是它们都是阻塞的,因此一旦我从一个窗格执行一个进程,我就无法移动到另一个窗格来执行另一项作业。

你似乎认为在这句话之后

tmux select-pane -t 1

下一行将在选定的窗格中执行。事实并非如此。即使您的主脚本在后台运行这两个脚本,它们仍将在主脚本运行的地方运行。


在 tmux 中发送按键来运行某些操作几乎不是正确的方法

其他答案建议send-keys将命令注入其他窗格。这很麻烦,而且不太可靠(例如,您需要确保那里有一个空闲的 shell,命令行为空)。


在 tmux 中运行某些程序的正确方法

在新创建的窗格中运行脚本。例如:

# create a new window and run the first script
tmux new-window -n my-scripts -c "$PWD" ./blocking_script_1
# split the newly created window and run the second script in a new pane
tmux split-window -v -c "$PWD" -t :my-scripts ./blocking-script_2

或者,如果您在一个窗格中运行主脚本,则可以在另一个窗格中启动另一个脚本后,将其中一个目标脚本作为主脚本的一部分正常运行:

# split the current window and run the second script in a new pane
tmux split-window -v -c "$PWD" ./blocking-script_2
# run the first script normally
./blocking_script_1

如果您想要利用现有的窗格,那么您需要respawn-pane,可能还-k需要终止当前在目标窗格中运行的所有内容(在这种情况下,不要定位您的主脚本运行的窗格,除非这是主脚本必须做的最后一件事)。

您可能会发现和-d选项很有用。它使新创建的窗口/窗格不会成为当前窗格。从现在开始,如果我需要将任何新窗格设为当前窗格,我将使用 和仅创建所有窗格,最后或一次。这是为了避免在 tmux 中的交互式 shell 中粘贴任何代码片段时发生意外,并且 tmux 在整个代码片段到达 shell 之前选择了另一个窗格。new-windowsplit-window-dselect-windowselect-pane


检查结果

请注意,上述任何命令实际上都会在窗格中./blocking-script_2运行 POSIX shell ( )。shell 解释命令 ( )。脚本退出后,shell 也退出。 的一些实现可能足够智能,可以首先检测到它们可以这样做。无论如何,脚本退出后,窗格中没有进程,因此通常窗格会被销毁。sh./blocking-script_2shexec./blocking-script_2

./blocking-script_2退出后,可以通过几个选项来查看输出:

  • 记录输出并且不要介意窗格会消失;
  • 而不是单独./blocking-script_2运行脚本和任何不会自行退出且不会生成(太多)输出的命令,按顺序运行;额外的命令将使窗格保持活动状态;示例:

    # it can be an interactive shell
    tmux … './blocking-script_2; exec bash -i'
    # or simply a loop like this
    tmux … './blocking-script_2; while sleep 3600; do :; done'
    
  • 使用remain-on-exit窗口选项;理论上这似乎是最优雅的。


设置问题remain-on-exit on

请注意,此代码片段是有缺陷的:

# flawed
tmux new-window -dn my-scripts -c "$PWD" ./blocking_script_1
tmux set-window-option -t :my-scripts remain-on-exit on
tmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2
tmux select-window -t :my-scripts

如果./blocking_script_1退出得足够快,窗口可能会在其他命令运行之前被销毁;然后它们将找不到窗口并失败。比较竞争条件. 一些想法:

  • 用于set remain-on-exit设置会话选项,或set-window-option -g remain-on-exit设置全局窗口选项。例如:

    # flawed
    tmux set-window-option -g remain-on-exit on
    tmux new-window -dn my-scripts -c "$PWD" ./blocking_script_1
    tmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2
    tmux select-window -t :my-scripts
    

    然后:

    tmux set-window-option -gu remain-on-exit
    

    所有未明确设置选项的窗口都将使用会话选项或全局窗口选项。这种继承有点复杂,我不会在这里解释它(请参阅man 1 tmux)。当窗格死亡时会检查该选项,因此您无法将其改回,直到您的两个脚本都退出。这种方法可能会在一段时间内影响许多窗口。如果某个其他窗格进程退出,窗格将不会被销毁,而通常它会被销毁。

  • 用于设置在创建新窗口时set set-remain-on-exit设置的会话选项:remain-on-exit

    # flawed
    tmux set set-remain-on-exit on
    tmux new-window -dn my-scripts -c "$PWD" ./blocking_script_1
    tmux set -u set-remain-on-exit
    tmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2
    tmux select-window -t :my-scripts
    

    这完全不会影响现有窗口。但如果又创建了另一个窗口(由另一个脚本或您手动创建),并且时机恰到好处,set-remain-on-exit也会设置该窗口remain-on-exit

  • 创建一个带有不会自行退出的虚拟原始窗格的窗口;设置窗口;生成至少一个应存活的窗格;销毁虚拟窗格:

    # flawed (because of a bug)
    tmux new-window -dn my-scripts 'while sleep 100; do :; done'
    tmux set-window-option -t :my-scripts remain-on-exit on
    tmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_1
    sleep 2
    tmux kill-pane -t :my-scripts.0
    tmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2
    tmux select-window -t :my-scripts
    

    我发现,如果我kill-pane在创建虚拟窗格后过早地使用它,那么我的 tmux 服务器就会崩溃。这不应该发生,它看起来像是一个错误。这种不雅的做法sleep 2在实践中可能会奏效;然而,从理论上讲,在某些情况下,任何延迟可能都不够长。

  • 让新窗口中的每个窗格在exec进入目标脚本之前设置正确的选项:

    # not flawed (AFAIK), cumbersome
    tmux new-window -dn my-scripts -c "$PWD" 'tmux set-window-option -t :my-scripts remain-on-exit on; exec ./blocking_script_1'
    tmux split-window -dvc "$PWD" -t :my-scripts 'tmux set-window-option -t :my-scripts remain-on-exit on; exec ./blocking-script_2'
    tmux select-window -t :my-scripts
    

    “每个窗格”是因为理论上第二个窗格可能在第一个 shell 运行之前就死了tmux set-window-option …。如果发生这种情况,并且只有第一个 shell 尝试更改选项,则保存第二个脚本的窗格将被销毁。

    您想要运行的脚本越多,这种方法就越麻烦。


处理竞争条件

如您所见,原始有缺陷的代码片段可能存在竞争条件。当我们尝试修复它时,新的竞争条件出现了。当以这种方式使用 tmux 时,这种情况很常见。tmux new-window …是 tmux 服务器的命令,tmux这里只是一个客户端。客户端成功退出后,您可以确定已创建一个新窗口;但您无法真正知道其中发生了什么、在哪个阶段,或者窗口是否尚未被销毁。

在您的特定情况下,在 tmux 内的 shell 中运行主脚本并定位其窗口可以保证窗口存在。脚本执行的第一件事应该是设置remain-on-exit on

仍然在 tmux 中处理竞争条件的正确一般方法是wait-for

wait-for [-L | -S | -U] channel
(别名wait:)

当不带选项使用时,会阻止客户端退出,直到使用wait-for -S相同的选项唤醒channel。[…]

我的测试表明tmux wait -S foo永远不会等待。当它被调用时,所有等待的tmux wait foo都会被唤醒,它们会退出。但如果没有tmux wait foo等待,那么“唤醒能力”就会被推迟,下一个(未来)tmux wait foo不会等待。这意味着两个命令可以按任何顺序调用。如果tmux wait foo退出,那么你可以确定tmux wait -S foo已经发生(刚刚发生或过去发生)。

原始片段已修复:

# robust (AFAIK)
tmux new-window -dn my-scripts -c "$PWD" 'tmux wait baz; exec ./blocking_script_1'
tmux set-window-option -t :my-scripts remain-on-exit on
tmux wait -S baz
tmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2
tmux select-window -t :my-scripts

第一个窗格中的 shell 将被阻塞,tmux wait直到主脚本配置完选项。然后第一个脚本多快退出都无关紧要。

两个 shell 都等待一切准备就绪的方法在我看来是比较美观的:

# flawed though
tmux new-window -dn my-scripts -c "$PWD"     'tmux wait baz; exec ./blocking_script_1'
tmux split-window -dvc "$PWD" -t :my-scripts 'tmux wait baz; exec ./blocking-script_2'
tmux set-window-option -t :my-scripts remain-on-exit on
tmux wait -S baz
tmux select-window -t :my-scripts

然后我意识到虽然我可以以任何顺序运行wait和,但对于 2x和来说wait -S这并不正确:waitwait -S

  • 命令waitwaitwait -S将解除对两个 shell 的阻止;
  • 任何其他命令都只会解锁一个 shell。

这可以推广到更多waits。恰好一个可以击败竞争条件。一次wait foo超过一个又会造成另一个竞争条件。wait foo

答案2

这应该可以实现你想要的效果。

#!/bin/bash
tmux split-window -v
tmux send-keys -t 0 ./blocking_script_1 C-m
./blocking-script_2

它不会切换窗格然后运行blocking_script_1,而是只发送执行它所需的按键,然后按回车键(Ctrl-M,又名Enter, 或C-m)。然后它在当前窗格中运行blocking_script_2(没有变化,仍然是窗格-t1)

如果您需要向blocking_script_1提供任何参数,请将整个命令及其所有参数括在双引号中,或使用\转义空格。例如

tmux send-keys -t 0 "./blocking_script_1 arg1 arg2 arg3..." C-m

或者

tmux send-keys -t 0 ./blocking_script_1\ arg1\ arg2\ arg3... C-m

相关内容