我的问题很简单 - 有没有办法显示 curl 的个体退出状态当 curl 执行多个请求时,每个 URL 都有什么问题?
假设我需要检查网站a.com
,b.com
并c.com
查看它们的:
- HTTP 返回代码
- 如果 HTTP 返回代码是
000
,我需要显示 curl 的exit code
。
注意 - a.com
、b.com
、c.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 个curl
s(即使有 400 个echo
s,如果echo
不是内置的;甚至有 400 个(子)shell,如果需要)并不那么耗时。罪魁祸首是你运行了所有这些同步(不是吗?)
简单循环及其问题
可以循环并异步运行该代码片段:
for url in … ; do
( curl … ; echo "$?" ) &
done
但这种简单的方法存在几个问题:
- 您无法轻松限制同时运行的 s 数量
curl
,因为没有队列。这对于性能和可用资源来说可能非常糟糕。 curl
来自两个或多个命令(例如来自两个或多个s)的并发输出可能会得到交错,可能是中线。- 即使每个命令的输出单独看起来很好,
curl
或者来自另一个子 shell 的输出可能会在它对应的输出echo
之间切入。curl
echo
- 无法保证较早调用的子 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
数组到a
和rep
数组,请按如下方式进行:
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
将保存143
到ax
因为read
忽略前导空格(情况很复杂)。通过反转顺序,我们可以避免代码中的错误。像这样的行143<space><empty ax>
可以通过 正确处理read -r repx ax
。您将有望在几分钟内检查 400 个 URL。持续时间取决于您允许并行执行的作业数量 (
parallel -j …
),还取决于:- 服务器响应的速度有多快;
- 下载多少数据以及
curl
下载速度有多快; --connect-timeout
和等选项--max-time
(考虑使用它们)。