我创建了一个通过lftp上传的脚本:
#! /usr/bin/bash
set -xe
if [ -n "$1" ]; then
# ...
else
source="."
# target=name of current local folder
target="${PWD##*/}"/
cmd="mirror --reverse --continue --parallel=5 "$source" "$target""
fi
lftp -u $user,$pass $host << EOF
set log:enabled true
eval "$cmd"
quit
EOF
如果当前目录不包含空格,则此操作正常。当当前目录确实有空格时会出现此问题。调试信息显示如下:
➜ upload.sh
+ '[' -n '' ']'
+ source=.
+ target='Two Words/'
+ cmd='mirror --reverse --continue --parallel=5 . Two Words/'
由于此问题,脚本会上传到名为 的目录,Two
而不是Two Words
.
我不知道如何解决这个问题,特别是因为我不知道哪一行是我出错的地方:cmd=...
?eval "$cmd"
?两者都不?两个都?
无论如何,我期望调试输出看起来像这样:(+ cmd='mirror --reverse --continue --parallel=5 "." "Two Words/"'
注意双引号),但我不确定为什么它没有。
这个问题与我到目前为止研究的许多其他问题类似。这对谷歌来说不同/困难的是,扩展发生在“这里字符串”内部。据我所知,这无关紧要,但据我所知,它也可能是非常独特的。
评论建议我将所有内容存储到数组中。这是我的尝试:
#! /usr/bin/bash
set -xe
if [ -n "$1" ]; then
cmd=(mput -c -P 5 "$1")
if [ -n "$2" ]; then
cmd=(mkdir -p "$2"
mput -c -P 5 -O "$2" "$1") #*
fi
else
source="."
# target=name of current local folder
target="${PWD##*/}"/
cmd=(mirror --reverse --continue --parallel=5 "$source" "$target")
fi
lftp -u $user,$pass $host << EOF
"${cmd[@]}"
quit
EOF
(* 我不知道如何在子 shell 中包含多行。这就是为什么我在其中放置了实际的换行符,但我不确定这是否是正确的做法。我谷歌搜索的每个结果都与命令替换有关,不是子壳。)
当我运行这个时,它给了我这个错误:Unknown command 'mirror --reverse --continue --parallel=5 . Two Words/'.
如果我将其复制并粘贴到我的 shell 中,它会给我一个不同的错误,所以我不明白发生了什么。
打电话lftp -u $user,$pass $host -e "${cmd[@]}"
也打不通。这给出了这个错误:
open: unrecognized option '--reverse'
Usage: lftp [-e cmd] [-p port] [-u user[,pass]] <host|url>
运行lftp ... -e "$("${cmd[@]}")"
出现这个错误:
open: option requires an argument -- 'e'
Usage: lftp [-e cmd] [-p port] [-u user[,pass]] <host|url>
正如您所看到的,此时我只是尝试随机的东西并希望它能提供见解。这不是一个好的策略。
答案1
您需要在语言语法中为s命令引用这些参数(或者至少转义lftp
s 语言中的那些空格或其他特殊字符)。lftp
mirror
lftp
lftp
支持简单的引用形式。虽然与大多数 shell 一样,它可以使用单引号、双引号和反斜杠,但语法不同。您可以使用lftp
's命令进行实验echo
。
lftp :~> echo \'
'
lftp :~> echo \a
\a
lftp :~> echo \
> a
a
\
是转义运算符,但仅适用于某些字符。当后面跟有换行符时,它是续行符,它不会转义换行符。
lftp :~> echo "a\'"
a\'
lftp :~> echo "a\"b"
a"b
lftp :~> echo "a\$b"
a\$b
在双引号内,它转义的字符列表进一步减少。"
里面可能有一个垃圾"..."
。\"
lftp :~> echo 'a\'b'
a'b
lftp :~> echo 'a\\b'
a\b
单引号内相同。'...'
那里的引号并不像 sh 一样的 shell 中的强引号。
lftp :~> echo $'a\nb'
$a\nb
$'...'
不支持ksh93 样式的引用形式。
lftp :~> echo 'foo
foo
lftp :~> echo 'a\
b'
ab
引号不必成对出现。似乎不可能在命令参数中包含换行符,无论它是否在引号内。
'...'
从代码看, or里面需要转义的字符"..."
只是反斜杠和对应的引号字符。 lftp
在字节级别工作,它不会将字节序列解码为字符。
因此,在这里,在 语法中正确引用字符串的最佳方法lftp
是使用 转义其中的每个'
and \
(实际上是 0x5c 字节,甚至是其他多字节字符编码中存在的字节)并将\
其包装在 中'...'
。或者与 相同"..."
。
在这里,使用eval
here是不必要的,并且会增加更多的复杂性,因为您需要管理两个级别的引用。
#! /usr/bin/bash -
set -xe
lftp_quote() {
local LC_ALL=C
local -n var
for var do
if [[ $var = *$'\n'* ]]; then
printf >&2 '%s\n' "\"$var\" contains newline characters. That's not supported by lftp"
exit 1
fi
var=${var//'\'/'\\'}
var=${var//"'"/"\'"}
var="'$var'"
done
}
if (( $# > 0 )); then
...
else
source="."
# target=name of current local folder
target="${PWD##*/}"/
lftp_quote source target
cmd="mirror --reverse --continue --parallel=5 -- $source $target"
fi
lftp_quote user pass host
lftp << EOF
set log:enabled true
open --user $user --password $pass -- $host && $cmd
EOF
(这里还要避免在命令行上传递密码,因为密码是公开的)
现在,虽然我们可能已经设法将这些字符串按原样传递给lftp
的mirror
命令,但lftp
它本身是否会在 FTP 协议的命令中正确转义这些文件名是另一回事。 FTP 因在这方面不可靠而臭名昭著,不同的服务器表现不同。请注意,lftp
除了 FTP 之外,它还支持许多其他文件传输协议,其中一些协议更为可靠。
答案2
我不知道如何解决这个问题,特别是因为我不知道哪一行是我出错的地方:
cmd=...
?eval "$cmd"
?两者都不?两个都?
无论如何,我期望调试输出看起来像这样:(
+ cmd='mirror --reverse --continue --parallel=5 "." "Two Words/"'
注意双引号),但我不确定为什么它没有。
你那里有的是:
cmd="mirror --reverse --continue --parallel=5 "$source" "$target""
第一个"
(at cmd="
) 开始一个带引号的字符串,第二个 (at "$source
) 结束它,接下来的字符串也是如此。生成的字符串中没有任何引号。你需要使用
cmd="mirror ... \"$source\" \"$target\""
要在字符串中添加双引号,或者如果单引号适用于 lftp,您可以使用
cmd="mirror ... '$source' '$target'"
cmd=(mirror --reverse --continue --parallel=5 "$source" "$target") lftp -u $user,$pass $host << EOF "${cmd[@]}"
当我运行此命令时,它给出了此错误: Unknown command 'mirror --reverse --continue --parallel=5 。两个字/'。
出色地。扩展"${array[@]}"
在 shell 中很有用,因为它将数组元素扩展为多个不同的 shell 单词(字符串),保持带空格的文件名完整。但在这里,我们位于一个here-doc中,所以不可能生成多个单词/字符串:整个here-doc只是一个字符串。所以它只是将数组元素与空格连接起来并扩展到该值。那里的引号会保留下来,就像在定界文档中一样。
lftp 获取的命令是
"mirror --reverse --continue ..."
由于引号,它将其视为单个命令,比较例如
lftp :~> "echo foo"
Unknown command `echo foo'.
lftp :~> echo foo
foo
问题标题还询问“[扩展]定界文档中的变量作为一个参数”,但这不是问题,因为不可能这样做(并且有人可能会说“参数”的概念不存在于无论如何,这里的文档)。不,您的问题是为 lftp 正确引用。
如果我将其复制并粘贴到我的 shell 中,它会给我一个不同的错误,所以我不明白发生了什么。
大概是这样的bash: mirror: command not found
?有mirror
一个用于 lftp 的命令,所以我不确定将其粘贴到 shell 的命令行是否有用。
我理解使用数组来存储 shell 命令的建议,有一些非常好其原因。但这里没有 shell 命令,所以这是不必要的。
打电话
lftp -u $user,$pass $host -e "${cmd[@]}"
也打不通。这给出了这个错误:open: unrecognized option '--reverse' Usage: lftp [-e cmd] [-p port] [-u user[,pass]] <host|url>
在这里,您在 lftp 的命令行上传递该数组的内容。因为您使用了[@]
,所以数组元素作为不同的参数传递,因此命令与
lftp -u USER,PASSWORD HOSTNAME -e mirror --reverse --continue --parallel=5 SOURCE TARGET
(当然,其中$user
、$host
、$source
也得到了扩展。)$target
该-e
选项采用命令作为参数,在这里,该命令只是mirror
。以下选项--reverse
被视为另一个命令行选项,lftp 无法识别它。在这里,您想要"${cmd[*]}"
使用避免将数组元素作为不同的参数,因为 lftp 的-e
选项无论如何只能采用一个字符串。
另一方面,由于它只能使用单个字符串,因此使用数组一开始就没什么用。
运行
lftp ... -e "$("${cmd[@]}")"
出现这个错误:
有$(...)
一个命令替换,它会将内部作为 shell 中的命令运行。我不确定你从哪里得到的,但尝试例如
$ cmd=(date)
$ lftp ... -e "$("${cmd[@]}")"
(你可能应该庆幸它不在rm something
那里。)
Stéphane 的回答为您提供了一个解决方案,可以处理引用大多数特殊字符,但如果您想要更简单,你很高兴地假设你只需要担心空白字符,我认为这应该有效:
#! /usr/bin/bash
set -xe
source="."
target="Two Words/"
cmd="mirror --reverse --continue --parallel=5 '$source' '$target'"
user=foo
pass=secret
lftp -u "$user,$pass" http://localhost << EOF
set log:enabled true
$cmd
quit
EOF
我们不需要数组,因为无论如何它最终都只是一个字符串。我们确实需要在结果字符串中的文件名周围加上引号;我在里面使用了单引号$cmd
,因为这是懒惰的解决方案,我不在乎文件名是否也包含引号。您可以改为使用双引号"... \"$source\"..."
。这里的文档最终如下:
set log:enabled true
mirror --reverse --continue --parallel=5 '.' 'Two Words/'
quit
看起来这将是传递给 lftp 的一组明智的命令。
当然,在尝试看到 lftp 发出嘎嘎声之前,测试文件名不带引号会相对简单:
case $target in
*[\'\"]*) echo "ugh, can't have quotes in filename!"; exit 1;;
esac
或者更严格地说,列出允许的字符:
case $target in
*[![:alnum:]._" "/]*) echo "ugh, disallowed characters in filename!"; exit 1;;
esac