我有一个程序 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;
}
请注意,如果tcgetattr
或term_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
失败(也许通过为此目的使用开关),而不是禁用按键处理代码处理,您的尝试就会成功。