bashスクリプトを使用してバイナリファイルコンテンツを読み取る方法


14

文字を読み取ってから、固定長の文字列を読み取りたい(文字列はファイル内でヌル終端されておらず、その長さは前の文字で指定されている)。

bashスクリプトでこれを行うにはどうすればよいですか?後処理を実行できるように、文字列変数を定義する方法は?

回答:


19

シェルユーティリティに固執する場合は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)

ただし、これバイナリデータでは機能しません。2つの問題があります。

  • コマンド置換は、$(…)ストリップ、最終的な改行をコマンドの出力に。かなり簡単な回避策があります。出力が改行以外の文字で終わっていることを確認してから、その1文字を取り除きます。

    string=$(head -c $n; echo .); string=${string%.}
  • Bashは、ほとんどのシェルと同様に、nullバイトの処理が苦手です。bash 4.1以降、nullバイトはコマンド置換の結果から単純に削除されます。Dash 0.5.5とpdksh 5.2の動作は同じであり、ATT kshは最初のヌルバイトで読み取りを停止します。一般に、シェルとそのユーティリティは、バイナリファイルの処理を対象としていません。(Zshは例外で、nullバイトをサポートするように設計されています。)

バイナリデータがある場合は、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シェルスクリプトが常に適切とは限らない
-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 -Nnullバイトで停止するため、これはバイナリデータを処理する適切な方法ではありません。一般に、zsh以外のシェルはnullに対処できません。
ジル 'SO-悪である停止

2

シェルでバイナリファイルを処理できるようにする場合、最適なオプション(のみ?)は、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を使用)、バイト文字の10進数値として「文字列」を読み取ります。

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-マンは「元に戻すモニカ言う

2
まあ、ここでマンページをコピーできますが、要点はわかりません。ここで使用されるのは基本的なコマンドのみです。唯一のトリックはhexdumpの使用です。
クレメントムーラン

2
あなたが私の答えを好まない/理解しないので、真剣に投票するのですか?
クレメントムーラン

1

更新(後知恵付き):...この質問/回答(私の答え)は、車を追いかけ続ける犬のことを考えさせます。ある日、ついに彼は車に追いつきます。彼は本当にそれで多くのことをすることはできません...このアンサーは文字列を「キャッチ」しますが、ヌルバイトが埋め込まれていると、あなたはそれらをあまりすることができません...(Gillesの答えに大きな+1 ..別の言語がここにあるかもしれません。)

ddすべてのデータを読み取ります...確かに「長さ」としてゼロになりません...しかし、データのどこかに\ x00がある場合、その処理方法を工夫する必要があります。dd問題はありませんが、シェルスクリプトに問題があります(ただし、データをどのように処理するかによって異なります)...以下は、基本的に各「データ文字列」を、各ストリンの間に線の区切りがあるファイルに出力します...

ところで:あなたは「文字」と言うと、私は仮定し、あなたが「バイト」を意味...
しかし、単語「の文字が」唯一の7ビットASCII文字セットは、文字あたり1バイトを使用してUNICODEのこれらの日に曖昧になってきました...また、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

出口

このスクリプトは、1行に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ほとんどのユニックスからランダムバイトを取得できます。また、ランダムテストデータは最適なテストデータではありません。ここでは、境界の場所にあるヌル文字や改行などの難しいケースに対処する必要があります。
ジル 'SO-悪である停止

はい、ありがとう。/ dev / randomを使用することを考えましたが、テストデータの生成はあまり重要ではないと考えました。私はあなたの答えを詳しく調べたところ、あなたはそれがより簡潔であることを除いて、ほぼ同じことをしていることに気づきました:)。私はあなたの他の言語の参照に焦点を当てていました。それを機能させるのは良い経験でした。\ x00はシェルストッパーになる可能性がある
-Peter.O

0

これはバイナリファイルをコピーするだけです:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.