Igor Tiulkanov
Igor Tiulkanov

Reputation: 596

Using perl system() query with spaces in path variable

I seem to be stuck here. I want to send a system request from my script to another server via SSH, checking if a folder there exists. A folder path is passed from another script, stored in a variable and might have a space character in it. Since I couldn't replace the space with another character, to avoid a "not found" on folder like "foo bar", I need to pass something like ls '/folderpath/foo bar' to other server's shell. Sample code looks like this:

$cmd = 'ssh -i id_pub $ssh_addr ls $remote_dir'; 
if (system($cmd) == 0) {
do something
}

I've exhausted all possible options - tired to escape the possible space with \ before passing it to the command, tried to pass it with ' ', " ", inside and adding both before passing it into $cmd. But I always end up with something like this:

ls \folderpath\foo\\ bar or ls \' \folderpath\foo bar\'

but not ls '\folderpath\foo bar'

I'm not that good with Perl, possible someone more experienced can recommend a workaround?

Upvotes: 6

Views: 1606

Answers (4)

salva
salva

Reputation: 10242

Let Net::OpenSSH take care of everything for you:

my $ssh = Net::OpenSSH->new($ssh_addr);
$ssh->error and die "unable to connect to remote host: " . $ssh->error;
if ($ssh->test('test', '-d', $remote_dir)) {
   # do something here!
}

Oh, it seems you are on a Windows machine! You can use Net::SSH::Any there in a similar fashion.

Upvotes: 1

Charles Duffy
Charles Duffy

Reputation: 295650

Running a local shell and using it to escape your command to be safe for the remote shell would look like this:

system('env', "ssh_addr=$ssh_addr", "remote_dir=$remote_dir", 'bash', '-c',
       'printf -v remote_cmd "%q " ls -- "$remote_dir"; ssh "$ssh_addr" "$remote_cmd"');

Unlike just using "'$remote_cmd'", the above works with all possible values, including intentionally malicious ones, so long as your remote shell is also bash.

Thanks to @ikegami's answer for demonstrating the use of the end-of-options sigil -- to ensure that even a remote_dir value that starts with dashes is parsed as a positional argument by ls

Upvotes: 5

ikegami
ikegami

Reputation: 386386

String::ShellQuote's shell_quote is useful in building shell commands.

my $remote_cmd = shell_quote("ls", "--", $remote_dir);
my $local_cmd = shell_quote("ssh", "-i", "id_pub", $ssh_addr, $remote_cmd);
system($local_cmd);

Of course, you can avoid the shell on the local side as follows:

use String::ShellQuote qw( shell_quote );

my $remote_cmd = shell_quote("ls", "--", $remote_dir);
system("ssh", "-i", "id_pub", $ssh_addr, $remote_cmd);

Upvotes: 5

JGNI
JGNI

Reputation: 4013

OK you have several possibilities for shell expansion with the way you are doing this.

Firstly is using system() with a string. This will break all your paths on the space characters. you can solve this by using system as a list

system('ssh', '-i', 'id_pub', $ssh_addr, 'ls', $remote_dir)

Now we still have a problem as ssh will run the remote code on the remote server in a shell with shell expansion which will break the path on spaces again

So you need to put $remote_dir inside ' characters to stop the remote shell from breaking up the path: giving

system('ssh', '-i', 'id_pub', $ssh_addr, 'ls', "'$remote_dir'")

Hope this helps/works

Note that as the commenters below have said this makes the assumption that $remote_dir has no ' characters in it. You need to be either escaping or parsing $remote_dir to ensure that you don't get a path that looks like /file.txt'; rm -rf / # which will attempt to remove every file on the remote system

Upvotes: 2

Related Questions