使用带有 SCP 的 bash 脚本来选择远程服务器上的目录
Using a bash script with SCP to choose directory on remote server
我的问题是,我想写一个bash脚本来上传一个文件到远程服务器,并使用文件名来确定使用远程服务器上的哪个目录。下面的脚本捕获文件名的第一部分以确定远程服务器上的目录。
#!/bin/bash
filename=
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 Three Men on the Same Horse.mp4为例,将文件定向到远程服务器上的"Herdmasters"目录。它也适用于第一部分有空格的情况 - Bonehead the Clown S03E15 My Stupid Dog.mp4 也适用。
但是,当文件名包含撇号或引号时,轮子会脱落 - I'd Hit That S02E09 Well, Maybe Not That.mp4 会抛出不匹配的引号错误。
如果我事先不知道文件名,我需要采取哪些步骤才能正确转义引号、括号等?
TL;DR。跳转到 "Solution to the problem".
教育material
你的剧本中的两个教训:
- 除非有正当理由,否则请始终引用参数扩展。
- 不惜一切代价避免
eval
(或至少非常谨慎地使用它),并且 从不 将它与任意字符串一起使用。
第一点应该很明显,第二点需要一些解释。就这样吧。
您的 ${BASH_REMATCH[1]}
实际上可以是任何东西, 和 eval
任意字符串可能会导致任意损坏 。您可以轻松地用恶意文件名摧毁您的计算机。例如,假设一个恶意文件名为 "'; ls $HOME; echo ' S01E01 whatever.mp4
。当你通过你的脚本 运行 它时,你将执行 ls $HOME
。如果您想亲自查看,只需尝试以下代码片段,将 ${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 可读的引用路径。因此,您可以做的是在路径周围添加单引号,并用 '\''
转义每次出现的单引号 '
('\''
的解释:第一个 '
关闭前一个单引号,\'
是字面单引号,最后一个'
开始新一轮的引号)。例如,try
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
我的问题是,我想写一个bash脚本来上传一个文件到远程服务器,并使用文件名来确定使用远程服务器上的哪个目录。下面的脚本捕获文件名的第一部分以确定远程服务器上的目录。
#!/bin/bash
filename=
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 Three Men on the Same Horse.mp4为例,将文件定向到远程服务器上的"Herdmasters"目录。它也适用于第一部分有空格的情况 - Bonehead the Clown S03E15 My Stupid Dog.mp4 也适用。
但是,当文件名包含撇号或引号时,轮子会脱落 - I'd Hit That S02E09 Well, Maybe Not That.mp4 会抛出不匹配的引号错误。
如果我事先不知道文件名,我需要采取哪些步骤才能正确转义引号、括号等?
TL;DR。跳转到 "Solution to the problem".
教育material
你的剧本中的两个教训:
- 除非有正当理由,否则请始终引用参数扩展。
- 不惜一切代价避免
eval
(或至少非常谨慎地使用它),并且 从不 将它与任意字符串一起使用。
第一点应该很明显,第二点需要一些解释。就这样吧。
您的 ${BASH_REMATCH[1]}
实际上可以是任何东西, 和 eval
任意字符串可能会导致任意损坏 。您可以轻松地用恶意文件名摧毁您的计算机。例如,假设一个恶意文件名为 "'; ls $HOME; echo ' S01E01 whatever.mp4
。当你通过你的脚本 运行 它时,你将执行 ls $HOME
。如果您想亲自查看,只需尝试以下代码片段,将 ${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 可读的引用路径。因此,您可以做的是在路径周围添加单引号,并用 '\''
转义每次出现的单引号 '
('\''
的解释:第一个 '
关闭前一个单引号,\'
是字面单引号,最后一个'
开始新一轮的引号)。例如,try
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