Come posso gestire i dati binari grezzi in una pipe bash?


15

Ho una funzione bash che accetta un file come parametro, verifica l'esistenza del file, quindi scrive tutto ciò che viene stdin nel file. La soluzione ingenua funziona bene per il testo, ma sto riscontrando problemi con dati binari arbitrari.

echo -n '' >| "$file" #Truncate the file
while read lines
do  # Is there a better way to do this? I would like one...
    echo $lines >> "$file"
done

Risposte:


15

La tua strada è aggiungere interruzioni di linea a tutto ciò che scrive nello spazio di qualunque separatore ( $IFS) stia usando per dividere la lettura. Invece di dividerlo in nuove righe, prendi tutto e passalo. È possibile ridurre l'intero bit di codice sopra a questo:

 cat - > $file

Non è necessario il bit troncato, questo troncerà e scriverà l'intero flusso STDIN su di esso.

Modifica: se stai usando zsh puoi semplicemente usarlo > $fileal posto del gatto. Stai reindirizzando a un file e troncandolo, ma se c'è qualcosa in attesa che qualcosa accetti STDIN verrà letto a quel punto. Penso che puoi fare qualcosa del genere con bash ma dovresti impostare una modalità speciale.


Non sono riuscito a far funzionare l'esempio di reindirizzamento stdin, ma cambiando l'esempio cat in> | (Ho impostato il noclobber) funziona come un incantesimo. Grazie per aver reso la mia giornata ^. ^
David Souther il

+1 per la versione senza gatto. Evita sempre gatti inutili;)
rozcietrzewiacz,

@rozcietrzewiacz: Vero, tranne che è stato un ripensamento e ho sbagliato. Questo potrebbe non essere un uso inutile del gatto. L'unica cosa che potresti essere in grado di fare è > $file. Funziona solo come prima cosa che cerca stdin nello script della shell padre. Fondamentalmente tutto il codice di David può essere ridotto a un singolo carattere, ma penso che cat -sia più elegante e meno problematico perché è compreso a vista.
Caleb,

A volte metto insieme quattro o cinque catsecondi, solo per infastidire i fanatici dell'UUOC
Michael Mrozek

@MichaelMrozek: A volte chiamo i miei file di dati, catcosì le persone che insistono nell'usarlo devono necessariamente fare ginnastica mentale per leggere il codice. Anche le pipe nominate sono buoni obiettivi.
Caleb,

7

Per leggere un file di testo letteralmente, non usare plain read, che elabora l'output in due modi:

  • readinterpreta \come un personaggio di fuga; usare read -rper disattivarlo.
  • readsi divide in parole sui personaggi in $IFS; impostato IFSsu una stringa vuota per disattivarlo.

Il solito linguaggio per elaborare un file di testo riga per riga è

while IFS= read -r line; do 

Per una spiegazione di questo linguaggio, vedi Perché viene while IFS= readusato così spesso, invece di IFS=; while read..? .

Per scrivere una stringa letteralmente, non usare semplicemente plain echo, che elabora la stringa in due modi:

  • Su alcune shell, i echoprocessi backslash escono. (Su bash, dipende se l' xpg_echoopzione è impostata.)
  • Alcune stringhe vengono trattate come opzioni, ad esempio -no -e(il set esatto dipende dalla shell).

Un modo portatile di stampare una stringa è letteralmente con printf. (Non c'è modo migliore in bash, a meno che tu non sappia che il tuo input non sembra un'opzione echo.) Usa il primo modulo per stampare la stringa esatta e il secondo modulo se vuoi aggiungere una nuova riga.

printf %s "$line"
printf '%s\n' "$line"

Questo è adatto solo per l'elaborazione del testo , perché:

  • La maggior parte delle shell si strozzerà con caratteri null nell'input.
  • Quando hai letto l'ultima riga, non hai modo di sapere se alla fine c'era una nuova riga. (Alcune shell meno recenti potrebbero avere problemi maggiori se l'input non termina con una nuova riga.)

Non è possibile elaborare dati binari nella shell, ma le versioni moderne dei programmi di utilità sulla maggior parte degli unici possono far fronte a dati arbitrari. Per passare tutto l'input all'output, utilizzare cat. Andare su una tangente echo -n ''è un modo complicato e non portatile di non fare nulla; echo -nsarebbe altrettanto buono (o meno a seconda della shell), ed :è più semplice e completamente portatile.

: >| "$file"
cat >>"$file"

o, più semplice,

cat >|"$file"

In uno script, di solito non è necessario utilizzare >|poiché noclobberè disattivato per impostazione predefinita.


grazie per aver sottolineato xpg_echo, questo è in realtà un problema che stavo riscontrando da qualche altra parte nel mio codice e non me ne ero nemmeno accorto. Per quanto riguarda la notte, ho l'abitudine di accenderlo nel mio bashrc.
David Souther,

0

Questo farà esattamente quello che vuoi:

( while read -r -d '' ; do
    printf %s'\0' "${REPLY}" ;
  done ;

  # When read hits EOF, it returns non-zero which exits the while loop.
  # That data still needs to be output:
  printf %s "${REPLY}"
) >> ${file}

Nota però l'uso della memoria. Questo legge l'input in modo delimitato da null.

Se non ci sono byte \0 nulli nell'input, bash dovrà prima leggere l'intero contenuto dell'input in memoria e quindi emetterlo.

Per quanto riguarda il passo troncato:

echo -n '' >| "$file" #Truncate the file

un molto più semplice ed equivalente è:

> ${file}   #Truncate the file
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.