文字を読み取り、次に固定長の文字列を読み取りたい(文字列はファイルでnullで終了しておらず、その長さは前の文字で指定されている)。
Bashスクリプトでこれを行うにはどうすればよいですか?文字列変数を定義して後処理を行うにはどうすればよいですか?
シェルユーティリティを使い続ける場合は、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 bytesの処理が苦手です。 bash 4.1以降、nullバイトはコマンド置換の結果から削除されるだけです。 Dash 0.5.5とpdksh 5.2は同じ動作をし、ATT kshは最初のnullバイトで読み取りを停止します。一般に、シェルとそのユーティリティは、バイナリファイルの処理に向いていません。 (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
'
_
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
シェルでバイナリファイルを処理できるようにする場合、最適なオプション(のみ?)は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
更新(後から):...この質問/回答(私の回答)は、車を追い続ける犬を思い起こさせます..ある日、ついに、彼は車に追いつきます。わかりました、彼はそれをキャッチしましたが、彼は本当にそれを使って多くを行うことはできません...このanserは文字列を「キャッチ」しますが、nullバイトが埋め込まれている場合、それらを使用して多くを行うことはできません... ..ここでは別の言語が使用されている場合があります。)
dd
はすべてのデータを読み取ります...確かにゼロで「長さ」とは言えません...しかし、データ内のどこかに\ x00がある場合、 creativeである必要があります。 dd
には問題はありませんが、シェルスクリプトに問題があります(ただし、データをどのように処理するかによって異なります)...以下は基本的に各「データ文字列」をファイルに出力します各筋の間のライン分割線...
ところで:あなたは「文字」と言います、そして私assumeは「バイト」を意味します...
しかし、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
は、ロケール設定に関係なく、bytesのみを参照します。 。
このスクリプトは、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
#
これはバイナリファイルをコピーするだけです:
while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"