以编程方式使用特殊字符的scp

时间:2014-04-08 21:35:36

标签: perl bash unix ssh scp

我一直在寻找这个,但找不到满意的答案。

我有一个perl脚本需要将文件从一个主机复制到另一个主机,基本上是

sub copy_file{
    my($from_server, $from_path, $to_server, $to_path, $filename) = @_;

    my $from_location = "$from_server:\"\\\"${from_path}${filename}\\\"\"";
    my $to_location = $to_path . $filename;
    $to_location =~ s/\s/\\\\ /g;
    $to_location = "${to_server}:\"\\\"${to_location}\\\"\"";

    return system("scp -p $from_location $to_location >/dev/null 2>&1"");
}

问题是,我的一些文件名看起来像这样:

BLAH;BLAH;BLAH.TXT
Some really nicely named file( With spaces, prentices, &, etc...).xlx

我已经在处理空格了,而且代码很难看,因为在每一方面,文件可以是本地的或远程的,并且对于scp调用的from和to部分,转义是不同的。

我真正想要的是以某种方式逃避所有可能的特殊字符或以某种方式通过使用POSIX系统调用完全绕过shell扩展。如果需要,我可以编写XS模块。

我在.ssh目录中设置了正确的密钥 此外,我不确定哪些特殊字符可以做到并且不会导致问题。我想支持所有合法的文件名字符。

4 个答案:

答案 0 :(得分:4)

假设您要使用foo(s)复制文件scp

如下所示,scp将源和目标视为shell文字,因此您将以下参数传递给scp

  • scp
  • -p
  • --
  • host1.com:foo\(s\)host1.com:'foo(s)'
  • host2.com:foo\(s\)host2.com:'foo(s)'

您可以使用system的多参数语法以及转义函数来执行此操作。

use String::ShellQuote qw( shell_quote );

my $source = $from_server . ":" . shell_quote("$from_path/$filename");
my $target = $to_server   . ":" . shell_quote("$to_path/$filename");

system('scp', '-p', '--', $source, $target);

如果您真的想构建一个shell命令,请照常使用shell_quote

my $cmd = shell_quote('scp', '-p', '--', $source, $target);

$ ssh ikegami@host.com 'mkdir foo ; touch foo/a foo/b foo/"*" ; ls -1 foo'
*
a
b

$ mkdir foo ; ls -1 foo

$ scp 'ikegami@host.com:foo/*' foo
*              100%    0     0.0KB/s   00:00
a              100%    0     0.0KB/s   00:00
b              100%    0     0.0KB/s   00:00

$ ls -1 foo
*
a
b

$ rm foo/* ; ls -1 foo

$ scp 'ikegami@host.com:foo/\*' foo
*              100%    0     0.0KB/s   00:00

$ ls -1 foo
*

答案 1 :(得分:2)

有三种方法可以解决这个问题:

  1. 使用system的多参数形式,它将完全避免shell:

    system 'scp', '-p', $from_location, $to_location;
    

    缺点:您无法使用重定向等shell功能。

  2. 使用String::ShellQuote转义字符。

    $from_location = shell_quote $from_location;
    $to_location   = shell_quote $to_location;
    

    缺点:可能存在某些无法安全引用的字符串。此外,此解决方案不可移植,因为它采用Bourne shell语法。

  3. 使用IPC::Run本质上是一个允许重定向的增压system命令。

    run ['scp', '-p', $from_location, $to_location],
      '>', '/dev/null',   # yes, I know /dev/null isn't portable
      '2>', '/dev/null';  # we could probably use IO::Null instead
    

    缺点:像这样的复杂模块有一定的局限性(例如Windows支持是实验性的),但我怀疑你会遇到任何问题。

  4. 我强烈建议您使用IPC :: Run。

答案 2 :(得分:0)

一些建议:

  • 它不是标准Perl发行版的一部分,但Net::SSH2Net::SSH2::SFTP是两个排名靠前的CPAN模块,可以满足您的需求。 sshscpsftp都使用相同的协议和sshd守护程序。如果您可以使用scp,则可以使用sftp。这为您提供了一种非常好的Perlish方式,可以将文件从一个系统复制到另一个系统。
  • quotemeta命令将接受一个字符串并为您引用所有非文本ASSCII字符。这比使用s/../../替换自己做这种情况更好。
  • 您可以使用qq(..)q(...)代替引号。这将允许您在字符串中使用引号,而无需反复引用它们。这在system命令中非常有用。

例如:

my $error = system qq(scp $user\@host:"$from_location" "$to_location");
  • 还有一个小技巧:如果system命令传递了一个参数,并且该参数中包含shell元字符,则system命令将传递给默认系统shell。但是,如果您传递system命令 list 项目,则这些项目将直接传递给execvp,而不会传递给shell。

通过数组将a_list_参数传递给system是避免文件名出现问题的好方法。避免使用空格,shell元字符和其他shell问题。

my @command;
push @command, 'scp';
push @command, "$from_user\@$from_host:$from_location",
push @command, "$to_user\@$to_host:$to_location"
my $error = system @command;

答案 3 :(得分:0)

使用Net::OpenSSH

my $ssh = Net::OpenSSH->new($host);
$ssh->scp_get($remote_path, $local_path);

必须引用参数的方式取决于远程端运行的shell。该模块的稳定版本支持Bourne兼容shell。 GitHub提供的development version也支持csh和几种Windows风格(Windows引用,错误,有趣)。

例如:

my $ssh = Net::OpenSSH->new($host, remote_shell => 'MSWin', ...);

请注意,在Windows上,有一些字符串无法正确引用!