Jak mogę pracować z binarnym w bash, aby kopiować bajty dosłownie bez konwersji?

14

Z wielu powodów staram się tłumaczyć kod c ++ na bash.

Ten kod odczytuje i manipuluje typem pliku specyficznym dla mojego subpola, który jest zapisany i ustrukturyzowany całkowicie w formacie binarnym. Moim pierwszym zadaniem związanym z plikami binarnymi jest skopiowanie pierwszych 988 bajtów nagłówka, dokładnie tak, jak jest, i umieszczenie ich w pliku wyjściowym, do którego mogę kontynuować zapisywanie podczas generowania reszty informacji.

Jestem całkiem pewien, że moje obecne rozwiązanie nie działa, i realistycznie nie znalazłem dobrego sposobu, aby to ustalić. Więc nawet jeśli jest napisane poprawnie, muszę wiedzieć, jak bym to przetestował, aby się upewnić!

Oto co teraz robię:

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

Jeśli użyję hexdump / xxd do sprawdzenia tej części pliku, chociaż nie mogę dokładnie odczytać większości tego pliku, coś wydaje się nie tak. A kod, który napisałem dla porównania, mówi mi tylko, czy dwa ciągi są identyczne, a nie, jeśli są kopiowane tak, jak chcę.

Czy jest lepszy sposób na zrobienie tego w bash? Czy mogę po prostu skopiować / odczytać bajty binarne w natywnym pliku binarnym, aby skopiować je do pliku dosłownie? (i idealnie, aby przechowywać również jako zmienne).

neurokoder
źródło
Możesz użyć dddo skopiowania pojedynczych bajtów (ustawiając countna 1). Jednak nie jestem pewien, czy je przechowywać.
DDPWNAGE
Nie rób ciosu w sposób C, spowoduje to wiele bólów głowy. Zamiast tego używaj odpowiednich konstrukcji bash
Ferrybig 24.04.16

Odpowiedzi:

22

Radzenie sobie z danymi binarnymi na niskim poziomie w skryptach powłoki jest ogólnie złym pomysłem.

bashzmienne nie mogą zawierać bajtu 0. zshjest jedyną powłoką, która może przechowywać ten bajt w swoich zmiennych.

W każdym razie argumenty poleceń i zmienne środowiskowe nie mogą zawierać tych bajtów, ponieważ są one łańcuchami rozdzielanymi NUL przekazywanymi do execvewywołania systemowego.

Pamiętaj również, że:

var=`cmd`

lub jego nowoczesna forma:

var=$(cmd)

usuwa wszystkie końcowe znaki nowej linii z wyniku cmd. Jeśli więc to wyjście binarne kończy się na bajty 0xa, zostanie ono zniekształcone, gdy zostanie zapisane $var.

W tym miejscu należy przechowywać zakodowane dane, na przykład za pomocą xxd -p.

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

Możesz zdefiniować funkcje pomocnicze, takie jak:

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 -pdane wyjściowe nie zajmują mało miejsca, ponieważ kodują 1 bajt na 2 bajty, ale ułatwiają manipulowanie nim (łączenie, wydobywanie części). base64to taki, który koduje 3 bajty na 4, ale nie jest tak łatwy w obsłudze.

ksh93Powłoka ma polecenie wbudowane kodujący Format (zastosowań base64), które można wykorzystać z jego readi printf/ printmedia:

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

Teraz, jeśli nie ma tranzytu przez zmienne powłoki lub env lub argumenty poleceń, powinieneś być OK, o ile używane narzędzia mogą obsłużyć dowolną wartość bajtu. Należy jednak pamiętać, że w przypadku narzędzi tekstowych większość implementacji innych niż GNU nie może obsługiwać bajtów NUL, a będziesz chciał ustawić ustawienia regionalne na C, aby uniknąć problemów ze znakami wielobajtowymi. Ostatni znak, który nie jest znakiem nowej linii, może również powodować problemy, a także bardzo długie linie (sekwencje bajtów między dwoma bajtami 0xa, które są dłuższe LINE_MAX).

head -ctam, gdzie jest dostępny, powinno być tutaj OK, ponieważ ma pracować z bajtami i nie ma powodu, aby traktować dane jako tekst. Więc

head -c 988 < input > output

Powinno być ok. W praktyce przynajmniej wbudowane implementacje GNU, FreeBSD i ksh93 są w porządku. POSIX nie określa -copcji, ale mówi, że headpowinien obsługiwać linie dowolnej długości (nie ograniczone do LINE_MAX)

Z zsh:

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

Lub:

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

Nawet wewnątrz zsh, jeśli $varzawiera bajty NUL, możesz przekazać go jako argument do zshwbudowanych funkcji (jak printwyżej) lub funkcji, ale nie jako argumentów do plików wykonywalnych, ponieważ argumenty przekazywane do plików wykonywalnych są łańcuchami ograniczonymi przez NUL, jest to ograniczenie jądra niezależne od powłoki.

Stéphane Chazelas
źródło
zshnie jest jedyną powłoką, która może przechowywać jeden lub więcej bajtów NUL w zmiennej powłoki. ksh93mogę to również zrobić. Wewnętrznie ksh93po prostu przechowuje zmienną binarną jako ciąg zakodowany w standardzie base64.
fpmurphy
@ fpmurphy1, to nie to, co nazywam obsługą danych binarnych , zmienna nie zawiera danych binarnych, więc nie możesz na przykład użyć żadnego z operatorów powłoki, nie możesz przekazać ich do wbudowanych funkcji lub funkcji forma zdekodowana ... nazwałbym to raczej wbudowaną obsługą kodowania / dekodowania base64 .
Stéphane Chazelas,
11

Z wielu powodów staram się tłumaczyć kod c ++ na bash.

No tak. Ale może powinieneś rozważyć bardzo ważny powód, aby tego NIE robić. Zasadniczo „bash” / „sh” / „csh” / „ksh” i tym podobne nie są przeznaczone do przetwarzania danych binarnych i nie są większością standardowych narzędzi UNIX / LINUX.

Lepiej byłoby trzymać się C ++ lub użyć języka skryptowego takiego jak Python, Ruby lub Perl, który jest w stanie radzić sobie z danymi binarnymi.

Czy jest lepszy sposób na zrobienie tego w bash?

Lepszym sposobem jest nie robić tego bash.

Stephen C.
źródło
4
+1 dla „Lepszym sposobem jest nie robić tego bash”.
Guntram Blohm obsługuje Monikę
1
Innym powodem, aby nie iść tą drogą, jest to, że wynikowa aplikacja będzie działać znacznie wolniej i zużywać więcej zasobów systemowych.
fpmurphy 24.04.16
Potoki Bash mogą działać jako język wysokiego poziomu specyficzny dla domeny, który może zwiększyć zrozumiałość. Nie ma nic o gazociągu, który nie jest binarny, a istnieją różne narzędzia zaimplementowane jako narzędzi wiersza polecenia, które współdziałają z danych binarnych ( ffmpeg, imagemagick, dd). Teraz, jeśli ktoś programuje zamiast sklejać rzeczy ze sobą, to najlepiej jest użyć języka programowania o pełnej mocy.
Att Righ
6

Z twojego pytania:

skopiuj pierwsze 988 linii nagłówka

Jeśli kopiujesz 988 linii, wygląda to na plik tekstowy, a nie binarny. Jednak twój kod wydaje się przyjmować 988 bajtów, a nie 988 linii, więc założę, że bajty są poprawne.

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

Ta część może nie działać. Po pierwsze, wszystkie bajty NUL w strumieniu zostaną usunięte, ponieważ używasz ich ${hdr_988}jako argumentu wiersza poleceń, a argumenty wiersza poleceń nie mogą zawierać wartości NUL. Backticks również może wykonywać munging spacjami (nie jestem tego pewien). (W rzeczywistości, ponieważ echojest to wbudowane ograniczenie NUL może nie mieć zastosowania, ale powiedziałbym, że nadal jest niepewne.)

Dlaczego nie napisać nagłówka bezpośrednio z pliku wejściowego do pliku wyjściowego, bez przekazywania go przez zmienną powłoki?

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

Lub, bardziej przenośnie,

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

Ponieważ wspomniałeś, że używasz bash, a nie powłoki POSIX, masz do dyspozycji substytucję procesu, więc co powiesz na ten test?

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

Wreszcie: rozważ użycie $( ... )zamiast backticks.

Celada
źródło
Pamiętaj, że ddniekoniecznie jest to równoważne z headplikami nieregularnymi. headwykona tyle read(2)wywołań systemowych, ile potrzeba, aby uzyskać 988 bajtów, podczas gdy ddzrobi tylko jedno read(2). GNU ddmusi iflag=fullblockspróbować odczytać ten blok w całości, ale jest to nawet mniej przenośne niż head -c.
Stéphane Chazelas