Question

I have this piece of code

#!/bin/bash
streamURL=http://devimages.apple.com/iphone/samples/bipbop/gear4/prog_index.m3u8

(
    echo "Debugging for stream: $streamURL";
    echo "Starting debugging...";
    vlc -vvv --color $streamURL --file-caching=10000 2>&1 | sed "s/^/ `date`/";

) | tee  debug.txt &
PROCESS_PID=$!

ps -e | grep $PROCESS_PID 
echo " killing process pid: "
echo $PROCESS_PID;
kill -9 $PROCESS_PID
ps -e | grep vlc #still there

My problem is I can't manage to save the "vlc ..." command PID into a variable in order to kill it later. If I move "PROCESS_PID=$!" right after it, it will be empty. Also need the pipe after it for sed. Any suggestions?

Was it helpful?

Solution

You can get the pid by twiddling file descriptors, but it's painful. For example:

{ PID=$({ ( 
   echo foo; 
   echo bar;
   sh -c 'echo $$ >&5; exec echo baz' ) |
   tr a o; } 5>&1 1>&3 ); } 3>&1

will assign the pid of 'echo baz' to PID. Replace that echo with your vlc and replace the tr with your sed and you should have a solution.

To try an provide a somewhat simplified explanation of what's going on here, first notice that we are using process substitution to make the assignment to PID. The $() syntax simply takes the command inside the parentheses and assigns to the variable the output of the command. It is important to remember that "output" here means "whatever is printed to file descriptor 1". Inside the sh command, we print a pid to file descriptor 5 and then exec echo. By using exec, that echo has the same pid that the previous echo wrote. Now the echo foo, bar and baz are all writing into the pipe that goes to tr. The output of tr is being redirected to fd 3 (before the edit, this was fd 2. Which file descriptor to use is mostly arbitrary, but modifying 2 is a bad idea in case any errors are generated) and file descriptor 5 is being redirected to fd 1, so that it becomes the "output" of the process substitution that is assigned to PID. Then outside the process substitution, we assign fd 3 to give output where it was originally desired. Hopefully, this paragraph is more explanatory than obfuscating: if confused, look at the code for clarification!

Unfortunately, it gets uglier if you want to run in the background:

{ PID=$({ ( 
   echo foo; 
   echo bar;
   sh -c 'echo $$ >&5; exec 5>&-; exec echo baz' >&3 & ) |
   tr a o; } 5>&1 1>&3 ); } 3>&1

Here, you need to close file descriptor 5 to ensure that the process substitution completes.

OTHER TIPS

You can't assign a variable in a subshell and get it back outside it.

In this case, if you kill $! you'll kill tee, which will (AFAIK) send SIGPIPE to the subshell and terminate the whole thing. So there's generally no need for the PID in the subshell.

I'm not sure, but the problem might be that you're nuking the process from orbit with SIGKILL rather than killing it softly with just kill $PID. It might be that tee does not send SIGPIPE in this case, because it doesn't get to clean up after itself.

In other words, just use kill $process_id. Be aware that killing a process is not synchronous - you're just sending it a signal and carrying on. See Kill bash processes “nicely” for details.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top