Odpowiedzi:
Jeśli chcesz trzymać się narzędzi powłoki, możesz użyć head
do wyodrębnienia liczby bajtów i od
przekonwertowania 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
'
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
read -N
zatrzymuje 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.
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
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).
dd
odczytuje 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ć; dd
nie 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 ... dd
widzi tylko bajty bez względu na ustawienia regionalne ...
Ten skrypt używa dd
do 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
#
/dev/urandom
wię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.