Perl’s exec, system, and fork functions all let one execute system commands, and they all do it by driving a POSIX exec system call so they don’t invoke a shell. But Perl’s backtick operator (aka the qx quotelike operator), the easiest way to capture output from a command, invokes a shell, so the arguments to the command need to be shell-escaped.
Sysadm::Install has quote and qquote functions that do a good job of this, but it’s easy enough to avoid needing shell escaping.sub command { my ($command, $dontchomp) = @_; $dontchomp //= 0; open(my $ph, qq{$command|}) or die "Can't fork $command: $!"; my $raw = do { local $/ = <$ph> }; close($ph) or die "$command returned error: $! $?"; chomp $raw unless $dontchomp; return wantarray ? split "\ ", $raw : $raw; }
Note that you pass the whole command line as one string, arguments and all. The default behavior is to chomp the command output, which differs from the native backtick behavior; you can pass a true value for the second arg, $dontchomp, if you don’t want it.
Reading all the output into a scalar is suitable for things with modest amounts of output (like the situtations in which you would have been using a backtick.) If you were processing a lot of output, you’d want to open the pipehandle and iterate on <$ph> yourself.
The reason I went down this rabbit-hole is wanting to get info from ratpoison so I could write some window management scripts. When you pass ratpoison a command with its -c option, it expects the whole command as one string. So we do have to worry about quote-escaping that string, like so:
sub rp { local $_ = "@_"; s/"/\\\\"/g; return command(qq{ratpoison -c "$_"}); }
The way this is written, you can pass your arguments as a string or as a list, to taste.
rp("windows %c %t %n"); rp("windows", "%c", "%t", "%n");
Ratpoison lets you pass it multiple commands at once, and this rp routine doesn’t, but that’s usually what you want when you’re getting info out of it.