指定したファイルの最初のバイト、次に 2 番目のバイト、3 番目などを読み取る必要があります。どうすればBASHでできますか? PSこのバイトのHEXを取得する必要があります
7 に答える
完全な書き直し: 2019 年 9 月!
以前のバージョンよりもはるかに短くて簡単です! (何か高速ですが、それほどではありません)
はい、bashはバイナリを読み書きできます。
構文:
LANG=C IFS= read -r -d '' -n 1 foo
$foo
1 バイナリ バイトが入力されます。残念ながら、bash 文字列はヌル バイト ($) を保持できないため、 1 バイトを 1 回\0
読み取る必要があります。
しかし、バイト読み取りの値については、これを見逃していましたman bash
(この下部にある2016年の投稿を見てください):
printf [-v var] format [arguments] ... Arguments to non-string format specifiers are treated as C constants, except that ..., and if the leading character is a single or double quote, the value is the ASCII value of the following character.
そう:
read8() {
local _r8_var=${1:-OUTBIN} _r8_car LANG=C IFS=
read -r -d '' -n 1 _r8_car
printf -v $_r8_var %d "'"$_r8_car
}
送信された変数名 (デフォルトは$OUTBIN
) に、STDIN の最初のバイトの 10 進数の ASCII 値を入力します
read16() {
local _r16_var=${1:-OUTBIN} _r16_lb _r16_hb
read8 _r16_lb &&
read8 _r16_hb
printf -v $_r16_var %d $(( _r16_hb<<8 | _r16_lb ))
}
送信された変数名 (デフォルトは$OUTBIN
) に、STDIN からの最初の 16 ビット ワードの 10 進値が入力されます...
もちろん、Endiannessを切り替えるには、次のように切り替える必要があります。
read8 _r16_hb &&
read8 _r16_lb
等々:
# Usage:
# read[8|16|32|64] [varname] < binaryStdInput
read8() { local _r8_var=${1:-OUTBIN} _r8_car LANG=C IFS=
read -r -d '' -n 1 _r8_car
printf -v $_r8_var %d "'"$_r8_car ;}
read16() { local _r16_var=${1:-OUTBIN} _r16_lb _r16_hb
read8 _r16_lb && read8 _r16_hb
printf -v $_r16_var %d $(( _r16_hb<<8 | _r16_lb )) ;}
read32() { local _r32_var=${1:-OUTBIN} _r32_lw _r32_hw
read16 _r32_lw && read16 _r32_hw
printf -v $_r32_var %d $(( _r32_hw<<16| _r32_lw )) ;}
read64() { local _r64_var=${1:-OUTBIN} _r64_ll _r64_hl
read32 _r64_ll && read32 _r64_hl
printf -v $_r64_var %d $(( _r64_hl<<32| _r64_ll )) ;}
これを行うことができsource
ます。パーティション分割されて/dev/sda
いる場合は、gpt
read totsize < <(blockdev --getsz /dev/sda)
read64 gptbackup < <(dd if=/dev/sda bs=8 skip=68 count=1 2>/dev/null)
echo $((totsize-gptbackup))
1
答えは次のようになります1
(最初の GPT はセクター 1 にあり、1 セクターは 512 バイトです。GPT バックアップの場所はバイト 32 にあります。512 bs=8
-> 64 + 32 -> 4 = 544 -> 68 ブロックをスキップします... GUID パーティション テーブルを参照してくださいウィキペディアで)。
クイック小書き込み機能...
write () {
local i=$((${2:-64}/8)) o= v r
r=$((i-1))
for ((;i--;)) {
printf -vv '\%03o' $(( ($1>>8*(0${3+-1}?i:r-i))&255 ))
o+=$v
}
printf "$o"
}
この関数のデフォルトは 64 ビット、リトルエンディアンです。
Usage: write <integer> [bits:64|32|16|8] [switchto big endian]
- 2 つのパラメーターを使用する場合、2 番目のパラメーターは、生成される出力のビット長である
8
、16
、32
またはのいずれかでなければなりません。64
- ダミーの 3 番目のパラメーター (空の文字列であっても) を使用すると、関数はビッグ エンディアンに切り替わります。
.
read64 foo < <(write -12345);echo $foo
-12345
...
2015年初投稿…
特定の bash バージョンを追加するためのアップグレード (バシズム付き)
printf
ビルトインの新しいバージョンを使用すると、フォーク ( $(...)
) しなくても多くのことを実行できるため、スクリプトが大幅に高速化されます。
最初に ( と を使用seq
して) hd 出力sed
を解析する方法を見てみましょう。
echo ;sed <(seq -f %02g 0 $(( COLUMNS-1 )) ) -ne '
/0$/{s/^\(.*\)0$/\o0337\o033[A\1\o03380/;H;};
/[1-9]$/{s/^.*\(.\)/\1/;H};
${x;s/\n//g;p}';hd < <(echo Hello good world!)
0 1 2 3 4 5 6 7
012345678901234567890123456789012345678901234567890123456789012345678901234567
00000000 48 65 6c 6c 6f 20 67 6f 6f 64 20 77 6f 72 6c 64 |Hello good world|
00000010 21 0a |!.|
00000012
16 進数の部分が 10 列目で始まり、56 列目で終わり、3 文字間隔で配置され、34 列目に余分なスペースが 1 つあります。
したがって、これを解析するには、次のようにします。
while read line ;do
for x in ${line:10:48};do
printf -v x \\%o 0x$x
printf $x
done
done < <( ls -l --color | hd )
古い元の投稿
16進数の編集2、使用できますhd
echo Hello world | hd
00000000 48 65 6c 6c 6f 20 77 6f 72 6c 64 0a |Hello world.|
またod
echo Hello world | od -t x1 -t c
0000000 48 65 6c 6c 6f 20 77 6f 72 6c 64 0a
H e l l o w o r l d \n
まもなく
while IFS= read -r -n1 car;do [ "$car" ] && echo -n "$car" || echo ; done
それらを試してください:
while IFS= read -rn1 c;do [ "$c" ]&&echo -n "$c"||echo;done < <(ls -l --color)
説明:
while IFS= read -rn1 car # unset InputFieldSeparator so read every chars
do [ "$car" ] && # Test if there is ``something''?
echo -n "$car" || # then echo them
echo # Else, there is an end-of-line, so print one
done
編集; 質問が編集されました: 16 進値が必要!?
od -An -t x1 | while read line;do for char in $line;do echo $char;done ;done
デモ:
od -An -t x1 < <(ls -l --color ) | # Translate binary to 1 byte hex
while read line;do # Read line of HEX pairs
for char in $line;do # For each pair
printf "\x$char" # Print translate HEX to binary
done
done
デモ 2: 16 進数と 2 進数の両方があります
od -An -t x1 < <(ls -l --color ) | # Translate binary to 1 byte hex
while read line;do # Read line of HEX pairs
for char in $line;do # For each pair
bin="$(printf "\x$char")" # translate HEX to binary
dec=$(printf "%d" 0x$char) # translate to decimal
[ $dec -lt 32 ] || # if caracter not printable
( [ $dec -gt 128 ] && # change bin to a single dot.
[ $dec -lt 160 ] ) && bin="."
str="$str$bin"
echo -n $char \ # Print HEX value and a space
((i++)) # count printed values
if [ $i -gt 15 ] ;then
i=0
echo " - $str"
str=""
fi
done
done
2016 年 9 月の新しい投稿:
これは、非常に特殊なケースで役立つ可能性があります (マウントせずに、低レベルで 2 つのディスク間で GPT パーティションを手動でコピーするために使用しました/usr
...)
はい、bash はバイナリを読み取ることができました!
... しかし、1 バイトずつ、1 つずつ... (`char(0)' を正しく読み取ることができなかったため、それらを正しく読み取る唯一の方法はend-of-fileを考慮することです。ここで、文字が読み取られない場合ファイルの終わりに達していない場合、読み取られた文字は char(0) です)。
これは、信頼できる便利なツールというよりは概念実証です。(hexdump)の純粋なbashバージョンがあります。hd
これは、最近のbashismsを下bash v4.3
または上で使用します。
#!/bin/bash
printf -v ascii \\%o {32..126}
printf -v ascii "$ascii"
printf -v cntrl %-20sE abtnvfr
values=()
todisplay=
address=0
printf -v fmt8 %8s
fmt8=${fmt8// / %02x}
while LANG=C IFS= read -r -d '' -n 1 char ;do
if [ "$char" ] ;then
printf -v char "%q" "$char"
((${#char}==1)) && todisplay+=$char || todisplay+=.
case ${#char} in
1|2 ) char=${ascii%$char*};values+=($((${#char}+32)));;
7 ) char=${char#*\'\\};values+=($((8#${char%\'})));;
5 ) char=${char#*\'\\};char=${cntrl%${char%\'}*};
values+=($((${#char}+7)));;
* ) echo >&2 ERROR: $char;;
esac
else
values+=(0)
fi
if [ ${#values[@]} -gt 15 ] ;then
printf "%08x $fmt8 $fmt8 |%s|\n" $address ${values[@]} "$todisplay"
((address+=16))
values=() todisplay=
fi
done
if [ "$values" ] ;then
((${#values[@]}>8))&&fmt="$fmt8 ${fmt8:0:(${#values[@]}%8)*5}"||
fmt="${fmt8:0:${#values[@]}*5}"
printf "%08x $fmt%$((
50-${#values[@]}*3-(${#values[@]}>8?1:0)
))s |%s|\n" $address ${values[@]} ''""'' "$todisplay"
fi
printf "%08x (%d chars read.)\n" $((address+${#values[@]})){,}
これを試す/使用することはできますが、パフォーマンスを比較しようとしないでください。
time hd < <(seq 1 10000|gzip)|wc
1415 25480 111711
real 0m0.020s
user 0m0.008s
sys 0m0.000s
time ./hex.sh < <(seq 1 10000|gzip)|wc
1415 25452 111669
real 0m2.636s
user 0m2.496s
sys 0m0.048s
同じ仕事:hd
20ミリ秒対私の2000ミリ秒bash script
。
...しかし、ファイルヘッダーまたはハードドライブのセクターアドレスでさえ4バイトを読み取りたい場合、これでうまくいく可能性があります...
試しましたxxd
か?必要に応じて、16進ダンプを直接提供します..
あなたの場合、コマンドは次のようになります。
xxd -c 1 /path/to/input_file | while read offset hex char; do
#Do something with $hex
done
注: 行を読み取っている間ではなく、16 進数から文字を抽出します。read は空白を適切にキャプチャしないため、これが必要です。
単一の文字を使用 read
すると、次のように一度に読み取ることができます。
read -n 1 c
echo $c
[答え]
これを試して:
#!/bin/bash
# data file
INPUT=/path/to/input.txt
# while loop
while IFS= read -r -n1 char
do
# display one character at a time
echo "$char"
done < "$INPUT"
このリンクから
2 番目の方法、 Using awk
、char ごとにループする
awk '{for(i=1;i<=length;i++) print substr($0, i, 1)}' /home/cscape/Desktop/table2.sql
第三の方法、
$ fold -1 /home/cscape/Desktop/table.sql | awk '{print $0}'
EDIT : 各文字をHEX
数値として出力するには:
ファイル名があるとしますfile
:
$ cat file
123A3445F
から char ごとに読み取り、に出力するawk
スクリプト ( ) を作成しました。 named x.awk
file
HEX
$ cat x.awk
#!/bin/awk -f
BEGIN { _ord_init() }
function _ord_init( low, high, i, t)
{
low = sprintf("%c", 7) # BEL is ascii 7
if (low == "\a") { # regular ascii
low = 0
high = 127
} else if (sprintf("%c", 128 + 7) == "\a") {
# ascii, mark parity
low = 128
high = 255
} else { # ebcdic(!)
low = 0
high = 255
}
for (i = low; i <= high; i++) {
t = sprintf("%c", i)
_ord_[t] = i
}
}
function ord(str, c)
{
# only first character is of interest
c = substr(str, 1, 1)
return _ord_[c]
}
function chr(c)
{
# force c to be numeric by adding 0
return sprintf("%c", c + 0)
}
{ x=$0; printf("%s , %x\n",$0, ord(x) )}
このスクリプトを作成するために、私はawk-documentation
を使用しました。これで、このawk
スクリプトを次のように作業に 使用できます。
$ fold -1 /home/cscape/Desktop/file | awk -f x.awk
1 , 31
2 , 32
3 , 33
A , 41
3 , 33
4 , 34
4 , 34
5 , 35
F , 46
注:A
値は41
16 進数です。10 進数で出力するには、スクリプトの最後の行を に変更%x
します。 %d
x.awk
試してみる!!
head、tail、およびprintfを使用したさらに別のソリューション:
for a in $( seq $( cat file.txt | wc -c ) ) ; do cat file.txt | head -c$a | tail -c1 | xargs -0 -I{} printf '%s %0X\n' {} "'{}" ; done
より読みやすい:
#!/bin/bash
function usage() {
echo "Need file with size > 0"
exit 1
}
test -s "$1" || usage
for a in $( seq $( cat $1 | wc -c ) )
do
cat $1 | head -c$a | tail -c1 | \
xargs -0 -I{} printf '%c %#02x\n' {} "'{}"
done
Perleone自身の投稿を拡張したかったのですが(それが彼の基本的なコンセプトだったので!)、私の編集は結局拒否され、これは別の回答として投稿するように親切にアドバイスされました. 十分に公平なので、そうします。
ペルレオーネのオリジナル スクリプトの改良に関する考慮事項:
seq
ここでは完全にやり過ぎです。(同様に単純な)カウンター変数として使用される単純なwhile
ループa
は、うまく機能します(そしてはるかに高速です)。- 最大値は変数に割り当てる
$(cat $1 | wc -c)
必要があります。そうしないと、毎回再計算され、この代替スクリプトの実行が元のスクリプトよりもさらに遅くなります。 - 単純な使用情報行で関数を無駄にする必要はありません。
{ }
ただし、2 つのコマンドを囲む (必須の) 中かっこについて知っておく必要がexit 1
あります。(最後の注意:( )
これも機能しますが、同じ方法ではありません! 括弧はsubshellを生成しますが、中括弧は現在のシェルでその中のコマンドを実行します。)
#!/bin/bash
test -s "$1" || { echo "Need a file with size greater than 0!"; exit 1; }
a=0
max=$(cat $1 | wc -c)
while [[ $((++a)) -lt $max ]]; do
cat $1 | head -c$a | tail -c1 | \
xargs -0 -I{} printf '%c %#02x\n' {} "'{}"
done
オプションで使用read
し-n
ます。
while read -n 1 ch; do
echo $ch
done < moemoe.txt