如何使用bash脚本读取二进制文件内容?


15

我想读取一个字符,然后读取固定长度的字符串(该字符串在文件中不为null终止,并且其长度由前面的字符给定)。

如何在bash脚本中执行此操作?如何定义字符串变量,以便可以对其进行一些后处理?

Answers:


19

如果要坚持使用shell实用程序,则可以使用head提取多个字节,od并将字节转换为数字。

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)

但是,这不适用于二进制数据。有两个问题:

  • 命令替换$(…)最终换行符在命令输出。有一个相当简单的解决方法:确保输出以换行符以外的字符结尾,然后剥离该字符。

    string=$(head -c $n; echo .); string=${string%.}
  • 与大多数shell一样,Bash在处理null字节方面很不好。从bash 4.1开始,仅将空字节从命令替换的结果中删除。破折号0.5.5和pdksh 5.2具有相同的行为,并且ATT ksh在第一个空字节处停止读取。通常,shell及其实用程序不适合处理二进制文件。(Zsh是例外,它被设计为支持空字节。)

如果您有二进制数据,则需要切换到Perl或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
'

+1 Shell脚本并不总是合适
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 -N在空字节处停止,因此这不是处理二进制数据的合适方法。通常,除zsh之外的其他shell无法处理空值。
吉尔(Gilles)“所以,别再邪恶了”

2

如果您希望能够在Shell中处理二进制文件,最好的选择(仅?)是使用hexdump工具。

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

只读X个字节:

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

读取长度(并以0作为长度),然后读取“字符串”作为字节的十进制值:

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

您不仅可以提供一堆命令,还可以解释它们的作用和工作方式吗?选项是什么意思?用户可以从您的命令中得到什么输出?请不要在评论中回复;编辑  您的答案,使其更清晰,更完整。
G-Man说'Resstate Monica''Sep

2
好吧,我可以在这里复制联机帮助页,但我看不到重点。这里仅使用基本命令,唯一的技巧是hexdump的用法。
克莱门特红磨坊- SimpleRezo

2
因为您不喜欢/不理解我的回答而认真投票吗?
克莱门特红磨坊- SimpleRezo

1

更新(事后看来):...这个问题/答案(我的回答)使我想到了不断追赶汽车的狗。。有一天,他终于赶上了汽车。他真的不能做太多...这个分析器“捕获”了字符串,但是如果它们嵌入了空字节,那么您就不能做很多事...(所以Gilles的答案是+1) ..另一种语言可能在这里。)

dd读取所有数据……它肯定不会以“长度”为零。但是,如果数据中的任何位置都有\ x00,则需要创造性地处理它;dd没有任何问题,但是您的shell脚本会出现问题(但这取决于您要对数据执行的操作)...下面基本上将每个“数据字符串”输出到一个文件,每个文件之间都带有一个行分隔符...

顺便说一句:您说的是“字符”,我假设您的意思是“字节” ...
但是在UNICODE的今天,单词“ character”已经变得模棱两可,其中只有7位ASCII字符集每个字符使用一个字节甚至在Unicode系统中,字节数也取决于编码字符的方法,例如。UTF-8,UTF-16等

这是一个简单的脚本,用于突出显示文本“字符”和字节之间的区别。

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)

如果您的长度字符是1个字节长并且表示一个字节长度,那么即使数据包含Unicode字符,此脚本也可以解决问题... 无论任何语言环境设置如何,它都dd只能看到字节 ...

该脚本用于dd读取二进制文件,并输出由“ ====”分隔符分隔的字符串。有关测试数据,请参见下一个脚本。

#   
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

出口

该脚本构建的测试数据每行包含一个3字节的前缀...
该前缀是单个UTF-8编码的Unicode字符...

# 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
您的代码看起来比应该的复杂,尤其是随机测试数据生成器。您可以从/dev/urandom大多数unices 获得随机字节。而且随机测试数据不是最佳测试数据,您应确保解决一些困难的情况,例如此处的空字符和边界位置的换行符。
吉尔(Gilles)“所以,别再邪恶了”,

是啊谢谢。我曾考虑使用/ dev / random,但认为测试数据生成没有太大意义,我想测试驱动器“ numrandom”(您在其他地方提到过;“ num-utils”有一些不错的功能。)。我仔细看了一下您的答案,意识到您在做几乎相同的事情,只是它更加简洁:) ..我没有注意到您在3行中指出了要点!我专注于您的其他语言参考。.使它正常工作是一个很好的经验,现在我更好地理解了您对其他语言的参考!\ x00可能是一个停止器
Peter.O 2011年

0

这只是复制一个二进制文件:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.