如何在 bash 中的源函数中的 scp 命令中正确转义空格形成多个文件

How to properly escape spaces form multiple files in an scp command in a sourced function in bash

我在我的 .bashrc 中构建了一个函数,当它尝试对名称中包含空格的文件进行 scp 时会中断,但是如果我 运行 从该函数生成的命令输出在 shell 直接它似乎工作正常。

我试过转义空格,以及单引号和双引号的几种变体,下面的版本是我最接近工作的版本,但我不明白为什么会失败。

来自 .bashrc

push2() {
    # parse args, build file list, get suffix from final arg
    files=""
    i=1
    orig_IFS=$IFS; IFS=":"
    for arg in $*; do
        if [ "$i" = "$#" ]; then
            suffix=$arg
        else
            files="$files $(echo $arg | sed -r 's/ /\ /')" #escape spaces
        fi
        i=$(($i+1))
    done
    IFS=$orig_IFS
    # determine prefix and proxy
    gen_prefix $suffix
    # output generated command for debugging
    echo "scp $scp_proxy$files testuser@$prefix$suffix:"
    # run command
    scp $scp_proxy$files testuser@$prefix$suffix:
}

运行 即使输出命令字符串显示正确转义,函数似乎仍然失败

root@DHCP-137:~$ push2 temp* 42
scp  temp temp\ file testuser@10.3.3.42:
temp                                          100% 6008     1.1MB/s   00:00 
temp\: No such file or directory
file: No such file or directory

运行 它生成的命令按预期工作

root@DHCP-137:~$ scp  temp temp\ file testuser@10.3.3.42:
temp                                          100% 6008   896.4KB/s   00:00 
temp file                                     100%    0     0.0KB/s   00:00 
root@DHCP-137:~$ 

附加信息:GNU bash,版本 4.4.12(1)-release (x86_64-pc-linux-gnu) - 运行ning on Debian 9

整个事情通过更改最后一行得到修复

scp $scp_proxy$files testuser@$prefix$suffix:

并将其包装在这样的 eval 中

eval "scp $scp_proxy$files testuser@$prefix$suffix:"

首先,更改您的呼叫签名,使后缀在前:

push2 42 ./temp*

那么函数应该简单定义为

push2 () {
  local -a scpProxy
  local prefix suffix
  suffix=
  shift

  gen_prefix "$suffix"

  scp "${scpProxy[@]}" "$@" "testuser@$prefix.$suffix:"
}

其中 gen_prefix 看起来像

gen_prefix () {
  case  in
     42) scpProxy=()
         prefix=10.3.3
         ;;
     89) scpProxy=(-o ProxyJump=user@server)
         prefix=123.456.789
         ;;
  esac
}

调用 shift 后,$@ 只包含您要传输的文件。 scpProxy 是一个数组,其中包含要传递给 scp 的多个单独参数;如果为空,则 "${scpProxy[@]}" 将扩展为 0 个参数,而不是空字符串。

(使用 ./temp* 而不是 temp* 可以防止包含 : 的匹配项因此可能被误认为是远程文件名。)

尽管 gen_prefix 似乎定义了它的变量 "globally",但它实际上只是在 gen_prefix 调用 的任何范围内定义它们(bash 使用动态范围,而不是词法范围,就像大多数其他常见语言一样)。对 local 的两次调用确保 gen_prefix 分配的内容保留在 push2 中,并且在 push2 退出后不可见。


作为附加说明,此功能的大部分可以通过合适的 ssh 配置消失。在您的 .ssh/config 文件中考虑这一点:

Host somehost
    User testuser
    Hostname 10.3.3.42

Host someotherhost
    User testuser
    Hostname 123.456.789.89
    ProxyJump user@server

现在您根本不需要 push2;只是 运行

scp temp* somehost:

scp temp* someotherhost:

并且将自动使用正确的地址和选项。 ssh 配置取代了 gen_prefix 所做的一切,并且无需调用 gen_prefix,不再需要包装 scp.