如何获取控制终端的真实名称(如果有,否则出错)作为路径名?
我所说的“真实姓名”是指 not /dev/tty
,其他任意进程不能使用它来引用同一终端。如果可能的话,我更喜欢将答案作为简单的 shell 代码(如下面的示例),否则作为 C 函数。
请注意,即使标准输入被重定向,这也必须起作用,因此该tty
实用程序无法使用:not a tty
在这种情况下会出现错误,因为tty
只打印连接到标准输入的终端的文件名。
在Linux下,可以使用:
echo "/dev/`ps -p $$ -o tty | tail -n 1`"
但这是不可移植的,根据 POSIX,终端名称的格式未指定。
对于 C 函数,ctermid (NULL)
返回/dev/tty
,这在这里没有用。
笔记:根据zsh
文档,人们应该能够做到
zsh -c 'echo $TTY'
但当前(版本 5.0.7)当标准输入和标准输出都重定向时会失败:
$ zsh -c 'echo $TTY > /dev/tty' < /dev/null
/dev/pts/9
$ zsh -c 'echo $TTY > /dev/tty' < /dev/null > /dev/null
/dev/tty
答案1
又名“控制终端”。 ctty,区别于“这进程正在与之交互的终端”。
获取 ctty 路径的标准方法是 ctermid(3)。调用此命令后, 从版本 10 开始,在 freebsd 中,会查找实际路径[1],而较旧的 freebsd 和glibc 实现[2]无条件返回“/dev/tty”。
ps(1) 来自 linux procps 3.2.8 软件包,读取 /proc/*/stat 中的数字条目[3],然后扣除路径名 部分通过猜测[4, 5] 由于缺乏系统支持[6]。
但是,如果我们对 ctty 并不严格感兴趣,而是对与 stdio 关联的任何终端感兴趣,则 tty(1) 会打印连接到 stdin 的终端路径,这与ttyname(fileno(stdin))
c 中的相同,替代方案是 readlink /proc/self/fd/0
.
关于无条件“/dev/tty”行为的不太重要的想法:规范只是说ctermid返回的字符串“当用作路径名时,指的是当前控制终端”,而不是一些简单的“是当前的路径名”控制终端”。它可能被解释为“/dev/tty”不是控制终端,而仅指同一进程打开(3)它时的控制终端。因此不违反 “一个终端最多可以是一个会话的 ctty”规则[7]。
另一个结果是,当我没有任何控制终端时,ctermid 不会失败——规范允许此类失败[8] --,所以只有在后续的 open(3) 失败之前我才能意识到我的 ctty'less,这是可以的,因为规范也说调用 open(3) 不能保证成功。
答案2
POSIX 规范确实对冲了它的赌注控制终端是有关的,它的定义如下:
- 控制终端
- POSIX.1 中没有解决可能涉及终端的几个特殊文件中的哪一个的问题。路径名
/dev/tty
是与进程关联的控制终端的同义词。
- POSIX.1 中没有解决可能涉及终端的几个特殊文件中的哪一个的问题。路径名
这在定义列表中 - 这就是全部内容。但在通用终端接口,还有一些说法:
终端可以属于一个进程作为其控制终端。具有控制终端的会话的每个进程都具有相同的控制终端。一个终端最多可以是一个会话的控制终端。会话的控制终端由会话领导者以实现定义的方式分配。如果会话领导者没有控制终端,并且在不使用 O_NOCTTY 选项(请参阅 open())的情况下打开尚未与会话关联的终端设备文件,则该终端是否成为会话的控制终端是实现定义的领导者。
在 fork() 函数调用期间,控制终端由子进程继承。当进程使用该函数创建新会话时,它会放弃其控制终端
setsid()
;旧会话中保留该终端作为其控制终端的其他进程将继续拥有该终端。当系统中与控制终端关联的最后一个文件描述符(无论它是否在当前会话中)关闭时,未指定将该终端作为其控制终端的所有进程是否不再拥有任何控制终端。在以这种方式放弃控制终端之后,会话领导者是否以及如何重新获取控制终端是未指定的。如果其他进程继续打开进程,则进程不会简单地通过关闭与控制终端关联的所有文件描述符来放弃其控制终端。
还剩下很多未指定- 老实说,我认为这是有道理的。虽然终端是一个关键的用户界面,但在某些情况下它也是各种其他东西 - 比如实际的硬件,甚至是一种打印机 - 但在很多情况下它实际上什么都不是 - 就像它xterm
只是一个模拟器。很难具体说明这一点 - 而且我认为这对 Unix 无论如何都没有多大好处,因为终端的功能比 Unix 多得多。
ps
不管怎样,POSIX对于 ctty 的行为方式也相当不确定。
有个-a
开关:
- 写入与终端关联的所有进程的信息。实现可能会从此列表中省略会话领导者。
伟大的。分会场领导可能被省略。这不是很有帮助。
和-t
:
- 写入与术语列表中给出的终端关联的进程的信息。应用程序应确保术语列表是一个
<blank>
以逗号分隔的列表形式的单个参数。终端标识符应在实现定义的格式。
...这是另一个令人失望的地方。但它确实继续谈到了 XSI 系统:
- 在符合 XSI 的系统上,它们应以两种形式之一给出:设备的文件名(例如,
tty04
),或者,如果设备的文件名以 开头tty
,则仅是字符后面的标识符tty
(例如,04
)。
这好一点,但不是一条路。 XSI 系统上还有一个-d
开关:
- 写入除会话领导者之外的所有进程的信息。
...这至少是清楚的。您-o
也可以使用tty
格式字符串指定输出开关,但是,正如您所指出的,其输出格式是实现定义的。尽管如此,我认为它已经是最好的了。我认为,通过大量的工作,上述开关与其他一些实用程序相结合可以为您带来相当不错的结果。但老实说,我不知道它何时/如何对你造成影响 - 而且我无法想象它会发生的情况。但是,我认为如果我们添加fuser
并且find
我们可以验证路径。
exec 2<>/dev/null
ctty=$(sh -c 'ps -p "$$" -o tty=' <&2)
sid=$(sh -c 'ps -Ao pid= -o tty=|
grep '"$ctty$"' |
grep -Fv "$(ps -do pid=)"' <&2)
find / -type c -name "*${ctty##*/}*" \
-exec fuser -uv {} \; 2>&1 |
grep ".*$ctty.*${sid%%"$ctty"*}"
这些/dev/null
东西只是为了表明当搜索子 shell 中没有任何 0、1、2 连接到 ctty 时它可以工作。不管怎样,打印出来的是:
/dev/pts/3: mikeserv 3342 F.... (mikeserv)zsh
现在上面的内容得到了我机器上的完整路径,我想在大多数情况下对于大多数人来说都是如此。我也可以想象它可能会失败。这只是粗略的启发法。
这可能会因许多其他原因而失败,但如果您所在的系统允许会话领导者将所有描述符放弃给 ctty,但仍保留 sid,那么按照规范允许,那么这绝对没有帮助。也就是说,我认为在大多数情况下这可以得到一个相当不错的估计。
当然最简单的如果你有要做的事任何连接到你的 ctty 的描述符只是......
tty <&2
...或类似的。