从 bash 脚本中,我使用 call 从服务器下载文件curl
。现在我想检查文件是否已完全下载。为此,我比较了下载文件和标头的大小Content-Length
。尽管两者相等,但我收到以下错误:
")syntax error: invalid arithmetic operator (error token is "
bash 脚本的一部分:
remote_size=$(curl -kI "${HEADERS[@]}" "$url" | grep -i content-length | awk '{print $2}')
local_size=$(stat --format=%s "$dest/$file")
if (( remote_size == local_size )); then
echo "File is complete" >&2
elif (( remote_size > local_size )); then
echo "Download is incomplete" >&2
elif (( remote_size < local_size )); then
echo "Remote file shrunk -- probably should delete local and start over" >&2
fi
谁能告诉我这里有什么问题以及如何解决它?
提前致谢
PS:我正在 ubuntu(WSL 上)进行测试,但该脚本最终将成为嵌入式平台上 Linux 的一部分。如果缺少任何信息,请告诉我
答案1
$ curl -sI https://google.com | sed -n '/content-length/l'
content-length: 220\r$
请参阅行尾的回车符(又名CR
, \r
, )(是表示行尾的方式)。 HTTP 标头^M
$
sed
用 CRLF 分隔,而 Unix 行分隔符是 LF。
还在 bash 和其他类似 Korn 的 shell 中的算术表达式中使用未经清理的数据是一个命令注入漏洞,这里更存在一个问题,即您使用了-k
aka--insecure
选项,允许 MitM 攻击者在响应中注入任意标头。
在 GNU 系统上,您可以使用:
local_size=$(stat -Lc %s -- "$dest/$file") || die
remote_size=$(curl -sI -- "$url" | LC_ALL=C grep -Piom1 '^content-length:\s*\K\d+') ||
die "No content-length"
case $((local_size - remote_size)) in
(0) echo same;;
(-*) echo remote bigger;;
(*) echo local bigger;;
esac
通过仅返回\d+
C 语言环境中的匹配内容,我们确保remote_size
仅包含十进制 ASCII 数字,从而消除了 ACE 漏洞。
该 GNU 命令的标准等效项grep
可能是:
LC_ALL=C sed '/^[Cc][Oo][Nn][Tt][Ee][Nn][Tt]-[Ll][Ee][Nn][Gg][Tt][Hh]:[[:space:]]*\([0-9]\{1,\}\).*/!d;s//\1/;q'
需要注意的是,如果找不到标头,它不会返回错误的退出状态就像grep
这样做,因此您需要额外检查[ -n "$remote_size" ]
.
die
上面可以是:
die() {
[ "$#" -eq 0 ] || printf>&2 '%s\n' "$@"
exit 1
}
(适应您想要使用的任何日志记录机制)。
另请注意,虽然这在实践中不太可能发生,但标题可以折叠。例如,内容长度标头可以返回为:
Content-Length:<CR>
123456<CR>
提取标头值的一种方法是使用formail
专门用于处理 RFC822 标头的工具:
remote_size=$(curl... | formail -zcx content-length -U content-length)
对于-U content-length
,如果有多个Content-Length
标头,则返回最后一个标头。更改-U
为-u
返回第一个,如grep -m1
上面所示。
您仍然需要清理结果或使用[
's(不是[[...]]
's!)-lt
/ -eq
/-gt
运算符,而不是((...))
避免 ACE 漏洞。
使用curl
7.84.0 或更高版本,您还可以curl
直接使用以下命令提供该标头的值:
remote_size=$(curl -w '%header{content-length}' -sIo /dev/null -- "$url") || die
通过测试我发现
- 如果标头出现多次,则仅返回第一个标头的值
- 如果该值不是以数字开头(可选地以 a 开头),它会抱怨
+
,但仍然需要对其后传递的任何字符进行清理。 - 它确实支持折叠标题,但拒绝第一行具有空值的内容长度。