如何检测何时从键绑定触发 bash 脚本

How to detect when a bash script is triggered from keybinding

背景

我有一个需要用户输入的 Bash 脚本。它可以是 运行 通过在终端中调用它 通过按下在 i3(或 Sway)配置文件中注册的键盘快捷键,如下所示:

bindsym --release $mod+Shift+t exec /usr/local/bin/myscript

问题

我知道我可以使用 read -p 在终端中进行提示,但是当通过键绑定触发脚本时,这显然不起作用。在这种情况下,我可以使用 Yad 之类的东西来创建 GUI,但我无法检测到它不是 "in a terminal"。本质上我希望能够做这样的事情:

if [ $isInTerminal ]; then
    read -rp "Enter your username: " username
else
    username=$(yad --entry --text "Enter your username:")
fi

我如何(自动)检查我的脚本是从键绑定调用的,还是 运行在终端中调用的? 理想情况下应该是 没有 用户指定的命令行参数。其他人可能会使用该脚本,因此,我想避免通过 "forgotten flags".

引入任何可能的用户错误

已尝试 "Solution"

This question 建议检查 stdout 是否进入终端,所以我创建了这个测试脚本:

#!/usr/bin/env bash

rm -f /tmp/detect.log

logpass() {
    echo " IS opened on a terminal" >> /tmp/detect.log
}
logfail() {
    echo " IS NOT opened on a terminal" >> /tmp/detect.log
}

if [ -t 0 ]; then logpass stdin; else logfail stdin; fi
if [ -t 1 ]; then logpass stdout; else logfail stdout; fi
if [ -t 2 ]; then logpass stderr; else logfail stderr; fi

但是,这并不能解决我的问题。无论我是在终端中 运行 ./detect.sh 还是通过键绑定触发它,输出总是相同的:

$ cat /tmp/detect.log
stdin IS opened on a terminal
stdout IS opened on a terminal
stderr IS opened on a terminal

看来解决实际问题的最简单方法是将 i3 绑定更改为

bindsym --release $mod+Shift+t exec /usr/local/bin/myscript fromI3

并做

if [[ -n "" ]]; then
    echo "this was from a keybind"
else
    echo "this wasn't from a keybind"
fi

在你的脚本中。

误报

正如 Google 上的大多数结果所暗示的那样,您 可以 使用 tty 通常 return "not a tty" 当它在终端中不是 运行 时。但是,这似乎与通过 i3/Sway:

中的 bindsym exec 调用的脚本不同
/dev/tty1  # From a keybind
/dev/pts/6 # In a terminal
/dev/tty2  # In a console

虽然 tty | grep pts 会部分回答问题,但它无法区分控制台中的 运行 与键绑定,如果您尝试这样做,您将不会想要显示 GUI。


"Sort of" 解决方案

通过键绑定触发似乎总是有 systemd 作为 parent 过程。考虑到这一点,像这样的东西 可以 工作:

{
    [ "$PPID" = "1" ] && echo "keybind" || echo "terminal"
} > /tmp/detect.log

systemd 进程将始终以 1 作为其 PID 可能是一个安全的假设,但不能保证每个使用 i3 的系统也将使用 systemd,因此这可能是最好的避免了。


更好的解决方案

更稳健的方法是使用 ps。根据手册页中的 PROCESS STATE CODES

For BSD formats and when the stat keyword is used, additional characters may be displayed:

<    high-priority (not nice to other users)
N    low-priority (nice to other users)
L    has pages locked into memory (for real-time and custom IO)
s    is a session leader
l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
+    is in the foreground process group

这里的关键是最后一行的+。要在终端中检查您可以调用 ps -Sp <PID>,当 运行 "interactively" 时,它的 STAT 值为 Ss,如果是 S+通过键绑定触发。

因为您只需要 STATE 列,您可以使用 -o stat= 进一步清理它,这也将删除 headers,然后通过 grep 管道为您提供以下内容:

is_interactive() {
    ps -o stat= -p $$ | grep -q '+'
}

if is_interactive; then
    read -rp "Enter your username: " username
else
    username=$(yad --entry --text "Enter your username:")
fi

这不仅可以在终端模拟器中通过 i3/Sway 键绑定工作,而且甚至可以在原始控制台 window 中工作,使其成为比上面的 tty 更可靠的选择.