Reindirizza l'output parziale del processo in background e memorizzalo in variabile

Aug 23 2020

Sfondo:

Sto lavorando a un progetto in cui un server viene avviato su una porta disponibile casuale. Il server viene avviato da un comando:

node server.js

Una volta che il server è pronto e inizia a servire su una porta disponibile, restituisce (insieme a una serie di altri log) l'URL completo del server (incluso il numero di porta) su stdout nella seguente forma:

Server started! Listening on URL: http://localhost:12927

Questo processo del server continua a funzionare finché non lo uccidi manualmente (e continua a stampare altri registri).

Problema reale:

Sto scrivendo uno script per avviare il server e quindi ascoltare il suo output. Non appena ricevo il messaggio qualcosa del genere "Server started! Listening on URL: [SERVER_URL]", voglio memorizzarlo SERVER_URLin una variabile ed elaborarlo più tardi nello script (ad esempio aprire un browser per i test di automazione).

Quello che ho provato:

Avendo solo una conoscenza di base di Bash, ho provato le seguenti opzioni (ovviamente, dopo aver effettuato ricerche su SE):

  1. Ho studiato un po 'sull'uso grepcon regex. Funziona abbastanza bene se lo faccio manualmente (cioè non memorizzandolo in una variabile e semplicemente collegandolo a un echocomando.

    SERVER_URL=$(node server.js | grep -m 1 -ohP 'Server started! Listening on URL: \K([^\s$]*)')
    

Tuttavia, si blocca su questa linea, probabilmente sta aspettando che il server del nodo venga ucciso. La mia esigenza è chiara: voglio popolare SERVER_URL dall'output del server senza aspettarlo oltre il punto in cui sputa quell'URL su stdout.

  1. Ho anche giocherellato un po 'con il reindirizzamento e la sostituzione del processo . Ho appreso che il pipe attende effettivamente il completamento del processo di origine. Ho provato a fare quanto segue con il reindirizzamento, ma non funziona:

    SERVER_URL=$(node server.js >&3 | grep -m 1 -ohP 'Server started! Listening on URL: \K([^\s$]*)')
    

Sta generando una strana eccezione "Evento" errore "non gestito" (Errore: scrivere EPIPE).

Qualsiasi aiuto è apprezzato. Grazie!

Risposte

3 StéphaneChazelas Aug 23 2020 at 20:55

Sì, in:

var=$(cmd | grep -m1 pattern)

cmdLo stdout di è un pipe per grepe lo grepstdout di è un pipe per la shell.

Dopo grepaver trovato la prima corrispondenza, la visualizza sullo stdout ed esce.

La shell leggerà grepl'output, vedrà la fine del file quando grepesce, ma nel caso di bash, la shell attenderà anche cmddi uscire.

Ora, poiché grepè uscito, lo cmdstdout di ora è un tubo rotto. Quindi la prossima volta che cmdscrive su stdout, riceverà un segnale SIGPIPE e morirà.

Qui, la domanda è: cosa vuoi fare con il resto cmddell'output di una volta che hai letto la prima riga che corrisponde al pattern?

Se non ti interessa puoi eseguire un catcomando in background che lo legge e lo scarta:

{
   url=$(grep -m1 -oP 'Server started! Listening on URL: \K\S+')
   cat <&0 > /dev/null & # <&0 to work around the fact that bash redirects
                         # background commands stdin from /dev/null
} < <(node server.js)

Se vuoi tenerlo, puoi reindirizzarlo a qualche file e usarlo tail -fper esempio nella shell per aspettare il messaggio URL:

node server.js > server.log &
{
  IFS= read -r pid
  url=$(grep -m1 -oP 'Server started! Listening on URL: \K\S+') kill -- "$pid"
} < <(echo "$BASHPID"; exec tail -n +1 -f server.log)

Qui passare il pid di coda in modo da ucciderlo dopo che il messaggio è stato trovato piuttosto che aspettare taildi essere ucciso da un SIGPIPE che potrebbe non accadere tailmai se non emettere mai altro.