sedを使用して最初の空白行と最後の行の間の行を表示するにはどうすればよいですか?

Aug 22 2020

Webサーバーからの応答をシェルスクリプトで解析しようとしています。これは応答です:

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)"

1つの変数の代わりに、ステータスコード用と応答本文用に1つずつ必要です。ステータスコードを取得するのは簡単です。

% echo "$foo" | head -n 1

難しいのは、sedを使用してヘッダーを除外することです。基づいて、ブルース・バーネットの素晴らしいのSED grymoire、私はこれが働くだろうと思いました:

% echo "$foo" | sed '1,/^$/ d'

または代わりに:

% echo "$foo" | sed -n '/^$/,$ p'

ただし、両方のコマンドの結果はまったくありません。理由がわかりません。

重要な場合は、Homebrewのzsh5.8とGNUSed 4.8、MacOSのcurl7.64.1を使用しています。

回答

2 icarus Aug 22 2020 at 04:31

問題は、curlからの出力にキャリッジリターン(CR)があるため/^$/、すべての行にCRがあり、空ではないため、「決して一致しない」などのパターンがあることです。

CRを削除するか、CRを説明するなど、実行できることがいくつかあります。

foo="$(curl -i http://127.0.0.1/404 | tr -d '\r')"

それらを削除し、その後

printf '%s\n' "$foo" | sed '1,/^$/d'

動作するか、またはを使用してCRを削除しなかった場合に使用します tr

printf '%s\n' "$foo" | sed $'1,/^\r$/d'

zshは文字列の置換を行うことができるので、私は使用する傾向があります

printf '%s\n' "${foo#*$'\r\n\r\n'}"

または

printf '%s\n' "${foo#*$'\n\n'}"

trsedプロセスを保存するために、CRを削除するために使用したかどうかによって異なります。

ただし、注意点があります。コマンド置換により末尾の改行文字がすべて削除されます(キャリッジリターン文字ではありません)。HTTP応答は<header1>CRLF...<headern>CRLFCRLF<body>です。<body>が空の場合、$foo含まれるのはCRのみ、<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の場合はそう ではない)が含まれている場合、これは正しく機能しません。 echo "$foo" $foo-

1 Isaac Aug 23 2020 at 14:57

RFC7230では、ヘッダーをCR-LFペア、次にCRLFのペア(CRLF-CRLF)(大まかに言うと、空の行)、次にHTTP応答「body」で区切る必要があります。したがって、通常のhttp /1.1にはキャリッジリターンが含まれます。

Unixが説明しているように、「空の行」はありません\n\n。つまり、ヘッダーの終わりのペアはありません。これは、sedの場合^$、ヘッダーの最後にある空の(DOS)行に\r(キャリッジリターン)が含まれているため、その行と一致しないことも意味します。(GNU)sedでは、この(ほぼ)空の行を検出するための代替手段は次のようになります^\r$

$ printf '%s\n' "$foo" | sed '1,/^\r$/ d'

キャリッジリターンを削除する

キャリッジリターン文字を削除することが有効な場合、http応答(サーバーが発行するhttp / 1.1メッセージ全体\n\n)には、ヘッダーを本文から分離するための2つの連続する改行()として空の行が含まれます。

もしそうなら、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応答(RFC7230のようにhttp / 1.1メッセージ全体)をヘッダーの行末マーカーCR NLとして使用する必要があります。オプションのキャリッジリターンを含めることができるRSには、正規表現と、定数ではないためRT(レコードターミネーター)の使用が必要です。つまり、GNUawkを使用する必要があります。

$ echo "foo" | awk 'BEGIN{RS="(\r?\n){2}"}NR>1{printf "%s%s",$0,RT}'
{
    "message": {
        "status": "404",
        "message": "Not Found"
    }
}