Come posso lavorare con binario in bash, per copiare byte alla lettera senza alcuna conversione?


14

Sto ambiziosamente cercando di tradurre un codice c ++ in bash per una miriade di ragioni.

Questo codice legge e manipola un tipo di file specifico per il mio sottocampo che è scritto e strutturato completamente in binario. Il mio primo compito relativo al binario è quello di copiare i primi 988 byte dell'intestazione, esattamente come sono, e inserirli in un file di output su cui posso continuare a scrivere mentre generi il resto delle informazioni.

Sono abbastanza sicuro che la mia soluzione attuale non funzioni, e realisticamente non ho trovato un buon modo per determinarlo. Quindi, anche se in realtà è scritto correttamente, devo essere sicuro di come testarlo!

Questo è quello che sto facendo in questo momento:

hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}
headInput=`head -c 988 ${inputTrack} | hexdump`
headOutput=`head -c 988 ${output_hdr} | hexdump`
if [ "${headInput}" != "${headOutput}" ]; then echo "output header was not written properly.  exiting.  please troubleshoot."; exit 1; fi

Se uso hexdump / xxd per dare un'occhiata a questa parte del file, anche se non riesco a leggerne la maggior parte, qualcosa sembra sbagliato. E il codice che ho scritto per il confronto mi dice solo se due stringhe sono identiche, non se sono copiate nel modo in cui voglio che siano.

C'è un modo migliore per farlo in bash? Posso semplicemente copiare / leggere byte binari in nativo-binario, per copiarli su un file alla lettera? (e idealmente anche da memorizzare come variabili).


È possibile utilizzare ddper copiare singoli byte (impostandolo countsu 1). Non sono sicuro di conservarli, però.
DDPWNAGE

Non colpire in modo C, creerà molti mal di testa. Usa invece i costrutti bash appropriati
Ferrybig

Risposte:


22

Trattare con dati binari a basso livello negli script di shell è generalmente una cattiva idea.

bashle variabili non possono contenere il byte 0. zshè l'unica shell che può memorizzare quel byte nelle sue variabili.

In ogni caso, gli argomenti dei comandi e le variabili di ambiente non possono contenere quei byte poiché sono stringhe delimitate NUL passate alla execvechiamata di sistema.

Si noti inoltre che:

var=`cmd`

o la sua forma moderna:

var=$(cmd)

rimuove tutti i caratteri di nuova riga finali dall'output di cmd. Quindi, se quell'output binario termina in byte 0xa, verrà modificato quando archiviato $var.

Qui, dovresti archiviare i dati codificati, ad esempio con xxd -p.

hdr_988=$(head -c 988 < "$inputFile" | xxd -p)
printf '%s\n' "$hdr_988" | xxd -p -r > "$output_hdr"

È possibile definire funzioni di supporto come:

encode() {
  eval "$1"='$(
    shift
    "$@" | xxd -p  -c 0x7fffffff
    exit "${PIPESTATUS[0]}")'
}

decode() {
  printf %s "$1" | xxd -p -r
}

encode var cat /bin/ls &&
  decode "$var" | cmp - /bin/ls && echo OK

xxd -pl'output non è efficiente in termini di spazio in quanto codifica 1 byte in 2 byte, ma semplifica la manipolazione (concatenazione, estrazione di parti). base64è uno che codifica 3 byte in 4, ma non è così facile da lavorare.

La ksh93shell ha un formato di codifica incorporato (usa base64) che puoi usare con le sue reade printf/ printutility:

typeset -b var # marked as "binary"/"base64-encoded"
IFS= read -rn 988 var < input
printf %B var > output

Ora, se non c'è transito tramite variabili shell o env o argomenti di comando, dovresti essere OK fintanto che le utility che usi possono gestire qualsiasi valore di byte. Tuttavia, per le utility di testo, la maggior parte delle implementazioni non GNU non è in grado di gestire byte NUL e si consiglia di correggere le impostazioni locali su C per evitare problemi con i caratteri multi-byte. L'ultimo carattere che non è un carattere di nuova riga può anche causare problemi e righe molto lunghe (sequenze di byte tra due byte 0xa più lunghe LINE_MAX).

head -cdove è disponibile dovrebbe essere OK qui, poiché è pensato per funzionare con byte e non ha motivo di trattare i dati come testo. Così

head -c 988 < input > output

dovrebbe essere ok. In pratica almeno le implementazioni integrate GNU, FreeBSD e ksh93 sono OK. POSIX non specifica l' -copzione, ma dice che headdovrebbe supportare linee di qualsiasi lunghezza (non limitate a LINE_MAX)

Con zsh:

IFS= read -rk988 -u0 var < input &&
print -rn -- $var > output

O:

var=$(head -c 988 < input && echo .) && var=${var%.}
print -rn -- $var > output

Anche in zsh, se $varcontiene byte NUL, puoi passarlo come argomento ai zshbuiltin (come printsopra) o alle funzioni, ma non come argomenti agli eseguibili, poiché gli argomenti passati agli eseguibili sono stringhe delimitate NUL, questa è una limitazione del kernel, indipendente dalla shell.


zshnon è l'unica shell che può memorizzare uno o più byte NUL in una variabile di shell. ksh93può farlo anche. Internamente, ksh93memorizza semplicemente la variabile binaria come stringa codificata in base64.
fpmurphy

@ fpmurphy1, non è quello che chiamo gestione dei dati binari , la variabile non contiene i dati binari, quindi non puoi usare nessuno degli operatori di shell su di essi, ad esempio, non puoi passarli a builtin o funzioni nei suoi forma decodificata ... La chiamerei piuttosto supporto di codifica / decodifica base64 incorporato .
Stéphane Chazelas,

11

Sto ambiziosamente cercando di tradurre un codice c ++ in bash per una miriade di ragioni.

Beh si. Ma forse dovresti considerare un motivo molto importante per NON farlo. Fondamentalmente, "bash" / "sh" / "csh" / "ksh" e simili non sono progettati per l'elaborazione di dati binari, e nemmeno la maggior parte delle utility UNIX / LINUX standard.

Sarebbe meglio attenersi al C ++ o usare un linguaggio di scripting come Python, Ruby o Perl in grado di gestire i dati binari.

C'è un modo migliore per farlo in bash?

Il modo migliore è di non farlo in bash.


4
+1 per "Il modo migliore è di non farlo in bash."
Guntram Blohm supporta Monica il

1
Un altro motivo per non seguire questa strada è che l'applicazione risultante funzionerà significativamente più lentamente e consumerà più risorse di sistema.
fpmurphy

Le pipeline di Bash possono agire come una sorta di linguaggio specifico di dominio di alto livello che può aumentare la comprensibilità. Non c'è nulla di una pipeline che non è binaria, e ci sono vari programmi di utilità implementati come strumenti della riga di comando che interagiscono con i dati binari ( ffmpeg, imagemagick, dd). Ora, se uno sta facendo la programmazione piuttosto che incollare le cose insieme, allora usare un linguaggio di programmazione completo è la strada da percorrere.
Att Righ,

6

Dalla tua domanda:

copia le prime 988 righe dell'intestazione

Se stai copiando 988 righe, sembra un file di testo, non binario. Tuttavia, il codice sembra assumere 988 byte, non 988 righe, quindi suppongo che i byte siano corretti.

hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}

Questa parte potrebbe non funzionare. Per prima cosa, tutti i byte NUL nel flusso verranno rimossi, poiché si utilizza ${hdr_988}come argomento della riga di comando e gli argomenti della riga di comando non possono contenere NUL. Anche i backtick potrebbero causare il munging degli spazi bianchi (non ne sono sicuro). (In realtà, poiché echoè incorporato, la restrizione NUL potrebbe non essere applicabile, ma direi che è ancora incerto.)

Perché non scrivere semplicemente l'intestazione direttamente dal file di input nel file di output, senza passarlo attraverso una variabile shell?

head -c 988 "${inputFile}" >"${output_hdr}"

O, più facilmente,

dd if="${inputFile}" of="${output_hdr}" bs=988 count=1

Dato che dici che stai usando bash, non la shell POSIX, hai a disposizione la sostituzione del processo, quindi che ne dici di questo come test?

cmp <(head -c 988 "${inputFile}") <(head -c 988 "${output_hdr}")

Infine: considera l' utilizzo $( ... )anziché i backtick.


Si noti che ddnon è necessariamente equivalente a headper i file non regolari. headfarà tutte read(2)le chiamate di sistema necessarie per ottenere quei 988 byte mentre ne ddfarà solo uno read(2). GNU dddeve iflag=fullblockprovare a leggere quel blocco per intero, ma è ancora meno portatile di head -c.
Stéphane Chazelas,
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.