IPC::Cmd does its job, but the interface is inconsistent, probably because a different author wrote run_forked() (and didn't follow the same convention). run() accepts a hash of arguments, but run_forked() accepts a scalar + hashref. run() returns a list, but run_forked() returns a hashref. Command in run_forked() cannot be an arrayref, but in run() it can be.
If you take a look at the documentation for IPC::Run or IPC::Open3, you'll see why a lot of us are overjoyed to have this sane, easily comprehensible wrapper around them that does the job of running an external command and capturing it's output for you in a simple, portable way.
I have one complaint about the interface (and I don't think it's possible to pick-up this oddity just from reading the documentation): if you do a scalar call to "run", the output buffer contains lines from stdout and stderr mixed together in chronological order. But if you do a list call, the "full_buffer" turns out to be an arrayref containing stdout and stderr as separate elements. That makes this "full_buffer" redundant with the stdout_buffer and stderr_buffer. In summary: If you want stdout and stderr intermixed, you need the a scalar context call, if you want just stdout or stderr, you need a list context call. This is a peculiar design glitch in a module who's reason for existence is it's clean interface.