It seems that -C
causes mpg123 to read from the terminal, not from stdin. I see this, however, in my version of mpg123's man page:
-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.
This may be what you are looking for; try mpg123 -vR <pipe
. The
interaction in your example would become something like the following
(this sets the volume to 30%):
$ cat >pipe
load /some/song.mp3
volume 30
But then, what does -C
do that -R
doesn't that results in the
former mode failing to read from stdin when a named pipe, rather than
a terminal, is connected?
A quick look at the mpg123 source code indicates that it uses the
termios facilities to read keypresses from the terminal, using
tcsetattr
to put it in the so-called "non-canonical mode", where
keypresses are transmitted to the reader without further processing
(in particular, without waiting for a complete line to have been
typed):
struct termios tio = *pattern;
(...)
tio.c_lflag &= ~(ICANON|ECHO);
(...)
return tcsetattr(0,TCSANOW,&tio);
(This is the same as the GNU libc code sample.)
Then, in a loop, a function get_key
is called, which uses select
to tell whether file descriptor 0 (stdin) has data available and, if
so, reads one byte from it (read(0,val,1)
). But this still doesn't
explain why a terminal works but a pipe doesn't! The answer lies in
the terminal initialization code:
/* 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;
}
Note that if either tcgetattr
or term_setup
fails, then
term_enable
is set to 0. (The function to read keys from the terminal starts with if(!term_enable) return 0;
.) And, indeed, when stdin isn't a terminal,
tcgetattr
fails, the corresponding error message is printed, and the
keypress-handling code is skipped:
$ mpg123 -C ~/input.mp3 <pipe
(...)
Can't get terminal attributes
This explains why attempting to send commands by piping into mpg123
-C
fails. That's a debatable choice by the implementors; presumably
by simply allowing tcgetattr
/ tcsetattr
to fail (perhaps by using
a switch for that purpose), instead of disabling the keypress-handling
code handling, your attempt would have worked.
If you get rid of the killing and shutdown stuff (which is unsafe and you may, in an extreme, but not unfathomable case when child.py
dies before the (head -n 1 shutdown; kill -9 $parent) &
subshell does end up kill -9
ing some innocent process),
then child.py
won't be terminating because your parent.py
isn't behaving like a good UNIX citizen.
The cat std_out &
subprocess will have finished by the time you send the quit
message, because the writer to std_out
is child_original.py
, which finishes upon receiving quit
at which moment it closes its stdout
, which is the std_out
pipe and that close
will make the cat
subprocess finish.
The cat > std_in
isn't finishing because it's reading from a pipe originating in the parent.py
process and the parent.py
process didn't bother to close that pipe. If it did, cat > stdin_in
and consequently the whole child.py
would finish by itself and you wouldn't need the shutdown pipe or the killing
part (killing a process that isn't your child on UNIX is always a potential security hole if a race condition caused due to rapid PID recycling should occur).
Processes at the right end of a pipeline generally only finish once they're done reading their stdin, but since you're not closing that (child.stdin
), you're implicitly telling the child process "wait, I have more input for you" and then you go kill it because it does wait for more input from you as it should.
In short, make parent.py
behave reasonably:
from __future__ import print_function
from subprocess import Popen, PIPE
import os
child = Popen('./child.py', stdin=PIPE, stdout=PIPE)
for letter in 'abcde':
print('Parent writes to child: ', letter)
child.stdin.write(letter+'\n')
child.stdin.flush()
response = child.stdout.readline()
print('Response from the child:', response)
assert response.rstrip() == letter.upper(), 'Wrong response'
child.stdin.write('quit\n')
child.stdin.flush()
child.stdin.close()
print('Waiting for the child to terminate...')
child.wait()
print('Done!')
And your child.py
can be as simple as
#!/bin/sh
cat std_out &
cat > std_in
wait #basically to assert that cat std_out has finished at this point
(Note that I got rid of that fd dup calls because otherwise you'd need to close both child.stdin
and the child_stdin
duplicate).
Since parent.py
operates in line-oriented fashion, gnu cat
is unbuffered (as mikeserv pointed out) and child_original.py
operates in a line oriented fashion, you've effectively got the whole thing line-buffered.
Note on Cat: Unbufferred might not be the luckiest term, as gnu cat
does use a buffer. What it doesn't do is try to get the whole buffer full before writing things out (unlike stdio). Basically it makes read requests to the os for a specific size (its buffer size), and writes whatever it receives without waiting to get a whole line or the whole buffer. (read(2) can be lazy and give you only what it can give you at the moment rather than the whole buffer you've asked for.)
(You can inspect the source code at http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c ; safe_read
(used instead of plain read
) is in the gnulib
submodule and it's a very simple wrapper around read(2) that abstracts away EINTR
(see the man page)).
Best Answer
It seems
grep
still opens files even if the regex tells it to skip them:(Note: none of these have read permissions.)
Granting read permissions, it appears that it doesn't try to read them after opening if they are excluded:
And since
openat
wasn't called withO_NONBLOCK
, the opening itself hangs, and grep doesn't reach the part where it excludes it from reading.Looking at the source code, I believe the flow is like this:
grep_command_line_arg
on each file.grepfile
if not on stdin.grepfile
callsgrepdesc
after opening the file.grepdesc
checks for excluding the file.When recursive:
grepdirent
checks for excluding the file before callinggrepfile
, so the failingopenat
never happens.