Kernel.system doesn't ignore output

I have a small script that I run in WSL (Debian) on ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu].

It does a simple thing:

cmd = ['ffmpeg', '-hide_banner', '-loglevel', 'panic', '-i', "#{video_file}",
     '-i', "#{audio_file}", '-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', "#{output}"].join(' ')
@log.debug("Command: #{cmd}")
system(cmd) or raise 'It broke'

As far as I understand, this should cause Ruby to ignore all output from FFmpeg (even though there is supposed to be none), but what happened is FFmpeg was barfing output all over the terminal breaking things.

How is this possible?

I wanted a way to feed array to system() call, so I would specify actual command once as an array and then reuse it.

8 comments · 75% upvoted · other discussions

comments

bradland

You can also use Kernel#` to soak up any output, then check$?(in Ruby, not in the shell) for status. This is my goto method for when I don't care about the shell output; only status. I wrap something like this in arun_cmdmethod that exists in aShellScriptUtils module that I include all over the place.

`#{cmd}`
raise 'It broke' if $? != 0

It seems in your other comment you've switched to using Open3, which of course gives you even more control.

Gendalph [OP]

In this case this approach is fine, but in general I would rather avoid checking global vars and instead go with something like Open3.

Kernigh

Discard output with out: '/dev/null' or errors with err: '/dev/null'

system('ls', '/dev', '/nowhere',
       out: '/dev/null', err: '/dev/null')

This works only if the system has a /dev/null.

f9ae8221b

You can use File::NULL instead for portability.

Gendalph [OP]

Thank you, it worked, but still something is leaking somewhere.

#!/bin/sh -e
grep '^https:' list.txt | while read -r url; do
        echo "# Downloading $url"
        download.rb --url "$url"
        echo
        sleep 0.5
done

For some reason first URL works fine, but following URLs lose one character at the start, even if I discard STDOUT and STDERR, it also doesn't happen with Open3.capture3, so I'm pretty sure system() is leaking something.

postmodern

system() will send all output to stdout. You might want to pass ffmpeg options to silence output, like -nostats -loglevel 0. Or you could use Shellwords.shelljoin to safely combine/escape the arguments into a single String then append 2>&1 >/dev/null.

f9ae8221b

That will create a subshell which isn't necessary. Might as well pass the commands components as positional argument and then out: File::NULL, err: File::NULL.

Gendalph [OP]

Thank you.

-loglevel changes based on verbosity argument and beer goes below fatal, but at some point, when script is running in a for cycle, it gets ignored completely by FFmpeg. I've printed out the command, interrupted the script, ran the command manually and FFmpeg ran silently.

Regardless, this answers my question and I already use Open3 to run FFmpeg.