2

I've been trying to write a simple bash script, but I cannot get it to work.

I want to:

  • Start a program, feeding it input from a text file (./prog < input1.txt)
  • Wait a short amount of time and the kill it as if it was served a keyboard interrupt (& PID=$!; sleep 1; kill -INT $PID) source
  • Find the differences between the program's output and a text file (| diff -y output1.txt -) source

Here's what I have now, putting the previous steps together:

./program < input1.txt & PID=$!; sleep 1; kill -INT $PID | diff -y output1.txt -

This version always reports that the first command has had no output, since the PID line is shadowing it. If I add even a file redirect after the program name, the kill command stops working since it is now pointing at the redirect.

Edit: I am on Ubuntu 16.04; the output of bash --version is GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

  • 2
    { ./program < input1.txt & PID=$!; sleep 1; kill -INT $PID; }| diff -y output1.txt - – Weijun Zhou Mar 17 '19 at 03:42
  • @WeijunZhou that stops the kill command from happening by blocking the PID assignment somehow – Alex Vermillion Mar 17 '19 at 04:14
  • 1
    How about bash -c './program < input1.txt & PID=$!; sleep 1; kill -INT $PID; '| diff -y output1.txt. I have tested both solutions using yes as ./program. – Weijun Zhou Mar 17 '19 at 04:38
  • @WeijunZhou The second solution also causes a hang for me, including when testing with yes – Alex Vermillion Mar 17 '19 at 04:43
  • 1
    I missed the final hyphen. Sorry about it. Should be diff -y output1.txt -. – Weijun Zhou Mar 17 '19 at 04:45
  • @WeijunZhou yea, I added it in when I tested. The problem is that using the output in some way appears to stop me from killing the process. Can you check that the output of yes is being recorded in some way? – Alex Vermillion Mar 17 '19 at 05:00
  • Issues with diff. cat works fine. – Weijun Zhou Mar 17 '19 at 05:03
  • @WeijunZhou You are right, cat shows both outputs. Unfortunately, the kill command no longer works, suggesting diff waits for the output to be complete while cat is able to work on running files. – Alex Vermillion Mar 17 '19 at 05:29
  • I played with some other command that generates far less data than yes and it worked (more specifically, base64 /dev/urandom). I don't know how many data ./program is generating. You may also try turning off buffering with stdbuf. – Weijun Zhou Mar 17 '19 at 05:36
  • I tried <() hours ago, also failed with yes. – Weijun Zhou Mar 17 '19 at 08:32

2 Answers2

2

Indeed, in your code diff receives the output of kill instead of program. You can solve this by compounding the commands before the pipe in a subshell with ().

Another potential issue with your script is that kill is not designed to wait for termination of program; instead it just sends a signal to it. Therefore you may see a race condition where diff works without catching the whole output of program. I advice to programatically wait for termination of the backgound process with the wait bash builtin.

Here goes a sample of your code containing the above fixes:

#!/bin/bash
(
    ./program <input1.txt &
    PID=$!
    sleep 1
    kill -INT $PID
    wait
) | diff -y reference_output.txt -
  • There is no termination of the background program, that's why I want to send a SIGSEV. The program expects to be ended early by the user pressing CTRL-C, so waiting will wait forever. – Alex Vermillion Mar 17 '19 at 04:44
  • I see. In this case the wait is not required. – coolparadox Mar 17 '19 at 04:57
  • @AlexVermillion The wait will not wait forever if there's no background tasks to wait for. In the submitted code, the wait would wait for the task to terminate (it may do some cleanup or processing when it receives the INT signal, which may take a bit of time). – Kusalananda Mar 17 '19 at 08:41
  • @Kusalananda In this case, it does wait forever. For some reason, although the parenthesis block is being exited, the program is not actually being killed. I can tell by checking top after and seeing it is still running. I am confused, because running the kill -INT $PID on the command line after properly setting the variable does kill it. I can tell that the kill command is passed by putting a touch blah.txt afterwards. – Alex Vermillion Mar 18 '19 at 02:51
1

You can use a process substitution[1]:

diff -y output1.txt <(your_program & sleep 1; kill $!)

If your_program is able to generate huge amounts of data in a second (as when testing with yes(1)), then diff will have a lot of trouble coping with, and unless you have some careful limits put in place, it may blow your memory and hang up your machine (and in the best case it will create the illusion that the command is "hanging" for a while). Instead of yes, try it with a slowyes script like while echo y; do sleep .01; done.

Notice that a <(...) process substitution runs in parallel, so diff will not wait for it to finish before starting to process its data.

I guess you will be better served by stopping your_program after it has generated a number of lines, instead of after a period of time:

diff -y output1.txt <(your_program | head -n 1000)

In this case, your_program should be killed by SIGPIPE, as usual with the left side of a pipeline. If your program is ignoring both SIGPIPE and write(2) errors, then please make another question about that, either how to fix it or how to work it around.

[1] if your shell does not support process substitutions (as the /bin/sh on debian, which is not bash), you can also use a pipeline, as in the other answer

{ your_program & sleep 1; kill $!; } | diff -y output1.txt -

the same caveats apply to this case, too.

  • The output of running the program is silenced in the first command since it is hidden behind a semicolon. I did not test the second as the program has 3 lines of output only. The third snippet fails for the same reason as the first. – Alex Vermillion Mar 17 '19 at 22:05
  • Can you perhaps provide an exact snippet you have tested this with? When I test with yes, there is not any output, which is the only part I am having a problem with as the rest works. – Alex Vermillion Mar 17 '19 at 22:06
  • "The output of running the program is silenced in the first command since it is hidden behind a semicolon". No. That's not how it works. The semicolon doesn't hide anything. You can try with any program you want -- it will work exactly as I described, including why there's "no output" with yes(1). –  Mar 17 '19 at 22:10
  • 1
    @AlexVermillion There are a lot of reasons why your_program could fail to produce any output; for instance, it may use buffering when not outputting to a terminal, and not flush its buffers when killed by a signal. But there are thousands of possible scenarios like that. –  Mar 17 '19 at 22:54
  • I have been running these commands and taking my best guess at what silences them. It's a very short C program that terminates when fed a certain letter or else does a simple array operation. It will not terminate if left alone, but the output that I know is happening does not show in the diff, only when followed by other commands, as if its stdout is ignored by the pipe. – Alex Vermillion Mar 18 '19 at 17:47
  • If it's printing with stdio funcs (printf, puts, fputc, etc) then you can try running it stdbuf -oL ./prog (stdbuf is a GNU coreutils program) –  Mar 18 '19 at 18:12