bash:在 Tmux 中执行阻塞脚本

1 linux terminal bash tmux

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

我怎样才能克服这个问题?

我将发布代码示例。

#! /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
Run Code Online (Sandbox Code Playgroud)

谢谢

利亚姆

Kam*_*ski 5

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

\n\n
\n

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

\n
\n\n

你似乎认为在这行之后

\n\n
tmux select-pane -t 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

下一行将在选定的窗格中执行。这不是真的。即使您的主脚本在后台运行这两个脚本,它们仍然会在主脚本运行的地方运行。

\n\n
\n\n

发送密钥来在 tmux 中运行某些东西几乎不是正确的方法

\n\n

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

\n\n
\n\n

在 tmux 中运行某些东西的正确方法

\n\n

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

\n\n
# create a new window and run the first script\ntmux new-window -n my-scripts -c "$PWD" ./blocking_script_1\n# split the newly created window and run the second script in a new pane\ntmux split-window -v -c "$PWD" -t :my-scripts ./blocking-script_2\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
# split the current window and run the second script in a new pane\ntmux split-window -v -c "$PWD" ./blocking-script_2\n# run the first script normally\n./blocking_script_1\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您想利用现有的窗格,那么您需要respawn-pane,可能需要-k杀死当前在目标窗格中运行的任何内容(在这种情况下,不要以主脚本运行的窗格为目标,除非它是主脚本中的最后一个东西)脚本必须做)。

\n\n

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

\n\n
\n\n

检查结果

\n\n

请注意,上述任何运行的命令实际上都在窗格中./blocking-script_2运行 POSIX shell ( )。shshell 解释命令 ( ./blocking-script_2)。脚本退出后,shell 也退出。的一些实现sh可能足够聪明,可以首先检测exec到它们的能力。./blocking-script_2脚本退出后,窗格中没有进程,因此通常窗格会被破坏。

\n\n

./blocking-script_2退出后有几个选项可以查看输出:

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

    \n\n
    # it can be an interactive shell\ntmux \xe2\x80\xa6 \'./blocking-script_2; exec bash -i\'\n# or simply a loop like this\ntmux \xe2\x80\xa6 \'./blocking-script_2; while sleep 3600; do :; done\'\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
  • 使用remain-on-exit窗口选项;理论上这似乎是最优雅的。

  • \n
\n\n
\n\n

设置问题remain-on-exit on

\n\n

请注意,此片段有缺陷:

\n\n
# flawed\ntmux new-window -dn my-scripts -c "$PWD" ./blocking_script_1\ntmux set-window-option -t :my-scripts remain-on-exit on\ntmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2\ntmux select-window -t :my-scripts\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

    \n\n
    # flawed\ntmux set-window-option -g remain-on-exit on\ntmux new-window -dn my-scripts -c "$PWD" ./blocking_script_1\ntmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2\ntmux select-window -t :my-scripts\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    然后:

    \n\n
    tmux set-window-option -gu remain-on-exit\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    所有没有显式设置该选项的窗口都将使用会话选项或全局窗口选项。这种继承有些复杂,我在这里不再解释(请参阅参考资料man 1 tmux)。当窗格消失时会选中该选项,因此在两个脚本退出之前您无法将其更改回来。这种方法可能会在一段时间内影响许多窗口。如果其他某个窗格进程退出,该窗格将不会被破坏,而通常情况下它会被破坏。

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

    \n\n
    # flawed\ntmux set set-remain-on-exit on\ntmux new-window -dn my-scripts -c "$PWD" ./blocking_script_1\ntmux set -u set-remain-on-exit\ntmux split-window -dvc "$PWD" -t :my-scripts ./blocking-script_2\ntmux select-window -t :my-scripts\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    这根本不会影响现有的窗口。不过,如果创建了另一个窗口(由另一个脚本或您手动创建)并且时机恰到好处,set-remain-on-exit也会对其进行设置remain-on-exit

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

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

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

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

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

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

    \n\n

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

  • \n
\n\n
\n\n

处理竞争条件

\n\n

正如您所看到的,原始的有缺陷的代码片段可能受到竞争条件的影响。当我们尝试修复它时,会出现新的竞争条件。以这种方式使用 tmux 时,这种情况很常见。tmux new-window \xe2\x80\xa6是tmux服务器的命令,tmux这里只是一个客户端。客户端成功退出后,可以确定已经创建了一个新窗口;但你无法真正知道其中发生了什么,在什么阶段,或者窗口是否还没有被破坏。

\n\n

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

\n\n

在 tmux 中处理竞争条件的正确通用方法仍然wait-for

\n\n
\n

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

\n\n

当不带选项使用时,阻止客户端退出,直到使用wait-for -S相同的channel. [\xe2\x80\xa6]

\n
\n\n

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

\n\n

修复了原始片段:

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

第一个窗格中的 shell 将被阻止,tmux wait直到主脚本配置该选项之后。那么第一个脚本多久退出并不重要。

\n\n

两个 shell 都等待一切准备就绪的方法在美学上对我来说是令人愉悦的:

\n\n
# flawed though\ntmux new-window -dn my-scripts -c "$PWD"     \'tmux wait baz; exec ./blocking_script_1\'\ntmux split-window -dvc "$PWD" -t :my-scripts \'tmux wait baz; exec ./blocking-script_2\'\ntmux set-window-option -t :my-scripts remain-on-exit on\ntmux wait -S baz\ntmux select-window -t :my-scripts\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    \n
  • 命令wait, wait,wait -S将解锁两个 shell;
  • \n
  • 任何其他命令只会解锁一个 shell。
  • \n
\n\n

这可以推广到更多waits。确切地说,一个人wait foo可以击败竞争条件。wait foo一次不止一个会产生另一种竞争条件。

\n