3

I am trying to count all keystrokes for a given period. Currently my code looks like this:

$ timeout -s 9 10s xinput test 8 | wc -l

This returns

Killed

instead of the expected number of key presses. When i run timeout without the pipe the output is as expected

$ timeout -s 9 20s xinput test 8
key release 36 
key press   42 
key release 42 
key press   26 
key release 26 
key press   28 
key release 28 
key press   38 
key release 38 
key press   46 
key release 46 
key press   31 
key release 31 
key press   41 
key release 41 
key press   26 
key release 26 
Killed

Neither changing the signal nor adding parameter --preserve-status fixes this.

I want to know why this does not work. Does 'timeout' hijack xinputs stdout somehow?

edit: However adding --foreground solves my problem. I don't understand why, though.

meschi
  • 111
  • 6

2 Answers2

0

It works because in that way, timeout command does not kill any child processes, avoiding wc to get the lines to count.

$ timeout --foreground -s 15 10s xinput test 8 | wc -l

Another way would be something like this:

$ xinput test 8 > output.log & sleep 10 && wc -l output.log && kill $!

That way, you don't need timeout. The first part redirect's xinput output to a file and sends the command to background and then sleeps (waiting until the file receives some data), and after sleep finishes we run wc and kill gracefully the xinput background command.

0

With --foreground, wc -l just sees an EOF, so it prints the line count.

Without --foreground, wc -l gets killed by a SIGKILL. (I traced it with strace -p $(pidof wc) from another terminal).

tracing timeout itself, to see what it does differently with/without --foreground:

Other than memory addresses being different:

After the time expires, --foreground just kills and wait(2)s for the child it forked (clone(2)), then exits with the normal exit_group(2) system call.

Without --foreground, it does

setpgid(0, 0)                           = 0

before forking the child. And after the time expires, it kill()s the child, then kills itself with kill(0, SIGKILL).

From kill(2):

If pid equals 0, then sig is sent to every process in the process group of the calling process.

I forget the details of the signal semantics, and I'm not sure why this explains wc receiving a SIGKILL. According to pstree, wc and xinput are both children of bash, not of each other. Processes are started in the order they appear in the pipeline, though.


The timeout(1) man page says about --foreground: "in this mode, children of COMMAND will not be timed out". It seems that this includes later elements in a pipeline. IDK if the the shell could have arranged for the different elements of the pipeline not to be in the same process group to avoid this, or what.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847