Bash: удаление заголовков из ответа HTTP

если у меня есть текст, содержащий HTTP-заголовки и тело, например:

HTTP/1.1 200 OK
Cache-Control: public, max-age=38
Content-Type: text/html; charset=utf-8
Expires: Fri, 22 Nov 2013 06:15:01 GMT
Last-Modified: Fri, 22 Nov 2013 06:14:01 GMT
Vary: *
X-Frame-Options: SAMEORIGIN
Date: Fri, 22 Nov 2013 06:14:22 GMT

<!DOCTYPE html>
<html>
<head>
    <title>My website</title>
</head>
<body>

Hello world!

</body>
</html>

и этот текст передается из команды, как я могу удалить заголовки, чтобы оставить только тело?

(в заголовках, rn используется в качестве разрыва строки.  rnrn отмечает конец заголовков и начало тела.)

вот что я пробовал (... указывает любую команду, такую как cat или curl который выведет некоторые HTTP-заголовки и тело stdout):

sed

моей первой идеей было сделать замену с sed, чтобы удалить все до первого вхождения rnrn:

... | sed 's|^.*?rnrn||'

но это не работает, главным образом потому, что sed работает только на отдельных линиях, поэтому он не может работать на r или n.  (Кроме того, он не поддерживает ? нежадный оператор.)

grep

я также думал об использовании grep с положительным lookbehind для rnrn:

... | grep -oP '(?<=rnrn).*'

но это тоже не работает (в основном потому, что grep работает только на отдельных линиях).

pcregrep имеет многострочный режим (-M), но pcregrep часто недоступен (он не установлен по умолчанию в Ubuntu 12.04, Mac OS X 10.7 и т. д.), И я хотел бы получить решение, которое не требует каких-либо нестандартных инструментов.

на Perl

я тогда подумал о замене perl, С помощью /s модификатор так что . матчи разрывы строк:

... | perl -pe 's/^.*?rnrn//s'

я думаю, что это ближе к рабочим решением.  Тем не менее, я думаю, что входной разделитель записей Perl ($/) составляет n по умолчанию, и должен быть изменен на rn, так что . может соответствовать rn.  The -0 опция может использоваться для установки $/ для одного символа, но не для нескольких символов.  Я пробовал это, но я не думаю, что это правильно:

... | perl -pe '$/ = "rn"; s/^.*?rnrn//s'

кроме того, я думаю ^ совпадающие "старт строки", но должен соответствовать "началу файла".

смещение и подстроки

у меня была идея получить смещение rnrn использование:

BodyOffset=$(expr index "$MyHttpText" "rnrn")

а затем извлечение тела в качестве подстроки с помощью:

HttpBody=${MyHttpText:BodyOffset}

к сожалению, Mac OS X версии expr не поддерживает index.  Кроме того, если возможно, я хотел бы получить решение, которое не требует создания переменных.

замена параметра

один другая идея, которую я имел, заключалась в использовании подстановки параметров, где # означает "удалить из $MyHttpText самая короткая часть *rnrn это соответствует передней части $MyHttpText":

HttpBody=${MyHttpText#*rnrn}

но я не уверен, как использовать это в конвейерной последовательности команд, и снова я предпочел бы решение, которое не требует переменных.

5 ответов


sed можно сделать так:

sed '1,/^$/d' data.txt

эта команда удаляет все, начиная с строки 1 и заканчивая первым появлением пустой строки (^$). Это работает, если у вас есть \n как символ новой строки. Если у вас есть \r\n в качестве символа новой строки вы можете использовать dos2unix и unix2dos чтобы конвертировать их туда и обратно, или вы можете добавить \r символ sed регулярное выражение:

sed '1,/^\r$/d' data.txt

однако последняя строка будет только работать, если у вас есть \r\n как символ новой строки, чтобы заставить его работать на обоих типах строк, вы можете использовать:

sed '1,/^\r\{0,1\}$/d' data.txt

здесь мы ищем пустую строку с 0 или 1 \r символы.


ваша однострочная команда Perl не удаляет (не может) заголовки, потому что она читает только одну строку ввода в то время. Вам нужно отключить разделитель входных записей, чтобы прочитать весь вход как одну строку.

perl -0777 ...

также интересно делать в bash (только внутренние команды):

#!/bin/bash

while read LINE                     #<-- while you can read line from input
do                                  #<-- do the following actions
    if    [ $FLAG ]                 #<-- if:   this flag is set
    then  echo "$LINE"              #<--       echo the input to output
    elif  [ ${LINE:0:1} = $'\r'  ]  #<-- else: if line starts with \r
    then  FLAG=true                 #<--       then raise the flag
    fi
done

... | perl -ne 'print if $after_header; $after_header = 1 if /^\r$/'

curl не возвращает заголовки по умолчанию из bash, если вы не укажете опцию-I (capital i) или-D (заголовки дампа). Поэтому сделайте cure ни один из них не указан в вашем вызове curl!