1

背景

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

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

问题

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

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

我如何(自动)检查我的脚本是从键绑定调用的,还是在终端中运行?理想情况下,这应该没有用户指定的命令行参数。其他人可能会使用该脚本,因此,我想避免通过“被遗忘的标志”引入任何用户错误的可能性。


尝试“解决方案”

这个问题建议检查是否stdout要去终端,所以我创建了这个测试脚本:

#!/usr/bin/env bash

rm -f /tmp/detect.log

logpass() {
    echo "$1 IS opened on a terminal" >> /tmp/detect.log
}
logfail() {
    echo "$1 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
4

2 回答 2

1

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

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

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

在你的脚本中。

于 2019-03-05T07:05:20.997 回答
0

假阳性

正如 Google 上的大多数结果所暗示的那样,当它不在终端中运行时,您可以使用tty它通常会返回“not a tty” 。但是,这似乎与bindsym execi3/Sway 中通过调用的脚本不同:

/dev/tty1  # From a keybind
/dev/pts/6 # In a terminal
/dev/tty2  # In a console

虽然tty | grep pts会部分回答这个问题,但它无法区分在控制台中运行与在尝试显示 GUI 时不需要的键绑定。


“某种”解决方案

通过键绑定触发似乎总是systemd作为父进程。考虑到这一点,这样的事情可能会起作用:

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

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


更好的解决方案

一种更强大的方法是使用ps. 根据PROCESS STATE CODES手册页:

对于 BSD 格式和使用 stat 关键字时,可能会显示其他字符:

<    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

这里的关键在+最后一行。要在终端中检查,您可以调用which 将在“交互式”运行时ps -Sp <PID>具有STAT值,或者它是否通过键绑定触发。SsS+

由于您只需要该STATE列,因此您可以进一步清理它,-o stat=这也将删除标题,然后通过 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 键绑定,甚至适用于原始控制台窗口,使其成为比tty上述更可靠的选项。

于 2019-03-15T05:56:15.863 回答