在两个分开的变量中获取卷曲响应的标题和正文?

时间:2014-09-15 16:22:14

标签: bash curl

我正在寻找一种方法来进行一次卷曲调用并从中获取变量:一个带有标题,另一个带有响应体。

我发现了几个问题,询问如何将标题与正文分开,但人们似乎只对其中一个感兴趣。我需要标题和正文。

我无法使用外部文件来存储正文(因此使用-o $文件不是一个选项)。

我可以用

headers=$(curl -D /dev/stdout $URL)

将标题放入一个变量,但如何将输出重定向到另一个变量?

非常感谢!

3 个答案:

答案 0 :(得分:8)

我想分享一种解析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和响应的长度相等(长度为3),则body为空。否则,只需删除http代码即可获得正文。

答案 1 :(得分:7)

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中,您可以使用命名引用来简化从&#34;标题&#34;模式到&#34;身体&#34;模式:

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")

谢谢你,格伦!

答案 2 :(得分:0)

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 –传输网址

--include

使用--include (-i)选项将响应标头包含在curl的标准输出中,位于响应正文之前。

--write-out

使用--write-out (-w)选项将一些有用的内容附加到curl的stdout的末尾:

  • \n(新行,因此sed可以单独处理)
  • %{size_header},%{size_download}(响应标头大小和响应正文大小,分别用逗号或其他分隔符)
... -w "\n%{size_header},%{size_download}" ...

--silent(可选)

根据请求的类型,curl可能会将进度更新输出到终端中的stderr。它不会影响stdout,但是您可以使用--silent (-s)选项将其抑制。 (有关更多信息,请参见here。)


使用command substitution$(...))在子shell中执行curl,现在将其stdout放入单个$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

请注意标题,仅在最后一行显示正文大小。

sed –流编辑器

... sed -n '$ s/^\([0-9]*\),.*$/\1/ p' ...
... sed -n '$ s/^.*,\([0-9]*\)$/\1/ p' ...

--silent

使用--silent--quiet)(-n)选项可防止sed输出通过它的每一行。

$ address

使用$地址仅处理最后一行。

s command

使用s命令进行替换:

  1. s:替换命令。
  2. /:开始使用正则表达式搜索模式。
  3. ^:匹配行的开头。
  4. \(:开始一个包含报头大小的组。
  5. [0-9]*:匹配0个或多个数字(即标头大小)。
  6. \):完成包含标头大小的组。
  7. ,.*:在该组之后,匹配一个逗号,后跟0个或多个任意字符(以匹配该行的其余部分)。
  8. $:匹配行尾。
  9. /:完成正则表达式搜索模式;开始替换模式。
  10. \1:将匹配的文本(即整行)替换为组(即标题大小)。
  11. /:结束替换模式。
s/^\([0-9]*\),.*$/\1/

上面是标题的大小,在逗号之前;下面的体形类似, 后是逗号。

s/^.*,\([0-9]*\)$/\1/

p command

尽管有p,也使用-n命令输出已处理的行。


使用“ here-string”(<<<)将curl的{​​{1}}传递到$response的标准输入。由于我们取消了sed的输出(sed),因此仅处理了最后一行(-n),然后用标题大小或身体大小,然后输出($),s/...应该分别输出那些大小。然后再次使用command substitution将它们放入psed变量中。

$headerSize

最后,现在知道标头和正文的大小,使用parameter substring expansion$bodySize)将响应标头和响应正文拉到单独的headerSize=$(sed -n '$ s/^\([0-9]*\),.*$/\1/ p' <<< "${response}") bodySize=$(sed -n '$ s/^.*,\([0-9]*\)$/\1/ p' <<< "${response}") ${variable:offset:length}变量中

$headers

问题排查

在macOS High Sierra 10.13.6中使用以下命令为我工作:

  • GNU bash版本3.2.57(1)-发行版(x86_64-apple-darwin17)
  • curl 7.54.0(x86_64-apple-darwin17.0)libcurl / 7.54.0 LibreSSL / 2.0.20 zlib / 1.2.11 nghttp2 / 1.24.0

如果您要数字符,以下几件事使我震惊:

  • HTTP标头具有EOL的CR + LF,因此每个为2个字节。
  • 某些代码编辑器(例如Visual Studio Code)将对行尾进行归一化,因此,编辑器中的最终结果可能与$body的实际输出不完全相同。
  • 某些字符编码(例如UTF-8等)使用多个字节来表示某些单个字符,因此,字符数可能少于以 bytes 为单位的标题/正文的大小;类似于控制字符/其他非打印字符等,它们可能不会出现在文本/代码编辑器中。 使用十六进制编辑器进行故障排除可能会更好。