Come posso catturare lo stdin in una variabile senza rimuovere le nuove righe finali?


9

In uno script di shell ...

Come posso catturare lo stdin in una variabile senza rimuovere le nuove righe finali?

In questo momento ho provato:

var=`cat`
var=`tee`
var=$(tee)

In tutti i casi $varnon avrà la nuova riga finale del flusso di input. Grazie.

ANCHE: Se non è presente una nuova riga finale nell'input, la soluzione non deve aggiungerne una .

AGGIORNAMENTO ALLA LUCE DELLA RISPOSTA ACCETTATA:

La soluzione finale che ho usato nel mio codice è la seguente:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

Se desideri vedere il codice completo, consulta la pagina github per expandr , uno script di shell di filtro di espansione delle parole chiave git open source che ho sviluppato per motivi di sicurezza delle informazioni. In base alle regole impostate nei file .gitattributes (che possono essere specifici del ramo) e git config , git reindirizza ogni file attraverso lo script della shell expandr.sh ogni volta che lo si fa dentro o fuori dal repository. (Questo è il motivo per cui è stato fondamentale preservare eventuali nuove righe finali o la loro mancanza.) Ciò consente di ripulire le informazioni sensibili e scambiare in diversi set di valori specifici dell'ambiente per test, stadiazione e rami attivi.


quello che fai qui non è necessario. filterprende stdin- corre sed. Si cattura stdinnel $GIT_INPUTquindi stampare che di nuovo a stdoutpiù di un tubo per filtere prendere la sua stdoutin $FILTERED_OUTPUTe quindi stampare di nuovo a stdout. Tutte le 4 linee nella parte inferiore del vostro esempio di cui sopra potrebbero essere sostituiti con proprio questo: filter. Senza offesa intendevo qui, è solo che ... stai lavorando troppo duramente. Non hai bisogno delle variabili della shell per la maggior parte del tempo, basta dirigere l'input nel posto giusto e trasmetterlo.
Mikeserv,

No, quello che faccio qui è necessario perché, se lo faccio filter, aggiungerà i caratteri di nuova riga alla fine di tutti i flussi di input che inizialmente non terminavano in nuove righe. In effetti, in origine l'ho fatto, filterma mi sono imbattuto in quel problema che mi ha portato a questa soluzione perché né "aggiungere sempre nuove righe" né "eliminare sempre le nuove righe" sono soluzioni accettabili.
CommaToast,

sedprobabilmente farà la nuova riga extra - ma dovresti gestirlo filternon con tutto il resto. E tutte quelle funzioni che hai sostanzialmente fanno la stessa cosa: a sed s///. Stai usando la shell per reindirizzare i dati che ha salvato nella sua memoria in sedmodo da sedpoter sostituire quei dati con altri dati che la shell ha archiviato nella sua memoria in modo da sedpoter reindirizzare alla shell. Perché non solo [ "$var" = "$condition" ] && var=new_value? Inoltre non ottengo gli array: stai memorizzando il nome dell'array e [0]poi lo usi sedper sostituirlo con il valore in [1]? Forse chattare?
Mikeserv,

@mikeserv - Quale sarebbe il vantaggio di spostare quel codice all'interno filter? Funziona perfettamente così com'è. Per quanto riguarda il funzionamento del codice sul mio collegamento e il motivo per cui l'ho impostato come ho fatto, sì, ne parliamo in una chat room.
CommaToast,

Risposte:


7

Le nuove righe finali vengono rimosse prima che il valore venga memorizzato nella variabile. Potresti voler fare qualcosa del tipo:

var=`cat; echo x`

e usa ${var%x}invece di $var. Per esempio:

printf "%s" "${var%x}"

Si noti che ciò risolve il problema delle nuove righe finali, ma non quello del byte null (se l'input standard non è testo), poiché secondo la sostituzione del comando POSIX :

Se l'output contiene byte nulli, il comportamento non è specificato.

Ma le implementazioni della shell possono conservare byte nulli.


I file di testo in genere contengono byte null? Non riesco a capire perché lo farebbero. Ma la sceneggiatura che hai appena citato non sembra funzionare.
CommaToast

@CommaToast I file di testo non contengono byte null. Ma la domanda dice semplicemente stdin / input stream, che potrebbe non essere testo nel caso più generale.
vinc17,

OK. Beh, l'ho provato dalla riga di comando e non ha fatto nulla, e all'interno del mio stesso script, il tuo suggerimento fallisce perché aggiunge "..." alla fine del file. Inoltre, se non c'era una nuova riga lì, allora ne aggiunge ancora una.
CommaToast,

@CommaToast Il "..." era solo un esempio. Ho chiarito la mia risposta. Non viene aggiunta una nuova riga (vedere il testo prima del "..." nell'esempio).
vinc17,

1
Bene, le conchiglie non dovrebbero nascondere le cose, non è bello. Quei proiettili dovrebbero essere sparati. Non mi piace quando il mio computer pensa di sapere meglio di me.
CommaToast

4

È possibile utilizzare il readbuilt-in per eseguire ciò:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

Per uno script leggere STDIN, sarebbe semplicemente:

IFS='' read -d '' -r foo

 

Non sono sicuro in quali shell funzionerà comunque. Ma funziona bene sia in bash che in zsh.


-dla sostituzione del processo ( <(...)) sono portatili; questo codice non funzionerà dash, per esempio.
Chepner,

Bene, la sostituzione del processo non fa parte della risposta, che era solo una parte dell'esempio che mostrava che funziona. Per quanto riguarda -d, ecco perché ho messo la dichiarazione di non responsabilità in fondo. L'OP non specifica la shell.
Patrick,

@chepner - mentre lo stile differisce leggermente, il concetto sicuramente funziona dash. Basta usare <<HEREDOC\n$(gen input)\nHEREDOC\n- in dash- che usa le pipe per heredocs nello stesso modo in cui le altre shell le usano per la sostituzione del processo - non fa differenza. La read -dcosa è solo specificare un delimitatore - puoi fare lo stesso in una dozzina di modi - basta esserne sicuri. Anche se avrai bisogno di un po 'di coda per gen input.
Mikeserv,

Hai impostato IFS = '' in modo che non inserisca spazi tra le righe che legge in eh? Bel trucco.
CommaToast,

In realtà in questo caso IFS=''probabilmente non è necessario. È pensato per readnon far collassare gli spazi. Ma quando legge in una singola variabile, non ha alcun effetto (che posso ricordare). Ma mi sento più sicuro a lasciarlo acceso :-)
Patrick

2

Puoi fare come:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

Qualunque cosa tu faccia $varscompare non appena esci da quel { current shell ; }gruppo comunque. Ma potrebbe anche funzionare come:

var=$(input | sed '$s/$/./'); var=${var%.}

1
Va notato che con la prima soluzione, ovvero dover utilizzare $varnel { ... }raggruppamento, non è sempre possibile. Ad esempio se questo comando viene eseguito all'interno di un ciclo e uno deve essere $varesterno al ciclo.
vinc17,

@ vinc17 - se è un ciclo che volevo usare, lo userei al posto delle {}parentesi graffe. È vero - ed è esplicitamente notato nella risposta - che il valore per $varè molto probabile che scompaia completamente quando il { current shell; }raggruppamento è chiuso. C'è un modo più esplicito per dirlo che, qualunque cosa tu faccia $varscompare ...?
Mikeserv,

@ vinc17 - probabilmente il modo migliore, però:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv

1
C'è anche la _variablesfunzione di bash_completion, che memorizza il risultato di una sostituzione di comando in una variabile globale COMPREPLY. Se fosse utilizzata una soluzione pipeline per mantenere le nuove linee, il risultato andrebbe perso. Nella tua risposta, si ha l'impressione che entrambe le soluzioni siano ugualmente buone. Inoltre, va notato che il comportamento della soluzione della pipeline dipende fortemente dalla shell: un utente potrebbe testare echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $varcon ksh93 e zsh e pensa che sia OK, mentre questo codice è difettoso.
vinc17,

1
Non hai detto "non funziona". Hai appena detto " $varscompare" (che in realtà non è vero poiché questo dipende dalla shell - il comportamento non è specificato da POSIX), che è una frase piuttosto neutra. La seconda soluzione è migliore perché non presenta questo problema e il suo comportamento è coerente in tutte le shell POSIX.
vinc17,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.