3

I have Bash script that is used to process data, which is sent through a pipe from another process.

I'm trying to add a functionality that causes and exits after a certain condition is met, whose simplified representation is:

( while true; do date; sleep 1; done ) | ( for ((i = 0; i < 3; i++)); do read -r line; echo LINE: $line; sleep 1; done; exit 1 )

In the real case:

  • the command after the pipe is included in (and replaced by) a script;
  • the command before the pipe is a binary executable, and I don't have control over its internals.

I've also tried to close stdin:

( while true; do date; sleep 1; done ) | ( for ((i = 0; i < 3; i++)); do read -r line; echo LINE: $line; sleep 1; done; exec 0<&- )

However, when the condition is met, nothing is sent to stdout, but the left program still runs.

How can I accomplish the task?

  • 1
    You must close standard output in the program which is pumping data into the pipe, that it, on the left of the | symbol. Otherwise, you must break the for loop when the condition is met. – AlexP Dec 11 '19 at 12:23
  • @AlexP thanks. I've made a clarification - I don't have control over the command before the pipe. – Marcus Dec 11 '19 at 12:28
  • 1
    Well then, it depends on what exactly you want to do. For example, you could timeout reading from standard input if no more data is received after 5 seconds. The point being that you need to define your problem exactly and document your decision. – AlexP Dec 11 '19 at 12:38
1

The reason that your pipeline does not exit is that the exit 1 statement only terminates the right hand side subshell.

After the right hand side has exit, the date command that you use for generating data will start failing since it can't write its output to anywhere (it would be killed by a PIPE signal). You don't test the exit status of date, so you don't see this.

Rewriting the left hand side as either

(
    while true; do
        if ! date; then
            exit    # or: break
        fi
        sleep 1
    done
)

or as

(
    while date; do
        sleep 1
    done
)

will make the pipeline as a whole behave as you expect.

This is indicative of the data-producing binary that you have on the left hand side of your pipeline not checking whether writes are successful or not (and it may also ignore the PIPE signal). Closing standard input in the right hand side subshell would not help (this is already done when you exit that subshell anyway).

This is a bug in the program that produces the data.

How can you work around that?

One way would be to use a named pipe. This would allow you the fetch the PID of the data-producing process, which you can later use to kill it:

#!/bin/bash

rm -f data.pipe
mkfifo data.pipe

( while true; do date; sleep 1; done ) >data.pipe & pid=$!

(
    for ((i = 0; i < 3; i++)); do
        read -r line
        printf 'LINE: %s\n' "$line"
        sleep 1
    done
    kill "$pid"
    exit 1
) <data.pipe

If the program is ignoring the PIPE signal, it may well be ignoring other signals, so you would have to experiment with sending it the default TERM first and if that does not work, try INT etc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.