【问题标题】:Get both the headers and the body of a curl response in two separated variables?在两个单独的变量中同时获取 curl 响应的标题和正文?
【发布时间】:2014-09-15 16:22:13
【问题描述】:

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

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

我不能使用外部文件来存储正文(因此不能使用 -o $file)。

我可以使用

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

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

非常感谢!

【问题讨论】:

    标签: bash curl


    【解决方案1】:

    我想分享一种无需任何外部程序即可解析 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 代码即可获得正文。

    【讨论】:

    • 这是一种只检查 status_code 的更简单的方法。然而,最初的问题是关于解析头字段,我喜欢这个解决方案并在我的库中使用它。 (bitbucket.org/kisp/bash_lib/src/…)感谢您的提示。
    • @kisp 很高兴知道您正在使用它。请注意,我没有对它进行太多测试,尽管它应该可以很好地工作,因为它非常简单。
    • 我知道这并没有严格回答这个问题,但它仍然非常有用(并且足够接近以被投票)。
    • 更简单的方法:response_code=${response: -3} echo $response_code connection_string=${response:0: -3} echo $connection_string
    【解决方案2】:
    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); } &lt; &lt;(curl -si $url)
    • 这是个好问题。请记住,curl 返回带有\r\n 行尾的标题,因此/^$/ 不会表示标题的结尾,您必须使用/^\r$/ 或像我在回答中那样去掉回车符.但即使纠正了这一点,我也不知道为什么 sed 在它应该退出后会吃掉所有的输入。
    • 虽然sed 一次处理其输入一行,但这并不意味着它一次读取其标准输入中的一行。它可能读取N 行,而其他N-1 被缓冲,直到sed 完成第一行。如果sed 在处理缓冲区的其余部分之前退出,则这些行不会“恢复”到标准输入以供下一个命令(在本例中为cat)使用。
    • @ooga,看来您必须用 shell while-read 循环替换该 sed 命令。
    • @Daniel,很好地解决了这个问题。问题:您是否注意到我将 curl 的输出传递给 sed 以删除回车?这可以解释你的观察。建议:使用这里字符串 &lt;&lt;&lt; "$output" 而不是进程替换 &lt; &lt;(echo "$output")
    【解决方案3】:

    受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 命令执行替换。基本上,使用正则表达式匹配包含标题/正文大小的行,然后仅用标题大小或正文大小替换整行,然后输出替换的行,即仅输出标题大小或仅输出正文大小:

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

    Above 是标头大小,即 before 逗号;下面的正文大小类似,在 逗号之后。

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

    p command

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


    使用“here-string”(&lt;&lt;&lt;) 将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 中为我工作:

    • GNU bash 版本 3.2.57(1)-release (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,因此每个 EOL 有 2 个字节。
    • 某些代码编辑器(例如 Visual Studio Code)会汇总规范化行尾,因此编辑器中的最终结果可能与 curl 实际输出的内容不完全相同。
    • 某些字符编码(如 UTF-8 等)使用多个字节来表示某些单个字符,因此 字符数 可能少于 中的标头/正文的大小字节;对于可能不会出现在文本/代码编辑器中的控制字符/其他非打印字符等也是如此。

    因此使用十六进制编辑器而不是文本编辑器进行故障排除可能会更好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-07
      • 1970-01-01
      相关资源
      最近更新 更多