Come posso utilizzare sed per visualizzare le righe tra la prima riga vuota e l'ultima riga?
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
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 tr
per 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, $foo
conterrà solo <header1>CRLF...<headern>CRLFCR
o <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 che non funziona correttamente se echo "$foo"
$foo
contiene barre rovesciate (comuni in json) o alcuni valori che iniziano con -
(non dovrebbe essere il caso di json).
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\n
estremità 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 NL
come 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"
}
}