Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my bash script blocking?

Tags:

bash

I have a bash script : my.sh

#!/bin/bash

do_sth()
{
    sleep 5
}

main()
{
    do_sth >/dev/null &
    echo do sth in background ...
}

if [ "$1" = "1st_way" ]; then
    main
elif [ "$1" = "2nd_way" ]; then
    main >/dev/null
fi

The following command returns immediately

./my.sh 1st_way | cat

But, the following command blocks for 5 seconds

./my.sh 2nd_way | cat

I wonder why it is blocking for 5 seconds in the 2nd way.

like image 533
赵小刚 Avatar asked Oct 02 '15 04:10

赵小刚


1 Answers

Consider this script:

#!/bin/bash

main() {
    cd /tmp
}

main > /dev/null
echo goodbye

Before bash runs the main function, it needs to redirect its own standard output to /dev/null. Then it needs to run the main function. Then, before executing the echo command, it needs to restore its standard output to whatever it was before the redirect.

Here's how it does that. To do the redirect, it opens /dev/null, getting (let's say) file descriptor 3. Then it duplicates its file descriptor 1 (standard output) to the first available fd ≥ 10, getting (let's say) 10. Then it duplicates fd 3 (open on /dev/null) to fd 1, and closes fd 3.

Later, to undo the redirect, it duplicates fd 10 (open on bash's original standard output) onto fd 1, and closes fd 10.

Okay, now back to your script. All the same things happen when your script says main >/dev/null, so while main is executing, fd 10 is open on the shell's original standard output, which is the writable end of the pipe to cat.

When your main says do_sth >/dev/null &, the shell forks. The child shell inherits fd 10, so both the parent and child shells have fd 10 open on the pipe. (Note that the shell knows that it doesn't need to save its standard output here because of the & making it fork.)

When the child shell, in do_sth, runs sleep, it forks to run sleep and waits for sleep to exit. So the child shell hangs around for five seconds, and during that time it has fd 10 open on the writable end of the pipe.

The cat process doesn't read EOF on the readable end of the pipe until all file descriptors on the writable end are closed, which doesn't happen until that child shell exits, which doesn't happen until sleep exits.

like image 137
rob mayoff Avatar answered Oct 03 '22 02:10

rob mayoff