初歩のシェルスクリプトで遊ぶ[head,tailコマンドでバイナリを切る]
調べものには使えません。
Ubuntu18.04,dash
odコマンドの偽物
headコマンド、tailコマンドで1バイト単位でファイルを読む、というか切り出せるか試すために、odコマンドの偽物を作りました。
$ echo あいうえお | ./odFake_01.sh e3 81 82 e3 81 84 e3 81 86 e3 81 88 e3 81 8a 0a
↑こんな感じで。標準入力から、文字や画像データやらを流し込むと、16進数が出力されます。「xxd -r -p」でバイナリに変換すると、元のデータに戻る。
「00」~「ff」まで含んだ画像ファイルも、切り出すことはできるみたい。「00」を変数に入れると怒られますけどね。
標準入力から入れる仕様にしてますが、データが入ってきたら、すぐファイルに落としてます。
スクリプトに「24秒」など書いてあるのは、テストに使った6kバイトのPNG画像を処理するのにかかった時間です。
めっちゃくっちゃ遅い。遅いというか、余計な処理をしすぎだよな。
#!/bin/sh : << '#--------------------------------------------------------------comment' ■ニセodコマンド-head,tail,ddコマンドとsumコマンドで headコマンドとteilコマンドの「--bytes=」オプションで、狙った1バイトを切り出すテスト。 ついでに普通にできるだろうけど、ddでも。 ▼処理 headコマンドで「00」バイトを含むバイナリファイルを切り出す。 1ループ目) 1 2ループ目) 1-2 3ループ目) 1-3 ・・・・・・・・・・ 100ループ目) 1-100 続けてtailコマンドで、最終1バイトのみ切り出す。 1_01〜1_06のスクリプトは、ほぼ同じ動作をする。 このうち、使うスクリプトを一つだけ有効にする。 ▼1バイトのデータの中身の判定方法 「sum」コマンドに渡すと、データの内容と同一の10進数が得られるようだ。 理屈は知らん。 ▼出力 od -An -tx1 と、ほぼ同様。 -sample----------------------------------------------- $ echo あいうえお | ./odFake_01.sh e3 81 82 e3 81 84 e3 81 86 e3 81 88 e3 81 8a 0a ------------------------------------------------------ ▼つかいかた cat files.png | odFake_01.sh 標準入力にのみ対応する。 最大で10000バイトに制限される。 標準入力のデータを、一時ファイルに作成してから処理する。 共通部分の「exit 0」をコメントアウトすると、入力制限はなくなる。 #--------------------------------------------------------------comment # 共通部分 odFake_1_comp1(){ stdinFile="`mktemp`" filesize=`tee "${stdinFile}" | wc -c` trap 'rm "${stdinFile}" 2>/dev/null ' 0 1 2 3 9 15 if test ${filesize} -gt 10000 ; then exit 0 true fi return 0 } # ループ内でほぼ全ての処理を済ませる。 # 処理がわかりやすいが、遅い。 odFake_1_01(){ #24秒 for byte in `seq 1 ${filesize}` ;do byteDec=`0< "${stdinFile}" head --bytes="${byte}" | tail --bytes=1 | sum | sed 's/0\{1,4\}//;s/[0-9]$//' ` printf " %02x" ${byteDec} done | fold -w 48 ;echo echo "odFake_1_01 終了" 1>&2 return 0 } # ループ内の処理を少なくして、後段はパイプで繋げた。 # 後段はsumコマンドの出力を16進数に変換して整形するだけだが、わかりにくい。 odFake_1_02(){ #13秒 for byte in `seq 1 ${filesize}` ;do 0< "${stdinFile}" head --bytes="${byte}" | tail --bytes=1 | sum done | cut -d " " -f 1 | xargs -I @@ echo "obase=16;ibase=10;@@" | bc | sed "s/[0-9A-F][0-9A-F]/& /;s/[0-9A-F]$/0&/" | paste --delimiters=" " --serial | tr --squeeze-repeats " " | fold --width=48 --spaces echo "odFake_1_02 終了" 1>&2 return 0 } : << '#--------------------------------------------------------------comment' 前段のforループは最小限にし、sedにパイプでつないだ。 sedはループ外で、1行1バイト分のsumコマンドの出力を、処理し続ける。 さらにパイプで繋いで、後段にwhileループを作った。 全てパイプにするより、小細工しやすいし、速い。 readするときに変数を2つ用意することで、後半の不要な文字列を捨てることができる。 #--------------------------------------------------------------comment odFake_1_03(){ #11秒 outputByteCount="" for byte in `seq 1 ${filesize}` ;do 0< "${stdinFile}" head --bytes="${byte}" | tail --bytes=1 | sum done | stdbuf -o 4k sed 's/0\{1,4\}//' | ( while read byteDec n ;do outputByteCount=${outputByteCount}@ printf " %02x" ${byteDec} if test _${outputByteCount} = _@@@@@@@@@@@@@@@@ ;then echo outputByteCount="" fi done if test _${outputByteCount} != _ ;then echo fi ) echo "odFake_1_03 終了" 1>&2 return 0 } # tailコマンドを主に使った版 # 中身は 1_03 とほぼ同じ odFake_1_04(){ #11秒 outputByteCount="" for byte in `seq 1 ${filesize} | tac` ;do 0< "${stdinFile}" tail --bytes="${byte}" | head --bytes=1 | sum done | stdbuf -o 4k sed 's/0\{1,4\}//' | ( while read byteDec n ;do outputByteCount=${outputByteCount}@ printf " %02x" ${byteDec} if test _${outputByteCount} = _@@@@@@@@@@@@@@@@ ;then echo outputByteCount="" fi done if test _${outputByteCount} != _ ;then echo fi ) echo "odFake_1_04 終了" 1>&2 return 0 } # ddコマンド版 # ddなら単独で任意の1バイトを切り出せる。 odFake_1_05(){ #07秒 outputByteCount="" for byte in `seq 0 $((filesize-1))` ;do 0< "${stdinFile}" dd ibs=1 count=1 skip="${byte}" 2>/dev/null | sum done | stdbuf -o 4k sed 's/0\{1,4\}//' | ( while read byteDec n ;do outputByteCount=${outputByteCount}@ printf " %02x" ${byteDec} if test _${outputByteCount} = _@@@@@@@@@@@@@@@@ ;then echo outputByteCount="" fi done if test _${outputByteCount} != _ ;then echo fi ) echo "odFake_1_05 終了" 1>&2 return 0 } : << '#--------------------------------------------------------------comment' ・ddコマンド別パターン ddコマンドは、たとえば100バイトのファイルの101バイト目を切り出すと、出力が無い。 「出力なし」をsumコマンドに通すと、データが「00000 0」になり、ヌルバイトとも区別できる。 ループの終了判定が容易。 head,tailコマンドでの処理を、例えば4バイトのデータで試すと、4バイト目と5バイト目が同じ結果になる。 切り出した1バイトだけ見ても、ファイルの終わりがわからない。 $ echo -n 0123 | od -An -tx1 30 31 32 33 $ echo -n 0123 | head -c4 | tail -c1 | od -An -tx1 33 $ echo -n 0123 | head -c5 | tail -c1 | od -An -tx1 33 #--------------------------------------------------------------comment odFake_1_06(){ #12秒 outputByteCount="" totalByteCount="0" sumResult="" while true ;do sumResult="`0< "${stdinFile}" dd ibs=1 count=1 skip="${totalByteCount}" 2>/dev/null | sum `" if test "${sumResult}" = "00000 0" ;then break ; #echoする前にループを抜ける fi echo ${sumResult} totalByteCount=$((totalByteCount+1)) done | stdbuf -o 4k sed 's/0\{1,4\}//' | ( while read byteDec n ;do outputByteCount=${outputByteCount}@ printf " %02x" ${byteDec} if test _${outputByteCount} = _@@@@@@@@@@@@@@@@ ;then echo outputByteCount="" fi done if test _${outputByteCount} != _ ;then echo fi ) echo "odFake_1_06 終了" 1>&2 return 0 } odFake_1_comp1 # コメントアウトしてはいけない # 以下のうち、1つだけ有効にする #odFake_1_01 #odFake_1_02 #odFake_1_03 #odFake_1_04 odFake_1_05 #odFake_1_06 exit 0 ;