Bash: 如何判断终端是否由第三方应用程序打开

我希望我的bash脚本(特别是我的~/.bashrc)只在终端由我直接打开时执行某些操作,而在通过应用程序如VS Code打开时执行其他操作。 我该如何确定情况是什么?是否有相应的变量可以判断? 提前感谢您的帮助。

1有一种方法,我的第一反应是采用 https://askubuntu.com/a/1042727/295286 中的第二个示例。尝试打开 VS 并运行 env 命令,看看是否有我们可以使用的特定于 VS 的变量。 - Sergiy Kolodyazhnyy
1如果没有任何东西,请尝试另一种方式:查看您的终端仿真器是否设置了一个变量。我使用yakuake并设置了一个变量PULSE_PROP_OVERRIDE_application.name=Yakuake,而且xterm在我的电脑上设置了XTERM_VERSION=XTerm(322) - dessert
@SergiyKolodyazhnyy你能否请你为环境变量的方法给出一个答案? - dessert
@dessert 我本来想帮忙的,但是我没有安装VS,而且OP也没有回复我们是否有任何特定的环境变量可以利用。 - Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy 我也没试过,但问题标题说的是“第三方应用程序”,我想它的工作方式应该和任何终端模拟器一样 - 我认为像 env >env_term1 这样的答案在一个模拟器中,env >env_term2 在第二个模拟器中,并且如何使用 diff env_term{1,2} 的结果非常有用。毕竟,OP说的是例如 VS Code - dessert
VS Code 可以打开一个终端供您输入内容,也可以运行一些脚本。 - Thorbjørn Ravn Andersen
4个回答

你可能可以通过追溯 shell 的祖先并确定它是由与"你"相等的东西或其他程序启动的方式来实现。

获取 shell 的 PID(进程 ID),然后从中获取其 PPID(父进程 ID)。继续向上直到找到告诉你它从哪里来的东西。你可能需要在你的系统上尝试一下,至少我不知道它是否普适。

例如,在我的系统上,获取一个 shell 的 PID 并使用 ps 显示它是 bash

$ echo $$
18852
$ ps --pid 18852
  PID TTY          TIME CMD
18852 pts/1    00:00:00 bash

获取18852的PPID:
$ ps -o ppid= -p 18852
18842

找出PPID(18842)是什么:
$ ps --pid 18842
  PID TTY          TIME CMD
18842 ?        00:00:02 gnome-terminal

我们可以看到这是gnome-terminal,即终端仿真器/终端窗口。如果您的shell由其他程序启动而不在终端仿真器窗口中运行,那么这可能已经足够了。
如果这还不够好,请再上一层:
$ ps -o ppid= -p 18842
 2313
$ ps --pid 2313
  PID TTY          TIME CMD
 2313 ?        00:00:00 init

这告诉我们 gnome-terminal 是由 init 启动的。我怀疑你的 shell 是由另一个程序启动的,那里可能会有一些不同的东西。

...或者通过执行pstree -s $$的结果来查看。 - steeldriver
9这告诉我们 gnome-terminal 是由 init 启动的。我觉得 init 不太可能启动终端窗口。相反,启动 gnome-terminal 的进程很可能已经停止,而 gnome-terminal 重新被赋予 init 为父进程。检查 gnome-terminal,似乎它会进行双重分叉。因此,在执行时,它首先对自身进行分叉并终止原始进程,在新进程中继续运行。 - JoL
@JoL 说得对。但是init进程并不是pid 1,不确定这会不会改变什么。 - kasperd
非常感谢!我发现无论是VS Code还是Eclipse都不能将终端作为gnome-terminal的子进程运行。我在if [ $(pstree -s $$ | grep "gnome-terminal" -c) -gt 0 ]; then ...下执行了我的命令,结果成功了。 - PaperBag

就 Visual Studio Code 而言,显然有一种方法可以为集成终端设置附加环境变量。因此,请设置 Visual Studio 使用此配置:
"terminal.integrated.env.linux": {
  "visual_studio": "true"
}

~/.bashrc文件中:
if [ -n "$visual_studio" ]; then
    # do something for Visual Studio
else
    # do something else for other types of terminal
fi

一般来说,你可以依赖于给予进程的环境。例如,the $TERM variable,并且运行类似的if..then...else...fi分支,用于判断[ "$TERM" = "xterm" ]或其他情况。根据具体情况,你可以通过在每个控制台中运行env来调查环境的差异,将其保存到文件中,如env > output_console1.txt,然后使用diff output_console1.txt output_console2.txt进行比较,正如评论中的dessert所建议的那样

$Env:var 不是 Bash 中的环境变量语法。这看起来更像是 Powershell 的东西。 - Dietrich Epp
@DietrichEpp 是的,我最初是在研究如何在Visual Studio中设置额外的环境变量,但忽略了答案是使用PowerShell。所以$foo就足够了。咖啡可能不够。 - Sergiy Kolodyazhnyy
对于没有环境设置的第三方程序的一般情况,您可以在运行程序之前在包装器中设置自定义环境变量。请参阅我的回答 - Peter Cordes

如果你在谈论一个特定的第三方应用程序,那么可以使用环境变量。大多数程序在fork+exec新进程时会传递整个环境而不做任何改变。
所以,用一个自定义的环境变量来启动这个应用程序,你可以进行检查。例如,可以为它创建一个别名,比如`alias vs=RUNNING_FROM_VSCODE=1 VSCode`,或者创建一个类似下面的包装脚本:
#!/bin/sh
export RUNNING_FROM_VSCODE=1
exec VSCode "$@"

在你的.bashrc文件中,你可以这样做。
if (($RUNNING_FROM_VSCODE)); then
   echo "started from inside VSCode"
   # RUNNING_FROM_VSCODE=0  # optional if you only want the immediate child
fi

一个bash算术语句(( ))在表达式求值为非零整数时为真(这就是我之前使用1的原因)。空字符串(对于未设置的环境变量)为假。它适用于bash布尔变量,但你也可以使用true并通过传统的POSIX方式进行检查。
if [ "x$RUNNING_FROM_VSCODE" = "xtrue" ]; then
   echo "started from inside VSCode"
fi

如果您的应用程序主要是为其子进程清除环境,但仍然保持$PATH不变,您可以在包装器中使用以下代码:
#!/bin/sh
export PATH="$PATH:/dev/null/RUNNING_FROM_VSCODE"
exec VSCode "$@"

并且使用类似于bash的模式匹配来检查它,例如[[ "${PATH%RUNNING_FROM_VSCODE}" != "$PATH" ]],以检查从PATH中去除后缀是否会改变它。

当程序在寻找未找到的外部命令时,这应该只会无害地进行一次额外的目录查找。在任何系统上,/dev/null绝对不是一个目录,因此可以安全地将其用作一个虚假的目录,如果PATH搜索在较早的PATH条目中找不到所需内容,它将迅速导致ENOTDIR错误。


封装脚本通常是一个明智的方法,所以点赞+1。唯一的小缺点是,如果你有3个程序,你可能想要有3个封装脚本或者一个封装脚本带有3个不同的参数,这可能会变得繁琐。尽管如此,这仍然是一个可靠的方法。 - Sergiy Kolodyazhnyy

这是我的两分钱。只需将其添加到您的.bashrc文件中即可。将terminals替换为您喜欢的终端,并将export命令替换为您自己的命令。
run_in_terminal(){
  local parent_command="$(ps --no-headers --pid $PPID -o command | awk '{print $1;}')"
  local parent="$(basename $parent_command)"
  local terminals=( gnome-terminal st xterm ) # list your favorite terminal here
  if [[ ${terminals[*]} =~ ${parent} ]]; then
    # Your commands to run if in terminal
    export MY_VAR_IN_TERMINAL="test"
  fi
}
run_in_terminal

这在gnome-terminal的服务器-客户端模型中行不通。 - egmont