Search code examples
rubyloggingtail

back-tick in ruby does not work with tail -f command


The code below does not print the output of tail -f. Why? How can I make it work?

#myApp.rb
`ls`               #works fine
`tail -f filename`      #does not work. why?

Solution

  • By using the follow option -f on tail the executed command will not immediately terminate.

    -f, --follow[={name|descriptor}]
      output appended data as the file grows;
    

    The idea of using backticks (or the %x shortcut), as opposed to using system('...') is that these statements return the output of the executed commands. This way you could store the result in a variable:

    dir_content = `ls`
    

    tail -f spawns another process, this process keeps on writing to standard output without terminating. Hence, your statement is never finished. Without finishing the statement, a value cannot be returned. If you want to watch and output of a growing file, see the solution to the question:

    Watch/read a growing log file.

    Alternatively, you could run the command through system like so:

    system('tail -f filename')
    

    What is the difference here? Instead of returning the outpout of the command, it will return true (command run successfully), false (unsuccessful) or nil (command execution failed). Due to the fact, that the output of the command is not redirected to the return statement running tail -f it will print the content to standard output.

    If you are fine with getting the results to standard output you can simply put it into a Thread block. This way the growing content of filename is written to standard output and you can continue to execute other Ruby code.

    Thread.new { system('tail -f filename') }
    

    If you want to have complete control, capture the output in order to store it for retrieval at another point of your script then have a look at the answer to the following question which describes such an approach:

    Continuously read from STDOUT of external process in Ruby

    It is based on the PTY module which creates and manages pseudo terminals. Basically you can spawn another terminal through this module. The code could then look like so:

    require 'pty'
    cmd = "tail -f filename" 
    begin
      PTY.spawn(cmd) do |stdin, stdout, pid|
        begin
          # Do something with the output here: just printing to demonstrate it
          stdin.each { |line| print line }
        rescue Errno::EIO
          puts "Errno:EIO error, but this probably just means " +
                "that the process has finished giving output"
        end
      end
    rescue PTY::ChildExited
      puts "The child process exited!"
    end
    

    Finally, there is also a socket approach. Open a socket in Bash or by using socat, pipe the output of tail -f to the socket and read in Ruby from the socket.