PTY 类

创建和管理伪终端 (PTY)。另请参阅 en.wikipedia.org/wiki/Pseudo_terminal

PTY 允许你使用 ::open 分配新的终端,或使用 ::spawn 使用特定命令生成新的终端。

示例

在此示例中,我们将更改 factor 命令中的缓冲类型,假设 factor 使用 stdio 进行 stdout 缓冲。

如果使用 IO.pipe 而不是 PTY.open,则此代码会发生死锁,因为 factor 的 stdout 是完全缓冲的。

# start by requiring the standard library PTY
require 'pty'

master, slave = PTY.open
read, write = IO.pipe
pid = spawn("factor", :in=>read, :out=>slave)
read.close     # we dont need the read
slave.close    # or the slave

# pipe "42" to the factor command
write.puts "42"
# output the response from factor
p master.gets #=> "42: 2 3 7\n"

# pipe "144" to factor and print out the response
write.puts "144"
p master.gets #=> "144: 2 2 2 2 3 3\n"
write.close # close the pipe

# The result of read operation when pty slave is closed is platform
# dependent.
ret = begin
        master.gets     # FreeBSD returns nil.
      rescue Errno::EIO # GNU/Linux raises EIO.
        nil
      end
p ret #=> nil

许可证

© 版权所有 1998 Akinori Ito。

本软件可全部或部分地自由再分发用于此目的,前提是本完整版权声明包含在本软件及其应用程序和衍生产品的任何副本中。

本软件按“原样”提供,不作任何形式的保证,无论是明示的还是暗示的,包括但不限于对适用性、适销性或使用本软件所获得结果的保证。

公共类方法

check(pid, raise = false) → Process::Status 或 nil 单击以切换源代码
check(pid, true) → nil 或引发 PTY::ChildExited

检查由 pid 指定的子进程的状态。如果进程仍在运行,则返回 nil

如果进程未运行,并且 raise 为 true,则将引发 PTY::ChildExited 异常。否则,它将返回 Process::Status 实例。

pid

要检查的进程的进程 ID

raise

如果为 true 并且由 pid 标识的进程不再运行,则会引发 PTY::ChildExited

static VALUE
pty_check(int argc, VALUE *argv, VALUE self)
{
    VALUE pid, exc;
    rb_pid_t cpid;
    int status;
    const int flag =
#ifdef WNOHANG
        WNOHANG|
#endif
#ifdef WUNTRACED
        WUNTRACED|
#endif
        0;

    rb_scan_args(argc, argv, "11", &pid, &exc);
    cpid = rb_waitpid(NUM2PIDT(pid), &status, flag);
    if (cpid == -1 || cpid == 0) return Qnil;

    if (!RTEST(exc)) return rb_last_status_get();
    raise_from_check(cpid, status);

    UNREACHABLE_RETURN(Qnil);
}
getpty(*args)

在新分配的 pty 上生成指定的命令。你也可以使用别名 ::getpty

该命令的控制 tty 设置为 pty 的从设备,其标准输入/输出/错误重定向到从设备。

env 是一个可选的哈希,它为生成的 pty 提供额外的环境变量。

# sets FOO to "bar"
PTY.spawn({"FOO"=>"bar"}, "printenv", "FOO") do |r, w, pid|
  p r.read #=> "bar\r\n"
ensure
  r.close; w.close; Process.wait(pid)
end
# unsets FOO
PTY.spawn({"FOO"=>nil}, "printenv", "FOO") do |r, w, pid|
  p r.read #=> ""
ensure
  r.close; w.close; Process.wait(pid)
end

commandcommand_line 是要运行的完整命令,给定一个字符串。任何额外的 arguments 将传递给该命令。

返回值

在非阻塞形式中,这会返回一个大小为三的数组 [r, w, pid]

在块形式中,这些相同的值将被传递给块

r

一个可读的 IO,其中包含命令的标准输出和标准错误

w

一个可写的 IO,它是命令的标准输入

pid

该命令的进程标识符。

清理

此方法不执行清理操作,例如关闭 IO 或等待子进程,除非该进程在块形式中被分离以防止其成为僵尸(请参阅 Process.detach)。任何其他清理都是调用者的责任。如果等待 pid,请务必先关闭 rw,然后再执行此操作;以相反的顺序执行可能会在某些操作系统上导致死锁。

别名:spawn
open → [master_io, slave_file] 单击以切换源代码
open {|(master_io, slave_file)| ... } → 块值

分配一个 pty(伪终端)。

在块形式中,会传递一个包含两个元素(master_io, slave_file)的数组,并且块的值会从 open 返回。

如果 IO 和 File 在块完成后尚未关闭,则两者都会关闭。

PTY.open {|master, slave|
  p master      #=> #<IO:masterpty:/dev/pts/1>
  p slave      #=> #<File:/dev/pts/1>
  p slave.path #=> "/dev/pts/1"
}

在非块形式中,会返回一个包含两个元素的数组 [master_io, slave_file]

master, slave = PTY.open
# do something with master for IO, or the slave file

两种形式中的参数都是

master_io

pty 的主设备,作为 IO

slave_file

pty 的从设备,作为 File。可以通过 slave_file.path 获取终端设备的路径

可以使用 IO#raw! 来禁用换行符转换

require 'io/console'
PTY.open {|m, s|
  s.raw!
  # ...
}
static VALUE
pty_open(VALUE klass)
{
    int master_fd, slave_fd;
    char slavename[DEVICELEN];

    getDevice(&master_fd, &slave_fd, slavename, 1);

    VALUE master_path = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename));
    VALUE master_io = rb_io_open_descriptor(rb_cIO, master_fd, FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX, master_path, RUBY_IO_TIMEOUT_DEFAULT, NULL);

    VALUE slave_path = rb_obj_freeze(rb_str_new_cstr(slavename));
    VALUE slave_file = rb_io_open_descriptor(rb_cFile, slave_fd, FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY, slave_path, RUBY_IO_TIMEOUT_DEFAULT, NULL);

    VALUE assoc = rb_assoc_new(master_io, slave_file);

    if (rb_block_given_p()) {
        return rb_ensure(rb_yield, assoc, pty_close_pty, assoc);
    }

    return assoc;
}
spawn([env,] command_line) { |r, w, pid| ... } 单击以切换源代码
spawn([env,] command_line) → [r, w, pid]
spawn([env,] command, arguments, ...) { |r, w, pid| ... }
spawn([env,] command, arguments, ...) → [r, w, pid]

在新分配的 pty 上生成指定的命令。你也可以使用别名 ::getpty

该命令的控制 tty 设置为 pty 的从设备,其标准输入/输出/错误重定向到从设备。

env 是一个可选的哈希,它为生成的 pty 提供额外的环境变量。

# sets FOO to "bar"
PTY.spawn({"FOO"=>"bar"}, "printenv", "FOO") do |r, w, pid|
  p r.read #=> "bar\r\n"
ensure
  r.close; w.close; Process.wait(pid)
end
# unsets FOO
PTY.spawn({"FOO"=>nil}, "printenv", "FOO") do |r, w, pid|
  p r.read #=> ""
ensure
  r.close; w.close; Process.wait(pid)
end

commandcommand_line 是要运行的完整命令,给定一个字符串。任何额外的 arguments 将传递给该命令。

返回值

在非阻塞形式中,这会返回一个大小为三的数组 [r, w, pid]

在块形式中,这些相同的值将被传递给块

r

一个可读的 IO,其中包含命令的标准输出和标准错误

w

一个可写的 IO,它是命令的标准输入

pid

该命令的进程标识符。

清理

此方法不执行清理操作,例如关闭 IO 或等待子进程,除非该进程在块形式中被分离以防止其成为僵尸(请参阅 Process.detach)。任何其他清理都是调用者的责任。如果等待 pid,请务必先关闭 rw,然后再执行此操作;以相反的顺序执行可能会在某些操作系统上导致死锁。

static VALUE
pty_getpty(int argc, VALUE *argv, VALUE self)
{
    VALUE res;
    struct pty_info info;
    char SlaveName[DEVICELEN];

    establishShell(argc, argv, &info, SlaveName);

    VALUE pty_path = rb_obj_freeze(rb_str_new_cstr(SlaveName));
    VALUE rport = rb_io_open_descriptor(
        rb_cFile, info.fd, FMODE_READABLE, pty_path, RUBY_IO_TIMEOUT_DEFAULT, NULL
    );

    int wpty_fd = rb_cloexec_dup(info.fd);
    if (wpty_fd == -1) {
        rb_sys_fail("dup()");
    }
    VALUE wport = rb_io_open_descriptor(
        rb_cFile, wpty_fd, FMODE_WRITABLE | FMODE_TRUNC | FMODE_CREATE | FMODE_SYNC,
        pty_path, RUBY_IO_TIMEOUT_DEFAULT, NULL
    );

    res = rb_ary_new2(3);
    rb_ary_store(res, 0, rport);
    rb_ary_store(res, 1, wport);
    rb_ary_store(res,2,PIDT2NUM(info.child_pid));

    if (rb_block_given_p()) {
        rb_ensure(rb_yield, res, pty_detach_process, (VALUE)&info);
        return Qnil;
    }
    return res;
}
也别名为:getpty