4

Here's my current bash which is just a loop

while true ; do 
     python3 /Users/Name/Desktop/pythoncode.py
done

I want to terminate pythoncode.py if it doesn’t output anything to the terminal in 2 min

4 Answers4

7

With zsh:

zmodload zsh/system
(
  echo $sysparam[pid]
  exec python3 /Users/Name/Desktop/pythoncode.py
) | {
  read pid
  if sysread -o 1 -s 1 -t120; then
    cat
  else
    kill -s PIPE $pid 2> /dev/null
  fi
}

With bash, you can do something similar with:

(
  echo "$BASHPID"
  exec python3 /Users/Name/Desktop/pythoncode.py
) | {
  read pid
  if LC_ALL=C IFS= read -rd '' -n1 -t120 byte; then
    if [ -n "$byte" ]; then
      printf %s "$byte"
    else
      printf '\0'
    fi
    cat
  else
    kill -s PIPE "$pid" 2> /dev/null
  fi
}

Those check whether that script writes anything to stdout, not specifically to the terminal.

If you also want to stop if the command has written something, but stopped and hasn't output anything within two minutes, or in other words, kill the command after 2 minutes of inactivity, then you could just use socat:

socat -T120 'exec:python3 /Users/Name/Desktop/pythoncode.py' -

Or with zsh:

zmodload zsh/system
(
  echo $sysparam[pid]
  exec python3 /Users/Name/Desktop/pythoncode.py
) | {
  read pid
  while
    sysread -o 1 -t 120; ret=$?
    (( ret == 0 ))
  do continue; done
  # 5 means EOF
  (( ret == 5 )) || kill $pid
}
  • Looks beautiful, but the Bash version isn't working for me (it never exits; my line 3 was exec tail -f ...). – bitinerant Dec 31 '20 at 20:27
  • Thank you Stephane – Ardalan Ghazizadeh Jan 01 '21 at 04:40
  • @bitinerant, see edit. I suspect you ran tail -f on a non-empty file, so it did output something initially. – Stéphane Chazelas Jan 01 '21 at 09:24
  • Thank you for your work, Stéphane. However, on Bash 5.0, the new version exits immediately: rm -f /tmp/watched_file; touch $_; ( echo "$BASHPID"; exec tail /tmp/watched_file; ) | { read pid; if LC_ALL=C IFS= read -rd '' -n1 -t15 byte; then if [ -n "$byte" ]; then printf %s "$byte"; else printf '\0'; fi; cat; else kill -s PIPE "$pid" 2> /dev/null; fi; } – bitinerant Jan 01 '21 at 16:56
  • @bitinerant, you missed the -f flag. But by see edit, I meant the socat part and below to handle inactivity timeout. – Stéphane Chazelas Jan 01 '21 at 18:27
  • Ugh. You're right. However, with exec tail -f ..., I can't get the Bash version to work (doesn't exit). The socat option looks useful. – bitinerant Jan 01 '21 at 19:20
3

With bash, you can use process substitution and redirection, and a while loop with read -t timeout. The subshell will be no more, when the while loop exits because of the timeout (That means when the python script has no output, seems inactive, for 2 minutes).

while IFS= read -r -t120 x; do
    printf "%s\n" "$x"
done < <(python3 script.py)

Note: I have assumed line-oriented output into this example (or else read and printf char by char).


The difference of this structure to using a pipe, is that we have one command here. When this command exits, the file input from the file descriptor is over, the command in the subshell writing into this descriptor is over. So the python script is killed just after the timeout.

When piping like this:

python3 script.py | { 
    while IFS= read -r -t120 x; do
        printf "%s\n" "$x"
    done
}

we execute two commands, and if the second one exits (because of the timeout), the first one will continue to run, until trying to write to the pipe again. When this happens, it will terminate, without writing anything more, because of broken pipe. So here the python script is killed when trying to write again after the timeout.

thanasisp
  • 8,122
  • Thanks for your reply. Context: My python script sometimes freezes cuz it's internet based. If it hasn't outputted anything in 2 min it means that it is frozen and it will never output anything again. Instead of waiting for me to find out that it's frozen and restarting it, I want it to happen automatically. Correct me if I'm wrong but this would not work for my situation because: If my python hasn't outputted in 2 min it means that is frozen and it will never output anything again so the problem of it being frozen will remain forever. – Ardalan Ghazizadeh Jan 01 '21 at 04:43
  • @ArdalanGhazizadeh if the script is inactive for 2 minutes, it will be terminated. – thanasisp Jan 01 '21 at 12:06
  • Excellent. IMHO, your answer is better than Stephane's. – ttsiodras Jan 07 '21 at 14:41
0

If the acceptable accuracy is ~1 second, you could use date +%s to collect the actual time and check how long there's no output from your script (collecting it as a shell variable):

ELAPSED_T=0 # the time that has passed since last output
LAST_OUT_T=`date +%s` # avoid to stop if the first iteration has empty output
while [ "$ELAPSED_T" -lt 120 ]; do # continue if the last output was printed less than 120 seconds ago
    OUT=`python3 /Users/Name/Desktop/pythoncode.py` # run the script and collect the output
    echo "$OUT" # print the output on screen
if [ -n &quot;$OUT&quot; ]; then # if the output is not empty, update the last output time
    LAST_OUT_T=`date +%s`
else # otherwise, evaluate how much time has passed since last output
    NOW_T=`date +%s`
    ELAPSED_T=$((NOW_T - LAST_OUT_T))
fi  

done

Maik93
  • 346
  • The assignment to OUT will not finish until the Python script exits. – Kusalananda Dec 31 '20 at 11:00
  • Correct, but I suppose that the author's script evaluates something, occasionally prints on the console, then terminates and is runed again, since the author wrapped it in an infinite loop. If @Ardalan could explain better his script behaviour we could understand better. – Maik93 Dec 31 '20 at 11:28
  • The issue seems to be that the Python script needs to be killed if it doesn't output anything during a certain amount of time. – Kusalananda Dec 31 '20 at 12:10
0

You might want to check out the GNU timeout command. It takes the duration as an argument followed by the command. Here is a simple invocation:

timeout 5s ping www.website.com

From its manpage:

TIMEOUT(1)                                                                          User Commands                                                                         TIMEOUT(1)

NAME timeout - run a command with a time limit

SYNOPSIS timeout [OPTION] DURATION COMMAND [ARG]... timeout [OPTION]

DESCRIPTION Start COMMAND, and kill it if still running after DURATION. ...

You can also specify the type of signal to send to the process (like HUP, 9, SIGTERM, etc.) at the end of the timeout.

HTH.