【发布时间】:2014-09-15 16:22:13
【问题描述】:
我正在寻找一种方法来进行一次 curl 调用并从中获取变量:一个带有标头,另一个带有响应正文。
我发现了几个关于如何将标题与正文分开的问题,但人们似乎只对其中一个感兴趣。我需要标题和正文。
我不能使用外部文件来存储正文(因此不能使用 -o $file)。
我可以使用
headers=$(curl -D /dev/stdout $URL)
将标头放入一个变量,但如何将输出重定向到另一个变量?
非常感谢!
【问题讨论】:
我正在寻找一种方法来进行一次 curl 调用并从中获取变量:一个带有标头,另一个带有响应正文。
我发现了几个关于如何将标题与正文分开的问题,但人们似乎只对其中一个感兴趣。我需要标题和正文。
我不能使用外部文件来存储正文(因此不能使用 -o $file)。
我可以使用
headers=$(curl -D /dev/stdout $URL)
将标头放入一个变量,但如何将输出重定向到另一个变量?
非常感谢!
【问题讨论】:
我想分享一种无需任何外部程序即可解析 curl 响应的方法,仅限 bash。
首先,获取传递-sw "%{http_code}"的curl请求的响应。
res=$(curl -sw "%{http_code}" $url)
结果将是一个包含正文的字符串,后跟 http 代码。
然后,获取http代码:
http_code="${res:${#res}-3}"
还有身体:
if [ ${#res} -eq 3 ]; then
body=""
else
body="${res:0:${#res}-3}"
fi
请注意,如果 http_code 和 response 的长度相等(长度为 3),则 body 为空。否则,只需去掉 http 代码即可获得正文。
【讨论】:
response_code=${response: -3} echo $response_code connection_string=${response:0: -3} echo $connection_string
head=true
while IFS= read -r line; do
if $head; then
if [[ -z $line ]]; then
head=false
else
headers+=("$line")
fi
else
body+=("$line")
fi
done < <(curl -sD - "$url" | sed 's/\r$//')
printf "%s\n" "${headers[@]}"
echo ===
printf "%s\n" "${body[@]}"
将数组的元素连接到单个标量变量中:
the_body=$( IFS=$'\n'; echo "$body[*]" )
在bash 4.3 中,您可以使用命名引用来简化从“header”模式到“body”模式的切换:
declare -n section=headers
while IFS= read -r line; do
if [[ $line = $'\r' ]]; then
declare -n section=body
fi
section+=("$line")
done < <(curl -sD - "$url")
出于某种原因,格伦杰克曼的回答没有抓住响应的正文部分。我不得不将 curl 请求分成另一个命令扩展,然后用双引号将其括起来。然后我没有使用数组,而只是将值连接到变量。这对我有用:
output=$(curl -si -d "" --request POST https://$url)
head=true
while read -r line; do
if $head; then
if [[ $line = $'\r' ]]; then
head=false
else
header="$header"$'\n'"$line"
fi
else
body="$body"$'\n'"$line"
fi
done < <(echo "$output")
谢谢你,格伦!
【讨论】:
cat 不能从sed 停止的地方继续阅读:{ h=$(sed '/^$/q'); b=$(cat); } < <(curl -si $url)
\r\n 行尾的标题,因此/^$/ 不会表示标题的结尾,您必须使用/^\r$/ 或像我在回答中那样去掉回车符.但即使纠正了这一点,我也不知道为什么 sed 在它应该退出后会吃掉所有的输入。
sed 一次处理其输入一行,但这并不意味着它一次读取其标准输入中的一行。它可能读取N 行,而其他N-1 被缓冲,直到sed 完成第一行。如果sed 在处理缓冲区的其余部分之前退出,则这些行不会“恢复”到标准输入以供下一个命令(在本例中为cat)使用。
<<< "$output" 而不是进程替换 < <(echo "$output")
受0x10203040's answer 的启发,以下脚本将响应标头放入一个变量中,将响应正文放入另一个变量中。我在这方面是个菜鸟,所以我可能做了一些不明智/低效的事情;随时提出改进建议。
# Perform the request:
# - Optionally suppress progress output from the terminal (-s switch).
# - Include the response headers in the output (-i switch).
# - Append the response header/body sizes to the output (-w argument).
URL="https://example.com/"
response=$(curl -si -w "\n%{size_header},%{size_download}" "${URL}")
# Extract the response header size.
headerSize=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}")
# Extract the response body size.
bodySize=$(sed -n '$ s/^.*,\([0-9]*\)$/\1/ p' <<< "${response}")
# Extract the response headers.
headers="${response:0:${headerSize}}"
# Extract the response body.
body="${response:${headerSize}:${bodySize}}"
curl – 传输一个 URL--include使用--include (-i) 选项将响应标头包含在curl 的标准输出中,位于响应正文之前。
--write-out使用--write-out (-w) 选项将一些有用的东西附加到curl 的标准输出的末尾:
\n(一个新行,所以 sed 可以分别处理标题/正文大小)%{size_header},%{size_download}(分别是响应头大小和响应体大小,用逗号或其他东西分隔)... -w "\n%{size_header},%{size_download}" ...
--silent(可选)根据请求的类型,curl 可能会将进度更新输出到终端中的 stderr。它不会影响标准输出,但您可以使用 --silent (-s) 选项抑制它。 (有关更多信息,请参阅here。)
现在使用command substitution ($(...)) 在子shell 中执行curl 并将其整个标准输出(响应头和正文)放入单个$response 变量中;我们将在事后分别从中提取标题和正文。
response=$(curl -si -w "\n%{size_header},%{size_download}" "${URL}")
$response 应该包含如下内容:
HTTP/2 200
age: 384681
cache-control: max-age=604800
content-type: text/html; charset=UTF-8
date: Tue, 03 Nov 2020 06:54:45 GMT
etag: "3147526947+ident"
expires: Tue, 10 Nov 2020 06:54:45 GMT
last-modified: Thu, 17 Oct 2019 07:18:26 GMT
server: ECS (ord/4CB8)
vary: Accept-Encoding
x-cache: HIT
content-length: 1256
<!doctype html>
<html>
...
</html>
331,1256
由于-w 选项,请注意最后一行的标题、正文大小。
sed – 流编辑器... sed -n '$ s/^\([0-9]*\),.*$/\1/ p' ...
... sed -n '$ s/^.*,\([0-9]*\)$/\1/ p' ...
--silent使用--silent (--quiet/-n) 选项防止sed 输出通过它的每一行。
$ address使用$ 地址仅处理最后一行(标题/正文大小)。
s command使用s 命令执行替换。基本上,使用正则表达式匹配包含标题/正文大小的行,然后仅用标题大小或正文大小替换整行,然后输出替换的行,即仅输出标题大小或仅输出正文大小:
s:替换命令。/:开始正则表达式搜索模式。^:匹配行首。\(:开始一个包含标题大小的组。[0-9]*:匹配 0 个或多个数字(即标头大小)。\):完成包含标头大小的组。,.*:在组之后,匹配一个逗号,后跟 0 个或多个任意字符(以匹配该行的其余部分)。$:匹配行尾。/:完成正则表达式搜索模式;开始替换模式。\1:将匹配的文本(即整行)替换为组(即只是标题大小)。/:结束替换模式。s/^\([0-9]*\),.*$/\1/
Above 是标头大小,即 before 逗号;下面的正文大小类似,在 逗号之后。
s/^.*,\([0-9]*\)$/\1/
p command尽管有-n 选项,但使用p 命令输出已处理的行。
使用“here-string”(<<<) 将curl 的$response 传递给sed 的标准输入。由于我们抑制了sed 的输出(-n),只处理最后一行($),用标题大小或正文大小替换(s/...)整行,然后输出它(@ 987654393@), sed 应该分别输出那些。再次使用command substitution 将它们放入$headerSize 和$bodySize 变量中。
headerSize=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}")
bodySize=$(sed -n '$ s/^.*,\([0-9]*\)$/\1/ p' <<< "${response}")
最后,现在知道了标头和正文的大小,使用parameter substring expansion (${variable:offset:length}) 将响应标头和响应正文拉到单独的$headers 和$body 变量中。
headers="${response:0:${headerSize}}"
body="${response:${headerSize}:${bodySize}}"
使用以下方法在 macOS High Sierra 10.13.6 中为我工作:
如果你在数字符,以下事情让我很头疼:
curl 实际输出的内容不完全相同。因此使用十六进制编辑器而不是文本编辑器进行故障排除可能会更好。
【讨论】: