初歩のシェルスクリプトで遊ぶ[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 ;