如何显示多个请求中的 curl 的单独退出状态?

如何显示多个请求中的 curl 的单独退出状态?

我的问题很简单 - 有没有办法显示 curl 的个体退出状态当 curl 执行多个请求时,每个 URL 都有什么问题?

假设我需要检查网站a.comb.comc.com查看它们的:

  • HTTP 返回代码
  • 如果 HTTP 返回代码是000,我需要显示 curl 的exit code

注意 - a.comb.comc.com在此代码/问题中用作示例。在实际脚本中,我确实有一个有效 URL 列表 - 其中超过 400 个具有不重叠模式 - 它们返回各种 HTTP 代码 - 200/4xx/5xx 以及 000。

000 是 curl 无法建立连接的情况,但会提供退出代码来了解是什么阻止它建立连接。在我的案例中,还有许多退出代码 - 6、7、35、60。

我尝试运行以下代码

unset a
unset rep
a=($(curl -s --location -o /dev/null -w "%{response_code}\n" {https://a.com,https://b.com,https://a.com}))
rep+=("$?")
printf '%s\n' "${a[@]}"
echo
printf '%s\n' "${rep[@]}"

虽然上述代码会为每个单独的请求返回 HTTP 返回代码,但仅显示最后一个请求的退出代码。

000
000
000

60

当我向 curl 提供多个 URL 时,我确实需要记录单个退出代码的功能。有没有解决这个问题的办法?

一些附加信息:目前我将所有 URL 放在一个数组中,并循环检查每个 URL。但是,检查 400 个 URL 需要 1-2 小时,我需要以某种方式加快这个过程。我确实尝试过将 -Z 与 curl 一起使用。虽然它确实将这个过程加快了大约 40-50%,但它没有帮助,因为除了仅显示上述最后一个退出状态外,在这种情况下,退出状态始终显示为 0,这是不正确的。

PS:如果其他命令行工具能够解决上述问题,我愿意使用它 - 并行检查 10/100 个 URL 并记录它们的 HTTP 代码,如果无法建立连接 - 则记录其他信息,就像 curl 的退出代码一样。

谢谢。

答案1

分析

退出代码名为“出口代码”,因为它是在命令退出时返回的。如果你只运行一个,curl那么它就会只退出一次。

curl,当给定一个或多个 URL 时,可能会提供一种方法来检索与单独处理当前 URL 的退出代码等效的代码curl;它将类似于%{response_code}您使用的东西。不幸的是,似乎没有这样的功能(目前还没有;也许添加它)。要获取 N 个退出代码,您需要 N 个curl进程。您需要运行类似下面的命令 N 次:

curl … ; echo "$?"

我知道你的 N 大约是 400,你尝试了一个循环,花了几个小时。好吧,生成 400 个curls(即使有 400 个echos,如果echo不是内置的;甚至有 400 个(子)shell,如果需要)并不那么耗时。罪魁祸首是你运行了所有这些同步(不是吗?)


简单循环及其问题

可以循环并异步运行该代码片段:

for url in … ; do
   ( curl … ; echo "$?" ) &
done

但这种简单的方法存在几个问题:

  1. 您无法轻松限制同时运行的 s 数量curl,因为没有队列。这对于性能和可用资源来说可能非常糟糕。
  2. curl来自两个或多个命令(例如来自两个或多个s)的并发输出可能会得到交错,可能是中线。
  3. 即使每个命令的输出单独看起来很好,curl或者来自另一个子 shell 的输出可能会在它对应的输出echo之间切入。curlecho
  4. 无法保证较早调用的子 shell 会在较晚调用的子 shell 之前开始(或结束)打印。

parallel

正确的工具是parallel。该工具的基本变体(moreutils至少来自在 Debian 中) 可解 (1)。在某些情况下,它可能可解 (2)。无论如何,这无关紧要,因为此变体无法解 (3) 或 (4)。

GNUparallel解决了所有这些问题。

  • 它通过设计解决了(1)。

  • 它用以下--group选项解决(2)和(3):

    --group
    分组输出。每个作业的输出被分组在一起,并且仅在命令完成时打印。首先是 Stdout(标准输出),然后是 stderr(标准错误)。[…]

    来源

    这是默认设置,因此通常您不必明确使用它。

  • 它利用以下--keep-order选项求解(4):

    --keep-order
    -k
    保持输出顺序与输入顺序一致。通常,作业完成后会立即打印输出。[…]-k仅影响打印输出的顺序 - 而不影响运行作业的顺序。

    来源

在 Debian GNU 中,parallel有一个名为 的包parallel。本答案的其余部分使用 GNU parallel


基本解决方案

<urls parallel -j 40 -k 'curl -s --location -o /dev/null -w "%{response_code}\n" {}; echo "$?"'

其中urls是一个带有 URL 的文件,-j 40表示我们允许最多 40 个并行作业(根据您的需要和能力进行调整)。在这种情况下,嵌入{}shell 代码是安全的。这是此答案中明确提到的例外情况:切勿嵌入{}shell 代码!

输出将会像这样

404
0
200
0
000
7

注意,单引号字符串是 shell 代码。在其中,您可以实现一些逻辑,因此0永远不会打印退出代码。如果我是你,我无论如何都会在同一行上打印它,在领导位置:

<urls parallel -j 40 -k '
   out="$(
      curl -s --location -o /dev/null -w "%{response_code}" {}
   )"
   printf "%s %s\n" "$?" "$out"'

现在,即使curl在打印之前手动杀死某些内容,您仍会在第一列中得到一些内容。这对于解析很有用(我们将返回到它)。示例:

0 404
0 200
7 000
143 

143终止方式(curl进程终止时的默认退出代码)。


使用数组

如果您的 URL 位于名为 的数组中urls,请避免使用此语法:

parallel … ::: "${urls[@]}"    # don't

parallel是一个外部命令。如果数组足够大,那么你将命中argument list too long. 改用这个:

printf '%s\n' "${urls[@]}" | parallel …

它会起作用是因为 Bashprintf是一个内置函数,因此之前的所有操作|都由 Bash 内部处理。

要从urls数组到arep数组,请按如下方式进行:

unset a
unset rep
while read -r repx ax; do
   rep+=("$repx")
   a+=("$ax")
done < <(printf '%s\n' "${urls[@]}" \
         | parallel -j 40 -k '
              out="$(
                 curl -s --location -o /dev/null -w "%{response_code}" {}
              )"
         printf "%s %s\n" "$?" "$out"')
printf '%s\n' "${a[@]}"
echo
printf '%s\n' "${rep[@]}"

笔记

  • 如果我们在第二列生成退出代码(这更容易,你不需要像这样的辅助变量out)并相应地调整我们的read,那么它就是read -r ax repx,然后一行<empty ax><space>143将保存143ax因为read忽略前导空格(情况很复杂)。通过反转顺序,我们可以避免代码中的错误。像这样的行143<space><empty ax>可以通过 正确处理read -r repx ax

  • 您将有望在几分钟内检查 400 个 URL。持续时间取决于您允许并行执行的作业数量 ( parallel -j …),还取决于:

    • 服务器响应的速度有多快;
    • 下载多少数据以及curl下载速度有多快;
    • --connect-timeout和等选项--max-time(考虑使用它们)。

相关内容