Cannot terminate a shell command with Ctrl+c
Asked Answered
K

1

1

Would someone please tell me why below bash statement cannot be terminated by Ctrl+c properly?

$( { ( tail -fn0 /tmp/a.txt & )| while read line; do echo $line; done } 3>&1 )

I run this statement, then two bash processes and one tail process are launched(got from ps auxf), then input Ctrl+c, and it won't quit to the bash prompt, at this moment, I see the two bash processes stopped, while the tail is still running, then I input something into /tmp/a.txt, then we could get into bash prompt.

What I want is, input Ctrl+c, then just quit into bash prompt without any relevant process left.

It will be more appreciative that someone explains the exact process of this statement, like a pipe causes the bash fork, something redirect to somewhere, etc.

Updated at Oct 9 2014:

Here provide some update in case it's useful to you. My adopt solution is alike with 2 factors:

  1. use a tmp pid file

    ( tail -Fn0 ${monitor_file} & echo "$!" >${tail_pid} ) | \
    while IFS= read -r line; do 
        xxxx
    done 
    
  2. use trap like: trap "rm ${tail_pid} 2>/dev/null; kill 0 2>/dev/null; exit;" INT TERM to kill relevant processes and remove remain files.

Please note, this kill 0 2 is bash specific, and 0 means all processes in the current process group. This solution used a tmp pid file, while I still expect other solution without tmp pid file.

Karyogamy answered 12/12, 2013 at 2:10 Comment(12)
Why are you running it with $(...)? That's for command substitution.Collencollenchyma
"tail -fn0 /tmp/a.txt &" starts a new process in background (due to &), so it is not affected by ctrl+c of the main process.Vaginismus
explainshell.com/…Chavannes
Thanks for your quick response, I know the $() seems useless here, just to trigger this issue to study.Karyogamy
While since the tail running in background, why can't I get to the bash prompt after the ctrl+c? That's my concern.Karyogamy
because it is a separate process from the main one, isolated, with its own pid. It lives its own life ! ctrl+c will kill the main process but not its child process. You have to manage the life of this child process. To me, the best approach would be to write a "service myscript [stop|start]" (or the same with init.d), so that you could handle stop and kill child process, for that you will have to get the child pid in your main processVaginismus
Thanks for your response! Let me explain more:Karyogamy
Like this: kill $( { ( tail -fn0 ${monitor_file} & echo "$!" >&3 ) | while read -r line; do echo "${line}"; done } 3>&1) I redirect the pid of tail, and use $() for killing the tail, while the ctrl+c and not interrupt the statement properly.Karyogamy
well, i will go to bed now, it is 4am here ! what are you exactly trying to do ? maybe we should review the whole approach !Vaginismus
Good night my remote friend. While I'm writing a script to monitor a log file, once certain string pattern is occuring in the log, it need stop monitoring and take some othen action. When the action is done, it need monitor log again, loop like this. This script is supposed to be able to clean all the process and temporary files created by itself once it's interrupted. Actually, I just want to study the exact process of this shell statement. For knowledge. Is it possible just quit after a ctrl-c like normal routine?Karyogamy
I wonder if this behaviour is to do with bash's way of handling a pipeline and collecting the status of every process in the pipeline. Classically, only the last process in a pipeline was a child of the shell, so when the last process exited, the others were left parentless. However, since bash waits (or can wait) for all children (so the statuses are collected in the PIPESTATUS array), which means that the 'pipeline' doesn't end until all processes are terminated, and tail -f does not terminate until it gets a SIGPIPE signal after writing something to the pipe.Protein
You might have some luck using trap (for the interrupt signal) to kill tail, You might need to save the PID, which can be tricky... (pgrep -f "tail -fn0 /tmp/a.txt" would work, but will give problems if multiple instances are running...)Touching
T
1

It works to trap the INT signal (sent by Ctrl-C) to kill the tail process.

$( r=$RANDOM; trap '{ kill $(cat /tmp/pid$r.pid);rm /tmp/pid$r.pid;exit; }' SIGINT EXIT; { ( tail -fn0 /tmp/a.txt & echo $! > /tmp/pid$r.pid  )| while read line; do echo $line; done } 3>&1 )

(I use a random value on the PID file name to at least mostly allow multiple instances to run)

Touching answered 2/1, 2014 at 8:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.