我正在尝试在 shell 脚本中解析来自网络服务器的响应。这是回应:
HTTP/1.0 404 NOT FOUND
Content-Length: 223
Content-Type: application/json
Last-Modified: Fri, 21 Aug 2020 15:24:23 GMT
Cache-Control: public, max-age=43200
Expires: Sat, 22 Aug 2020 08:04:19 GMT
ETag: "1598023463.02863-223-4034336499"
Date: Fri, 21 Aug 2020 20:04:19 GMT
Server: Werkzeug/1.0.1 Python/3.8.5
{
"message": {
"status": "404",
"message": "Not Found"
}
}
我将其分配给一个变量:
% foo="$(curl -i http://127.0.0.1/404)"
我想要一个变量用于状态代码,一个用于响应正文,而不是一个变量。获取状态代码很容易:
% echo "$foo" | head -n 1
困难的部分是使用 sed 过滤掉标头。基于布鲁斯·巴尼特 (Bruce Barnett) 精彩的塞德·格莱莫尔 (Sed grymoire),我认为这会起作用:
% echo "$foo" | sed '1,/^$/ d'
或者:
% echo "$foo" | sed -n '/^$/,$ p'
然而,这两个命令的结果都是什么都没有。我不明白为什么。
如果重要的话,我使用的是 Homebrew 中的 zsh 5.8 和 GNU sed 4.8 以及 Mac OS 中的curl 7.64.1。
答案1
RFC7230 要求标头由 CR-LF 对分隔,然后是一对 CRLF (CRLF - CRLF)(宽松的术语:空行),然后是 HTTP 响应“正文”。因此,正常的 http/1.1 将包含一些回车。
不存在 Unix 所描述的“空行”,即\n\n
标头没有两端。这也意味着对于 sed,a^$
将不匹配标头末尾的空 (DOS) 行,因为该行包含 a \r
(回车符)。在 (GNU) sed 中,检测此(几乎)空行的替代方法可以是^\r$
:
$ printf '%s\n' "$foo" | sed '1,/^\r$/ d'
删除回车符
如果删除回车符是有效的,则 http 响应(服务器将发出的整个 http/1.1 消息)将包含空行,作为两个连续的换行符 ( \n\n
),以将标头与正文分开。
如果是这样,null 的特殊值RS
(awk 中的段落模式)可以处理此标头:
$ echo "$foo" | tr -d '\r' | awk -v RS="" 'NR>1'
或者,为了确保保留电子邮件正文中的空行:
$ echo "$foo" | tr -d '\r' | awk 'BEGIN{ORS=RS="\n\n"}NR>1'
允许回车
然而,邮件(如 RFC5322 中)和 http 响应(整个 http/1.1 消息如 RFC7230 中)需要CR NL
用作标题的行尾标记。 RS 可以包含选修的回车需要正则表达式并使用 RT(记录终止符),因为它不是常量。这意味着应该使用 GNU awk。
$ echo "foo" | awk 'BEGIN{RS="(\r?\n){2}"}NR>1{printf "%s%s",$0,RT}'
{
"message": {
"status": "404",
"message": "Not Found"
}
}
答案2
问题在于,curl 的输出中有回车符 (CR),因此模式/^$/
永远不会匹配,因为每行都有一个 CR,因此不为空。
有几件事可以做,要么删除 CR,要么解释它们。
foo="$(curl -i http://127.0.0.1/404 | tr -d '\r')"
将删除它们,然后
printf '%s\n' "$foo" | sed '1,/^$/d'
会起作用,或者如果我没有使用删除 CRs 则使用tr
printf '%s\n' "$foo" | sed $'1,/^\r$/d'
由于 zsh 可以进行字符串替换,所以我倾向于使用
printf '%s\n' "${foo#*$'\r\n\r\n'}"
或者
printf '%s\n' "${foo#*$'\n\n'}"
取决于我是否曾经tr
剥离 CR,以保存 sed 进程。
但有一个警告:命令替换条全部尾随换行符(不是回车符)。 HTTP 响应是<header1>CRLF...<headern>CRLFCRLF<body>
.如果<body>
为空,$foo
则仅包含<header1>CRLF...<headern>CRLFCR
或<header1>CRLF...<headern>
如果我们已删除 CR。在这些情况下,*$'\r\n\r\n'
或*$'\n\n'
不会匹配,并且标头不会被删除。
无论如何,要打印后跟换行符的任意字符串,语法为:
printf '%s\n' "$foo" # POSIX
print -r - "$foo" # ksh/zsh
echo -E - "$foo" # zsh
如果包含反斜杠(在 json 中常见)或某些以 开头的值(json 中不应该出现这种情况),则Not无法正常工作。echo "$foo"
$foo
-