如何检测何时从键绑定触发 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
更可靠的选择.
背景
我有一个需要用户输入的 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
更可靠的选择.