如何使用 sed 显示第一个空行和最后一行之间的行?

如何使用 sed 显示第一个空行和最后一行之间的行?

我正在尝试在 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 中不应该出现这种情况),则Notecho "$foo"无法正常工作。$foo-

相关内容