当脚本作为 UDEV 触发器调用时没有 /dev/tty

当脚本作为 UDEV 触发器调用时没有 /dev/tty

我有安装了原装 raspbian 的 raspberry pi。我还安装了 RetroPI,运行良好。

我想要做的是编写一个小型自动启动脚本,当游戏手柄通过蓝牙连接时,该脚本将启动模拟站(retropi 主运行脚本)。我可能制定了简单的 udev 规则

pi@raspberrypi:~ $ cat /etc/udev/rules.d/99-zlocal.rules
SUBSYSTEM=="bluetooth",SUBSYSTEMS=="amba", ATTRS{id}=="00241011", ACTION=="add", RUN+="/usr/local/bin/emulator-controll.sh start"

其工作符合预期,当游戏手柄配对时,脚本就会运行。

问题是,如果我通过 SSH 运行我的脚本

sudo emulator-controll.sh start        #sudo is optional here, but I want the same conditions as in udev trigger context

emulationstation 正如预期的那样。但是,如果使用相同的脚本作为上述 udev 规则的操作触发器,则会失败出现类似错误

Wed Aug 21 00:02:33 CEST 2019
Invoking user: root
Invoked start command
Invoking emulation station start command
Done
'unknown': I need something more specific.
tput: unknown terminal "unknown"
Segmentation fault
/usr/bin/emulationstation: line 23: /dev/tty: No such device or address
tput: unknown terminal "unknown"

我认为由于某些原因(我是 Linux 新手),无法从 udev 触发器上下文访问 tty。我能以某种方式修复它吗?

PS. 我曾尝试运行在启动时启动的后台守护进程(使用命名管道),但效果完全相同 - 在脚本执行上下文中没有 tty,这让我认为这是后台任务的一般行为

在我看来,脚本本身是无关紧要的,但是为了清楚起见,我也会提供它:

#!/bin/bash
#/bin/date >> /tmp/udev.log
#env >> /tmp/udev.log
#echo "Connected" > /dev/pts/2

log=/var/log/emulator-controll.log

{
readarray -t PIDS <<< $(ps aux | grep [e]mulationstation | awk '{print $2}')
echo ">${PIDS[@]}<"
echo >> $log
/bin/date >> $log
echo "Invoking user: $(whoami)"
case "$1" in
        start )
                echo "Invoked start command" >> $log
                if [[ "${#PIDS[@]}" -ge 3 ]]; then
                        echo "Emulator is already running. No action is taken" >> $log
                else
                        echo "Invoking emulation station start command" >> $log
                        export DISPLAY=:0
                        sudo -u pi nohup emulationstation  &
                fi
                echo "Done" >> $log
                ;;
        stop )
                echo "Invoked stop command" >> $log
                if [[ -z "${PIDS[0]}" ]]; then
                        echo "Emulator is not running, no action is taken" >> $log
                else
                        echo "Killing processes with PIDs ${PIDS[@]}" >> $log
                        for pid in "${PIDS[@]}"; do
                                kill $pid
                        done
                        echo "Done" >> $log
                fi
                ;;
        *)
                echo "Usage: $0 {start|stop}"
esac

} >> $log 2>>$log

答案1

我没有尝试将脚本附加到 TTY 或将 tty 分配给脚本,而是通过以下方式将命令直接注入目标 tty 的输入缓冲区来实现我的目标:

sudo writevt /dev/tty1 "emulationstation $(echo -ne '\r')"

在主终端上输入命令并按“Enter”键。

PS.writevt是 Raspbian 的一部分console-tools,并且至少在今天可用

答案2

当内核触发动作并udev作为规则的一部分运行脚本时,这些脚本的环境受到很大限制。

一台 Linux 计算机可以同时登录多个用户,每个用户有多个 tty,因此当然从内核运行的脚本没有 tty:没有用户启动过此脚本,因此即使可以为其分配 tty,它应该是正在使用的许多 tty 中的哪一个?X 连接也是如此(这也是经常被问到的问题)。

还有更多限制,如下图所示man 7 udev

跑步

将程序添加到要为特定设备执行的程序列表中。这只能用于非常短的运行任务。长时间运行事件进程可能会阻止此设备或相关设备的所有后续事件。长时间运行的任务需要立即与事件进程本身分离。如果指定了选项 RUN{fail_event_on_error},并且执行的程序返回非零,则事件将被标记为失败,以便稍后处理。

如果我没看错您的脚本,您想要在给定的 X 连接上启动或停止某个将运行很长时间的程序。这只有在您将任务分为两个任务时才有效:让某种守护程序在相应的 X 服务器启动时启动(需要启动 X 服务器以便您获取授权 cookie),然后在 udev 运行脚本中告诉守护程序它应该执行某些操作,例如启动或停止服务。

这样udev运行脚本就可以立即退出了。

如果你正在使用 systemd,你也可以从 udev 规则启动一个单元(例如参见这里)。

编辑

如果您想使用,请考虑将运行于您的恶魔上的tty1登录服务()更改为,然后您可以从脚本中触发它。gettytty1

例如这里了解可能的解决方法的详细信息,请根据需要进行更改。

相关内容