Search code examples
pythonbufferparamikoreadlinetail

Python breaking out of tail -f process when remote process is done


I have two processes running

  1. exeAPI (running in the background with nohup)
  2. tail -f server.log to output the remote server log

I am able to successfully see the output flush to my python client console like this:

@bp.route('/executeAPI', methods=['GET', 'POST'])
def executeAPI():

    ...
    (ssh set up params)

    print('started...')
    data_buffer = ""
    stdin_run, stdout_run, stderr_run = client.exec_command('nohup echo source "exeAPI" | nc 127.0.0.1 40000 &')

    stdin, stdout, stderr = client.exec_command('tail -n 1 -f /home/server.log', get_pty=True)

    for line in iter(lambda: stdout.readline(1024), ""):
        data_buffer += line
        print(line, end="")
        if not data_buffer:          # never hits this if statement
            print('No more data')
            break

    print('finished.')
    client.close()

    return data_buffer

However, it never exits the for loop after the output is complete.

How can I break out of the for loop when there are no newlines?


Solution

  • By design tail -f doesn't exit when a process is done writing to the log file (it doesn't really know the difference between the log being done and the process just taking a really long time to write its next line). What you probably want is tail -f --pid=PID if you can obtain the pid of your background process (for example by using ps). According to the documentation this should terminate tail after the process with process ID PID dies.

    Furthermore, the if not data_buffer check does not indicate No more data. The if condition will only be true, if tail returns without a single byte written to stdout, as data_buffer will be non-empty after the first non-empty line is read and appended. You could check line instead, but since you are using iter() with a sentinel value, this would be redundant.