【问题标题】:scp with special characters programmatically以编程方式使用特殊字符的 scp
【发布时间】:2014-05-21 20:03:14
【问题描述】:

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

我有一个 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 目录中设置了正确的密钥 另外,我也不确定哪些特殊字符会引起问题,哪些不会引起问题。我想支持所有合法的文件名字符。

【问题讨论】:

  • 你不能使用 SFTP 吗?这是摆脱中间壳的最简单方法

标签: perl bash unix ssh scp


【解决方案1】:

使用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 上,有些字符串不能正确引用!

【讨论】:

    【解决方案2】:

    假设你想使用scp复制文件foo(s)

    如下所示,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
    *
    

    【讨论】:

    • 谢谢,“--”只是一个例子,还是出于某种原因需要它?
    • 它标志着选项的结束。如果 $source 以 - 开头,它可以避免问题,诚然,在这种特定情况下似乎不可能。不过,这是一个好习惯;我宁愿多一个--,也不愿少一个。
    • 谢谢,我的脚本的 SCP 部分正在运行,但我有一个验证步骤仍然没有。我想知道你能不能帮忙。以下行未正确转义:system('ssh', $to_server, 'stat' .shell_quote($to_location))
    【解决方案3】:

    一些建议:

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

    例如:

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

    通过数组将参数列表传递给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;
    

    【讨论】:

    • scpSFTP 是两个完全不同的协议。即使两者通常都可用,但也有一种情况,另一种不可用。
    • 我不会称它们为两种完全不同的协议。两者都是在 SSH 协议之上实现的,并且可以由同一个 sshd 守护进程提供服务。
    • 嗯,你的句子很混乱。 SCP 和 SFTP 通常都使用 SSH 作为传输层,但它们并不建立在 SSH 协议之上。类似于 HTTP 和 TCP 之间发生的情况:TCP 通常是 HTTP 的传输层,但 HTTP 不是 TCP 的扩展。
    【解决方案4】:

    有三种方法可以处理这个问题:

    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 支持是实验性的),但我怀疑你会在这里遇到任何问题。

    我强烈建议你使用 IPC::Run。

    【讨论】:

    • 我不确定这是否可行,在另一台机器上转义怎么办。我会给系统 'scp', '-p', $from_location, shell_quote $to_location;试一试
    • @Smartelf 在另一台机器上无需考虑转义。不要双重转义文件名。
    • IIRC、$from_location 和 $to_location 被解释为远程服务器上的 glob。您需要两个级别的引用。我得跑了,但我会尽快检查一下
    • Re“某些字符串可以存在,不能安全引用。”,所述字符串也不能通过任何其他方式传递,所以这几乎不是缺点。
    • 是的,我没记错。这些都不行。看我的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 1970-01-01
    • 2020-09-20
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    相关资源
    最近更新 更多