使用命名管道将按键发送到交互式程序

使用命名管道将按键发送到交互式程序

我有一个程序 mpg123,它具有交互模式,允许来自标准输入的键盘命令执行有用的操作,例如控制音量。我正在尝试启动 mpg123,以便它从命名管道读取命令;这样我就可以让其他程序与它交互。

在一个终端中我执行以下操作:

mkfifo pipe
tail -n1 -f pipe | mpg123 -vC /some/song.mp3

在另一个终端中我执行以下操作:

cat > pipe
-

现在,我期望-将其发送到 mpg123 程序,就像我坐在终端中按下按键一样-,但事实并非如此。谁能告诉我我做错了什么?

答案1

似乎这-C会导致 mpg123 从终端读取,而不是从标准输入读取。然而,我在我的 mpg123 手册页版本中看到了这一点:

-R, --remote
       Activate  generic  control interface.  mpg123 will then read and
       execute commands from stdin. Basic usage is ``load <filename> ''
       to  play some file and the obvious ``pause'', ``command.  ``jump
       <frame>'' will jump/seek to a given point (MPEG  frame  number).
       Issue ``help'' to get a full list of commands and syntax.

这可能就是您正在寻找的;尝试mpg123 -vR <pipe。您的示例中的交互将如下所示(这将音量设置为 30%):

$ cat >pipe
load /some/song.mp3
volume 30

但是,当连接命名管道而不是终端时,这会导致前一种模式无法从 stdin 读取数据吗-C-R

快速浏览一下 mpg123 源代码表明它使用 termios 工具从终端读取按键,用于 tcsetattr将其置于所谓的“非规范模式”,其中按键被传输到读取器而不进行进一步处理(在特别是,无需等待输入完整的行):

struct termios tio = *pattern;
(...)

tio.c_lflag &= ~(ICANON|ECHO);
(...)

return tcsetattr(0,TCSANOW,&tio);

(这与GNU libc 代码示例.)

然后,在循环中get_key调用一个函数,该函数用于select 判断文件描述符 0 (stdin) 是否有可用数据,如果有,则从中读取一个字节 ( read(0,val,1))。但这仍然无法解释为什么终端可以工作而管道却不能!答案就在终端初始化代码中:

/* initialze terminal */
void term_init(void)
{
    debug("term_init");

    term_enable = 0;

    if(tcgetattr(0,&old_tio) < 0)
    {
        fprintf(stderr,"Can't get terminal attributes\n");
        return;
    }
    if(term_setup(&old_tio) < 0)
    {
        fprintf(stderr,"Can't set terminal attributes\n");
        return;
    }

    term_enable = 1;
}

请注意,如果tcgetattrterm_setup失败,则 term_enable设置为 0。(从终端读取按键的函数以 开头if(!term_enable) return 0;。)事实上,当 stdin 不是终端时, tcgetattr失败,会打印相应的错误消息,并且按键- 跳过处理代码:

$ mpg123 -C ~/input.mp3 <pipe
(...)
Can't get terminal attributes

这解释了为什么尝试通过管道发送命令mpg123 -C失败。对于实施者来说,这是一个有争议的选择;大概通过简单地允许tcgetattr/tcsetattr失败(也许通过为此目的使用开关),而不是禁用按键处理代码处理,您的尝试就会成功。

相关内容