【问题标题】:Using a bash script with SCP to choose directory on remote server使用带有 SCP 的 bash 脚本来选择远程服务器上的目录
【发布时间】:2015-11-15 02:16:11
【问题描述】:

我的问题是我想写一个 bash 脚本来上传一个文件到远程服务器,并使用文件名来确定远程服务器上的哪个目录要使用。下面的脚本捕获文件名的第一部分以确定远程服务器上的目录。

#!/bin/bash

filename=$1
regex="(.*) S([0-9]{2})E([0-9]{2}) (.*)\.mp4"
for filename; do
    if [[ "$filename" =~ $regex ]]; then
        a=`echo scp \"$filename\" mal@serenity.local:\'\"/Volumes/Vault/${BASH_REMATCH[1]}\"\'`
        eval $a
fi
done

我的困难是该脚本适用于文件名中确定目录的部分是单个单词的文件。例如,Herdmasters S01E14 同一匹马上的三个人.mp4 将文件定向到远程服务器上的“Herdmasters”目录。当第一部分有空格时它也有效 - Bonehead the Clown S03E15 My Stupid Dog.mp4 也有效。

但是,当文件名包含撇号或引号时,轮子会脱落 - Id Hit That S02E09 Well, Maybe Not That.mp4 会引发不匹配的引号错误。

如果我事先不知道文件名,我需要采取哪些步骤来正确转义引号、括号等?

【问题讨论】:

    标签: bash escaping quotes


    【解决方案1】:

    TL;DR。跳转到“问题的解决方案”。

    教育材料

    脚本中的两个教训:

    1. 除非您有正当理由不这样做,否则请始终引用参数扩展。
    2. 不惜一切代价避免使用eval(或至少非常谨慎地使用它),并且切勿将其与任意字符串一起使用。

    第一点应该很明显,第二点需要解释一下。就这样吧。

    您的${BASH_REMATCH[1]} 几乎可以是任何东西,eval 输入任意字符串可能会导致任意损坏。您可以轻松地使用恶意文件名将您的计算机炸飞。例如,假设一个恶意文件名为"'; ls $HOME; echo ' S01E01 whatever.mp4。当您通过脚本运行它时,您将执行ls $HOME。如果您想亲自查看,只需尝试以下 sn-p,它将${BASH_REMATCH[1]} 放在$malicious_string 中(set -x 让您查看正在执行的内容):

    set -x
    malicious_string="\"'; ls \$HOME; echo '"
    a=`echo scp \"$filename\" mal@serenity.local:\'\"/Volumes/Vault/$malicious_string\"\'`
    eval $a
    

    你最后执行的命令,根据set -x,是

    scp '' 'mal@serenity.local:"/Volumes/Vault/"'; ls /Users/johndoe; echo '"'
    

    想象一下,如果我将 ls $HOME 替换为 rm -rf $HOME


    问题的解决方法

    在这种情况下,您根本不需要eval,尽管引用scp 的文件名仍然很麻烦。 scp 期望您传递远程 shell 可读的引用路径。因此,您可以做的是在路径周围添加单引号,并用'\'' 转义每次出现的单引号''\'' 的解释:第一个' 关闭前一个单引号,\' 是一个字面单引号,最后一个 ' 开始新一轮的引用)。例如,尝试

    stupid_name=$'word \n $`\"'\'  # This is the most stupid filename ever, with space, $, `, ", \, and '
    quoted_stupid_name=\'${stupid_name//\'/\'\\\'\'}\'  # This command wraps $stupid_name in single quotes and replace each occurrenceof ' with '\''
    touch file && scp file user@domain:"$quoted_stupid_name"
    

    把它们放在一起,你的最终产品是

    #!/bin/bash
    regex="(.*) S([0-9]{2})E([0-9]{2}) (.*)\.mp4"
    for filename; do
        if [[ "$filename" =~ $regex ]]; then
            remote_filename="/Volumes/Vault/${BASH_REMATCH[1]}"
            quoted_remote_filename=\'${remote_filename//\'/\'\\\'\'}\'
            scp "$filename" mal@serenity.local:"$quoted_remote_filename"
    done
    

    【讨论】:

    • 也就是说,最简单的解决方案是在文件名中不包含空格和特殊字符。省去了很多麻烦...
    • 这正是我需要的帮助水平。您不仅为我的问题制定了解决方案,还解释了为什么我以前的方法是一个危险的想法,从而使我免于遭受我什至不知道自己受到损害的风险。我非常感激;感谢您的宝贵时间。
    • @user2480136 您实际上可以接受此答案,因此通过单击此答案左侧的复选标记来标记问题已解决。
    • 完成!真的非常感谢。
    猜你喜欢
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-17
    • 2019-03-28
    • 2019-01-31
    相关资源
    最近更新 更多