İkili dosya içeriğini okumak için bash betiği nasıl kullanılır?


15

Bir karakter ve daha sonra sabit bir dize okumak istiyorum (dize dosyada sonlandırıldı ve uzunluğu önceki karakter tarafından verilir).

Bunu bir bash betiğinde nasıl yapabilirim? Dize değişkeni tanımlamak, böylece üzerinde bazı post-processing yapabilirim?

Yanıtlar:


19

Kabuk yardımcı programlarına bağlı kalmak istiyorsanız, headbirkaç bayt ayıklamak ve odbaytı bir sayıya dönüştürmek için kullanabilirsiniz.

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)

Ancak, bu ikili veriler için çalışmaz . İki sorun var:

  • Komut ikamesi $(…)şeritler nihai yeni satır komutu çıktısında. Oldukça kolay bir çözüm var: çıktının yeni satırdan başka bir karakterle sona erdiğinden emin olun, ardından bu karakteri soyun.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, çoğu kabuk gibi, boş baytlarla uğraşmada kötüdür . Bash 4.1'den itibaren, boş baytlar komut değiştirme işleminin sonucundan çıkarılır. Çizgi 0.5.5 ve pdksh 5.2 aynı davranışa sahiptir ve ATT ksh ilk boş baytta okumayı durdurur. Genel olarak, kabuklar ve bunların yardımcı programları ikili dosyalarla uğraşmaya yönelik değildir. (Zsh istisnadır, boş baytları desteklemek için tasarlanmıştır.)

İkili verileriniz varsa, Perl veya Python gibi bir dile geçmek istersiniz.

<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
'

+1 kabuk komut dosyaları her zaman uygun değildir
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

5
read -Nboş baytta durur, bu nedenle bu ikili verilerle çalışmak için uygun bir yol değildir. Genel olarak, zsh dışındaki mermiler boş değerlerle baş edemez.
Gilles 'SO- kötü olmayı durdur'

2

Eğer kabuk ikili dosya ile başa çıkmak istiyorsanız, en iyi seçenek (sadece?) Hexdump aracı ile çalışmaktır .

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

Yalnızca X baytını oku:

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

Uzunluğu okuyun (ve uzunluk olarak 0 ile çalışın) ve ardından bayt ondalık değeri olarak "dize":

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

Bir sürü komut sunmak yerine, ne yaptıklarını ve nasıl çalıştıklarını açıklayabilir misiniz? Seçenekler ne anlama geliyor? Kullanıcı komutlarınızdan hangi çıktıyı bekleyebilir? Lütfen yorumlarda yanıt vermeyin;  daha net ve eksiksiz hale getirmek için cevabınızı düzenleyin .
G-Man, 'Monica'yı Eski

2
Manajları buraya kopyalayabilirim, ama anlamýyorum. Burada sadece temel komutlar var, tek hile hexdump kullanmak.
Clément Moulin - SimpleRezo

2
Aşağı oylama çünkü cevabımı beğenmediniz / anlamıyorsunuz, cidden?
Clément Moulin - SimpleRezo

1

GÜNCELLEME (geziyle birlikte): ... Bu soru / cevap (cevabım) arabayı kovalayan köpeği düşünmemi sağlıyor .. Bir gün, nihayet arabaya yetişiyor .. Tamam, yakaladı, ama o gerçekten onunla pek bir şey yapamaz ... Bu anser ipleri 'yakalar', ancak sonra boş bayt yerleştirmişlerse onlarla çok fazla şey yapamazsınız ... (böylece Gilles'in cevabına büyük bir +1 .. burada başka bir dil düzenlenmiş olabilir.)

ddtüm verileri okur ... Kesinlikle sıfır olarak "uzunluk" olarak baulk olmaz ... ama verilerinizde herhangi bir yerde \ x00 varsa, nasıl işlediğiniz konusunda yaratıcı olmanız gerekir; ddonunla herhangi bir sorun yoktur, ancak kabuk betiğinizde sorunlar olacaktır (ancak verilerle ne yapmak istediğinize bağlıdır) ... Aşağıdaki temelde her "veri dizesini" her strin arasında bir çizgi bölücüsü olan bir dosyaya çıkarır ...

btw: "Karakter" diyorsunuz ve "bayt" demek istediğinizi varsayalım ...
ancak "karakter" kelimesi UNICODE'un bu günlerinde belirsiz hale geldi, burada sadece 7 bitlik ASCII karakter kümesi karakter başına tek bir bayt kullanıyor ... Ve Unicode sisteminde bile, bayt sayıları karakterleri kodlama yöntemine bağlı olarak değişir , örn. UTF-8, UTF-16 vb.

İşte bir Metin "karakteri" ve bayt arasındaki farkı vurgulamak için basit bir komut dosyası.

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)

Senin uzunluğu ise karakter uzunluğunda 1 bayt ve bir işaret bayt uzunlukta , o zaman bu senaryo ... veri Unicode karakterleri içerse bile hile yapmak gerekir ddyalnızca görür bayt bakılmaksızın herhangi yerel ayarı ...

Bu komut dosyası ddikili dosyayı okumak için kullanır ve "====" ayırıcısı ile ayrılmış dizeleri çıkarır ... Test verileri için sonraki komut dosyasına bakın

#   
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

çıkış

Bu komut dosyası, satır başına 3 bayt önek içeren test verileri oluşturur ...
Önek, UTF-8 kodlu tek bir Unicode karakterdir ...

# 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
#

1
Kodunuz olması gerekenden daha karmaşık görünüyor, özellikle rastgele test veri oluşturucusu. /dev/urandomÇoğu birleşimden rasgele bayt alabilirsiniz . Ve rastgele test verileri en iyi test verileri değildir, burada, sınır yerlerinde boş karakterler ve satırsonu gibi zor durumları ele aldığınızdan emin olmalısınız.
Gilles 'SO- kötü olmayı bırak'

Evet teşekkürler. / Dev / random kullanmayı düşündüm ama test veri geninin büyük bir ithalatı olmadığını düşündüm ve 'numrandom' sürücüsünü test etmek istedim (başka bir yerde bahsettiğinizde; 'num-utils'some nice özellikleri.). Cevabınıza daha yakından baktım ve hemen hemen aynı şeyi yaptığınızı fark ettim, ancak bunun daha özlü olması :) .. 3 satırda kilit noktaları belirttiğinizi fark etmemiştim! Diğer dil referanslarınıza odaklanmıştım .. Çalıştırmak iyi bir deneyimdi ve şimdi diğer dillere olan referanslarınızı daha iyi anlıyorum! \ x00 bir kabuk durdurucu olabilir
Peter.O

0

Bu sadece bir ikili dosyayı kopyalayın:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.