Here's what I do. This function doesn't really need to be an object/class method, but it does make it easier when it's in a base class to be able to call $self->quote_cmd from a derived object.

# shellish quote. sub quote_cmd { my $self = shift; if (any { ref $_ eq 'ARRAY' } @_) { return join ' ; ', map { $self->quote_cmd(@$_) } @_; } # if we have only one parameter, assume it's a full command by # itself, already quoted (nothing else we can do anyway). return $_[0] if @_ == 1; join ' ', apply { if (not defined) { die("Undefined command parameter?"); } elsif (0 == length) { $_ = q[''] } elsif (/[\s"]/ && !/['\$]/) { s[\\][\\\\]g; s['] [\\']g; $_ = qq['$_'] } elsif (/['\$]/) { s['] ['"'"']g; $_ = qq['$_'] } } @_; }
As for double-ssh'ing, I do that do. I just quote the whole thing again.
# @options has options for ssh itself. my @cmd = ( qw(ssh), @options, $self->usernode(), $self->quote_cmd(@cmd) ); # for testing purposes, allow proxying. if ($ENV{SSH_TEST_PROXY}) { @cmd = ( qw(ssh), @options, $ENV{SSH_TEST_PROXY}, $self->quote_cmd(@cmd) ); }
And then it's all taken care of. I haven't yet found a case where this doesn't work, though I haven't yet had a need to try more than two ssh calls.

My unit tests involve a perl script that takes its args, converts to JSON, and prints it out. The .t file then gets the stdout, decodes the JSON, and compares it to what it sent in (think "is_deeply"). Spaces, quotes (both single and double), etc., all work fine.

Note that my expectation is that the command and arguments are set up as if you were running system(@cmd), and not system($cmd). Of course, this does mean that my code doesn't really go for redirection (> /dev/null, 2>&1, etc.), but that's okay for me because I use IPC::Open3 and IO::Select to read all the output from the ssh calls on my (caller) end, and then I work with the output here. (If you try to pass in the redirection characters, this will kinda choke, but those characters could be added, and, again, it's not a concern here - this didn't go to CPAN.)

My next project here, after a conversion to AnyEvent, is to create a wrapper script where I'll pass the parameters in a method independent of the shell, and have that script do the select bit, sending back all pertinent data via JSON (probably in chunks). The wrapper script will then be able to handle more than simple parameters (args:["a","b","c d e"]), but also set environment variables (env:{foo:"blah"},addenv:{PATH:"/some/extra/path"}), drop privileges if run as root (user:"nobody"), etc., almost like its own minilanguage :-)


In reply to Re: safely passing args through ssh by Tanktalus
in thread safely passing args through ssh by perl5ever

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.