Come posso utilizzare sed per visualizzare le righe tra la prima riga vuota e l'ultima riga?

Aug 22 2020

Sto cercando di analizzare la risposta dal mio server web in uno script di shell. Questa è la risposta:

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

Ho assegnato questo a una variabile:

% foo="$(curl -i http://127.0.0.1/404)"

Invece di una variabile, ne voglio una per il codice di stato e una per il corpo della risposta. È abbastanza facile prendere il codice di stato:

% echo "$foo" | head -n 1

La parte difficile è usare sed per filtrare le intestazioni. Basato sul meraviglioso Sed grymoire di Bruce Barnett , ho pensato che avrebbe funzionato:

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

O in alternativa:

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

Tuttavia, il risultato di entrambi i comandi non è niente. Non capisco perché.

Nel caso sia importante, sto usando zsh 5.8 e GNU sed 4.8 da Homebrew e curl 7.64.1 da Mac OS.

Risposte

2 icarus Aug 22 2020 at 04:31

Il problema è che ci sono ritorni a capo (CR) nell'output di curl, quindi modelli come /^$/non corrispondono mai poiché ogni riga ha un CR e quindi non è vuota.

Ci sono un paio di cose che possono essere fatte, rimuovere i CR o tenerne conto.

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

li rimuoverà, e poi

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

funzionerà, oppure usa se non ho rimosso i CR usandotr

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

Poiché zsh può eseguire sostituzioni di stringhe, tenderei a utilizzare

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

o

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

a seconda se avevo usato trper rimuovere i CR, per salvare il processo sed.

Tuttavia, c'è un avvertimento: la sostituzione del comando rimuove tutti i caratteri di nuova riga finali (non quelli di ritorno a capo). Una risposta HTTP è <header1>CRLF...<headern>CRLFCRLF<body>. Se <body>è vuoto, $fooconterrà solo <header1>CRLF...<headern>CRLFCRo <header1>CRLF...<headern>se abbiamo rimosso i CR. In questi casi, *$'\r\n\r\n'o *$'\n\n'non corrisponderà e le intestazioni non verranno rimosse.

In ogni caso, per stampare una stringa arbitraria seguita da un carattere di nuova riga, la sintassi è:

printf '%s\n' "$foo"  # POSIX
print -r - "$foo"     # ksh/zsh
echo -E - "$foo"      # zsh

Non echo "$foo"che non funziona correttamente se $foocontiene barre rovesciate (comuni in json) o alcuni valori che iniziano con -(non dovrebbe essere il caso di json).

1 Isaac Aug 23 2020 at 14:57

Le intestazioni sono richieste da RFC7230 per essere separate da coppie CR-LF, quindi una coppia di CRLF (CRLF - CRLF) (in termini generici: una riga vuota), quindi la risposta HTTP "body". Pertanto, un normale http/1.1 conterrà alcuni ritorni a capo .

Non esiste una "linea vuota" come la descrive Unix, cioè nessuna coppia di \n\nestremità delle intestazioni. Ciò significa anche che per sed, a ^$non corrisponderà alla riga vuota (DOS) alla fine delle intestazioni, poiché quella riga contiene un \r(Carriage Return). In (GNU) sed, un'alternativa per rilevare questa riga (quasi) vuota potrebbe essere ^\r$:

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

Rimuovi i ritorni a capo

Se è valido rimuovere i caratteri di ritorno a capo, una risposta http (l'intero messaggio http/1.1 che il server emetterà) avrà righe vuote come due newline consecutive ( \n\n) per separare le intestazioni dal corpo.

In tal caso, il valore speciale di un null RS(modalità paragrafo in awk) potrebbe gestire queste intestazioni:

$ echo "$foo" | tr -d '\r' | awk -v RS="" 'NR>1' 

Oppure, per garantire che le righe vuote nel corpo dell'e-mail vengano preservate:

$ echo "$foo" | tr -d '\r' | awk 'BEGIN{ORS=RS="\n\n"}NR>1'

Consenti ritorno a capo

Tuttavia, la posta (come in RFC5322) e le risposte http (l'intero messaggio http/1.1 come in RFC7230) devono essere utilizzate CR NLcome indicatore di fine riga per le intestazioni . Un RS che potrebbe contenere un ritorno a capo opzionale richiede un'espressione regolare e l'uso di RT (terminatore di record) in quanto non è costante. Ciò significa che dovrebbe essere usato GNU awk.

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