TL;DR。跳转到“问题的解决方案”。
教育材料
脚本中的两个教训:
- 除非您有正当理由不这样做,否则请始终引用参数扩展。
- 不惜一切代价避免使用
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