我正在编写一个计划的 shell 脚本。目标是为 Minecraft 服务器编写一个简单的备份脚本。该服务器以自己的命名方式运行屏幕。
我假设我的脚本包含类似以下内容的内容
screen -S $(screen_name) -p 0 -X stuff "save-all^M"
服务器命令需要一些时间。我的问题是,脚本会立即继续,还是会等到所访问屏幕内的命令完成?如果它立即返回,我该如何等待它(在这种情况下,命令save-all
会保存当前世界。下一步是将文件复制到备份文件夹中,因此我显然想等到世界正确保存)?
答案1
不会。该screen -X stuff
命令会立即返回,因为它是没意识到首先,它被要求运行一个命令,当该命令完成时更是如此——它所做的就是注入按键(这就是您必须手动添加的原因^M
)。一旦发送了虚假的 tty 输入,命令就会返回,您的脚本将继续。
一般来说,作为一个终端仿真器,screen 并不知道在任何特定的终端窗口中到底发生了什么——没有任何东西可以将 shell 提示符(或任何其他交互式控制台提示符)与常规输出区分开来。
(这并非根本不可能——一些终端仿真器允许 shell 输出“输出开始/结束”和“提示开始/结束”标记,例如VSCode将向 Bash 注入特殊配置以实现此目的 - 但它需要终端仿真器的支持和每个会显示输入提示的程序都会进行合作,而 Screen 和 Minecraft 目前都不会这样做。
另一方面,你的脚本可能会更容易,因为它只处理一个程序,这意味着它只需要等待具体的提示符出现,而不是一般的提示符。您可以通过一个循环来实现这一点,该循环将使用 查询 Screen 的当前缓冲区“内容” -X hardcopy
,并且如果该缓冲区的最后一行是不是Minecraft 提示,睡眠 1 秒并重复。
这与程序的工作方式类似expect
:可以编写一个 Expect 脚本来自动执行各种交互式输入,但这种脚本的核心始终是一组和expect "this"
,expect "that"
即预先知道某些特定文本是“提示”并等待该文本。
[...]
expect "Password:" { send "$password\r" }
expect ">" { send "enable\r" }
expect "Password:" { send "$enablepwd\r" }
expect "#" { send "show run\r" }
其他 Minecraft 管理脚本的作用(例如这个项目(最近从 Screen 切换到 tmux)似乎是 1)盲目地同时提交“save-all”和“stop”,2)等待服务器处理机器人命令,直到它自己退出。也就是说,不是等待命令要完成,他们等待结果该命令。
同样,如果你想等待节省要完成,您无需等待“save-all”命令 - 您可以改为使用inotifywait
等待,直到服务器完成写入特定文件。这可能有点棘手(通常您需要使用“coproc”来启动 inotifywait前发出命令,这样你就不会错过事件),但由于保存可能需要一段时间,因此大概之后就可以这样做了。
echo "waiting..."
inotifywait -q -e close_write /path/to/game
echo "probably done!"
有些程序故意最后创建一个特定的文件,以便其他工具可以等待它出现:
echo "waiting..."
until [ -e /path/to/marker_file ]; do sleep 1; done
echo "marker file showed up"