Jak używać skryptu bash do odczytywania zawartości pliku binarnego?

15

Chcę odczytać znak, a następnie ustaloną długość ciągu (ciąg nie jest zakończony zerem w pliku, a jego długość jest podana przez poprzedni znak).

Jak mogę to zrobić w skrypcie bash? Jak zdefiniować zmienną typu string, aby móc na niej wykonać przetwarzanie końcowe?

Amanda
źródło

Odpowiedzi:

19

Jeśli chcesz trzymać się narzędzi powłoki, możesz użyć headdo wyodrębnienia liczby bajtów i odprzekonwertowania bajtu na liczbę.

export LC_ALL=C    # make sure we aren't in a multibyte locale
n=$(head -c 1 | od -An -t u1)
string=$(head -c $n)

Nie działa to jednak w przypadku danych binarnych. Istnieją dwa problemy:

  • Podstawienie polecenia $(…)usuwa ostatnie znaki nowej linii z wyniku polecenia. Jest dość łatwe obejście: upewnij się, że wynik kończy się znakiem innym niż nowa linia, a następnie usuń ten znak.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, podobnie jak większość powłok, źle radzi sobie z bajtami zerowymi . Począwszy od wersji bash 4.1 bajty zerowe są po prostu usuwane z wyniku podstawienia polecenia. Dash 0.5.5 i pdksh 5.2 mają to samo zachowanie, a ATT ksh przestaje czytać przy pierwszym bajcie zerowym. Ogólnie rzecz biorąc, powłoki i ich narzędzia nie są przeznaczone do obsługi plików binarnych. (Zsh jest wyjątkiem, jest zaprojektowany do obsługi bajtów zerowych).

Jeśli masz dane binarne, możesz przełączyć się na język taki jak Perl lub Python.

<input_file perl -e '
  read STDIN, $c, 1 or die $!;    # read length byte
  $n = read STDIN, $s, ord($c);   # read data
  die $! if !defined $n;
  die "Input file too short" if ($n != ord($c));
  # Process $s here
'
<input_file python -c '
  import sys
  n = ord(sys.stdin.read(1))      # read length byte
  s = sys.stdin.read(n)           # read data
  if len(s) < n: raise ValueError("input file too short")
  # Process s here
'
Gilles „SO- przestań być zły”
źródło
Skrypty powłoki +1 nie zawsze są odpowiednie
forcefsck,
2
exec 3<binary.file     # open the file for reading on file descriptor 3
IFS=                   #
read -N1 -u3 char      # read 1 character into variable "char"

# to obtain the ordinal value of the char "char"
num=$(printf %s "$char" | od -An -vtu1 | sed 's/^[[:space:]]*//')

read -N$num -u3 str    # read "num" chars
exec 3<&-              # close fd 3
Glenn Jackman
źródło
5
read -Nzatrzymuje się na bajty zerowe, więc nie jest to odpowiedni sposób pracy z danymi binarnymi. Ogólnie rzecz biorąc, powłoki inne niż zsh nie radzą sobie z zerami.
Gilles „SO- przestań być zły”,
2

Jeśli chcesz mieć możliwość radzenia sobie z plikiem binarnym w powłoce, najlepszą opcją (tylko?) Jest praca z narzędziem hexdump .

hexdump -v -e '/1 "%u\n"' binary.file | while read c; do
  echo $c
done

Tylko do odczytu X bajtów:

head -cX binary.file | hexdump -v -e '/1 "%u\n"' | while read c; do
  echo $c
done

Odczytaj długość (i pracuj z 0 jako długością), a następnie „ciąg” jako bajtową wartością dziesiętną:

len=$(head -c1 binary.file | hexdump -v -e '/1 "%u\n"')
if [ $len -gt 0 ]; then
  tail -c+2 binary.file | head -c$len | hexdump -v -e '/1 "%u\n"' | while read c; do
    echo $c
  done
fi
Clément Moulin - SimpleRezo
źródło
Czy zamiast wyjaśnić kilka poleceń, możesz wyjaśnić, co robią i jak działają? Co oznaczają opcje? Jakich wyników może oczekiwać użytkownik od twoich poleceń? Proszę nie odpowiadać w komentarzach; edytuj  swoją odpowiedź, aby była jaśniejsza i bardziej kompletna.
G-Man mówi „Przywróć Monikę”
2
Cóż, mogę tutaj skopiować strony podręcznika, ale nie widzę sensu. Użyto tu tylko podstawowych poleceń, jedyną sztuczką jest użycie hexdump.
Clément Moulin - SimpleRezo
2
Oddawanie głosu, ponieważ nie podoba ci się moja odpowiedź, poważnie?
Clément Moulin - SimpleRezo
1

AKTUALIZACJA (z perspektywy czasu): ... To pytanie / odpowiedź (moja odpowiedź) sprawia, że ​​myślę o psie, który wciąż goni samochód. Pewnego dnia w końcu dogania samochód. Ok, złapał go, ale on naprawdę nie może wiele z tym zrobić ... Ten anser „łapie” ciągi, ale wtedy nie można wiele z nimi zrobić, jeśli mają osadzone null-bajty ... (więc duża +1 odpowiedź Gillesa .. inny język może być tutaj w porządku).

ddodczytuje wszystkie dane ... Z pewnością nie zajmie to zera jako „długości” ... ale jeśli masz \ x00 gdziekolwiek w swoich danych, musisz być kreatywny, jak sobie z nimi poradzić; ddnie ma z tym żadnych problemów, ale twój skrypt powłoki będzie miał problemy (ale zależy to od tego, co chcesz zrobić z danymi) ... Poniższe informacje w zasadzie wypisują każdy „ciąg danych” do pliku z dzielnikiem linii między każdą strin ...

btw: Mówisz „znak” i zakładam , że masz na myśli „bajt” ...
ale słowo „znak” stało się dwuznaczne w czasach UNICODE, gdzie tylko 7-bitowy zestaw znaków ASCII używa jednego bajtu na znak ... A nawet w systemie Unicode liczba bajtów różni się w zależności od metody kodowania znaków , np. UTF-8, UTF-16 itp.

Oto prosty skrypt do podkreślenia różnicy między „znakiem” tekstowym a bajtami.

STRING="௵"  
echo "CHAR count is: ${#STRING}"  
echo "BYTE count is: $(echo -n $STRING|wc -c)" 
# CHAR count is: 1
# BYTE count is: 3  # UTF-8 ecnoded (on my system)

Jeśli twój znak długości ma długość 1-bajta i wskazuje długość bajtu , to ten skrypt powinien załatwić sprawę, nawet jeśli dane zawierają znaki Unicode ... ddwidzi tylko bajty bez względu na ustawienia regionalne ...

Ten skrypt używa dddo odczytu pliku binarnego i wyświetla ciągi znaków oddzielone dzielnikiem "====" ... Zobacz dane testowe w następnym skrypcie

#   
div="================================="; echo $div
((skip=0)) # read bytes at this offset
while ( true ) ; do
  # Get the "length" byte
  ((count=1)) # count of bytes to read
  dd if=binfile bs=1 skip=$skip count=$count of=datalen 2>/dev/null
  (( $(<datalen wc -c) != count )) && { echo "INFO: End-Of-File" ; break ; }
  strlen=$((0x$(<datalen xxd -ps)))  # xxd is shipped as part of the 'vim-common' package
  #
  # Get the string
  ((count=strlen)) # count of bytes to read
  ((skip+=1))      # read bytes from and including this offset
  dd if=binfile bs=1 skip=$skip count=$count of=dataline 2>/dev/null
  ddgetct=$(<dataline wc -c)
  (( ddgetct != count )) && { echo "ERROR: Line data length ($ddgetct) is not as expected ($count) at offset ($skip)." ; break ; }
  echo -e "\n$div" >>dataline # add a newline for TEST PURPOSES ONLY...
  cat dataline
  #
  ((skip=skip+count))  # read bytes from and including this offset
done
#   
echo

wyjście

Ten skrypt buduje dane testowe, które zawierają 3-bajtowy prefiks w wierszu ...
Prefiks to pojedynczy znak Unicode zakodowany w UTF-8 ...

# build test data
# ===============
  prefix="௵"   # prefix all non-zero length strings will this obvious 3-byte marker.
  prelen=$(echo -n $prefix|wc -c)
  printf \\0 > binfile  # force 1st string to be zero-length (to check zero-length logic) 
  ( lmax=3 # line max ... the last on is set to  255-length (to check  max-length logic)
    for ((i=1;i<=$lmax;i++)) ; do    # add prefixed random length lines 
      suflen=$(numrandom /0..$((255-prelen))/)  # random length string (min of 3 bytes)
      ((i==lmax)) && ((suflen=255-prelen))      # make last line full length (255) 
      strlen=$((prelen+suflen))
      printf \\$((($strlen/64)*100+$strlen%64/8*10+$strlen%8))"$prefix"
      for ((j=0;j<suflen;j++)) ; do
        byteval=$(numrandom /9,10,32..126/)  # output only printabls ASCII characters
        printf \\$((($byteval/64)*100+$byteval%64/8*10+$byteval%8))
      done
        # 'numrandom' is from package 'num-utils"
    done
  ) >>binfile
#
Peter.O
źródło
1
Twój kod wygląda na bardziej skomplikowany, niż powinien być, szczególnie generator losowych danych testowych. Możesz uzyskać losowe bajty z /dev/urandomwiększości jednorożców. Losowe dane testowe nie są najlepszymi danymi testowymi, dlatego należy zająć się trudnymi przypadkami, takimi jak tutaj puste znaki i znaki nowej linii w miejscach granicznych.
Gilles 'SO - przestań być zły'
Tak, dziękuję. Myślałem o użyciu / dev / random, ale pomyślałem, że gen danych testowych nie ma wielkiego znaczenia i chciałem przetestować dysk „numrandom” (o czym wspominałeś gdzie indziej; „kilka fajnych funkcji num-utils). Właśnie przyjrzałem się twojej odpowiedzi i zdałem sobie sprawę, że robisz to samo, tyle że bardziej zwięzłe :) .. Nie zauważyłem, że wskazałeś kluczowe punkty w 3 liniach! Skoncentrowałem się na twoich referencjach w innych językach . Przygotowanie do pracy było dobrym doświadczeniem i teraz lepiej rozumiem twoje referencje w innych językach! \ x00 może być ogranicznikiem
Peter.O
0

Ten po prostu skopiuj plik binarny:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
rzr
źródło