我正在尝试在bash中实现CRC16校验和。我正在从现有的C ++代码中移植。我快到了,但是得到的答案却有所不同。
我不太明白为什么C ++代码和bash脚本之间的校验和不同。
另一双眼睛会很有帮助。
这里是C ++代码:
uint16_t Encoder::checksum(std::string thestring)
{
uint8_t d, e, f;
uint16_t c, r, crccalc;
c = 0xffff;
for (unsigned int i = 0; i < thestring.length(); i++)
{
d = thestring[i];
e = c ^ d;
f = e ^ (e << 4);
r = (c >> 8) ^ (f << 8) ^ (f << 3) ^ (f >> 4);
c = r;
}
c ^= 0xffff;
crccalc = c;
return crccalc;
}
这是我的bash代码:
function calc_crc16()
{
string=$1
while read -d "" -n 1 ; do astring+=( "$reply" ) ; done <<< "$string"
cnt=${#astring[@]}
c=0xffff
for ((x=0;x<$cnt;x++)); do
char=${astring[$x]}
e=$(($c ^ $char))
s=$(($e << 4))
f=$(($e ^ $s))
t1=$(($c >> 8))
t2=$(($f << 8))
t3=$(($f << 3))
t4=$(($f >> 4))
r1=$(($t1 ^ $t2 ^ $t3 ^ $t4))
c=$r1
done
c=$c ^ 0xffff
echo "checksum = $c"
}
这与整数的大小有关吗?我猜bash对此无能为力。
我得到一个实际的数字,但它与C ++不匹配,我知道它可以正常工作。有人看到我在搞砸的地方吗?
第一个问题在顶部附近
while read -d "" -n 1 ; do astring+=( "$reply" ) ; done <<< "$string"
[$reply
是错误的,因为您没有为读取指定变量名,所以名称是$REPLY
。
下一个错误在末尾
c=$c ^ 0xffff
应该是
c=$(($c ^ 0xffff))
至少这样,它将运行无错误,正确性和适当性是另外一回事。
正确性问题:如果输入字符串中有空格怎么办?这将严重破坏。一律引用变量例外
更改
char=${astring[$x]}
到
char="${astring[$x]}"
奇怪的是,此规则在$(())
构造内部不同。在这种情况下,您的位操作应引用不带任何$
的变量
e=$(( c ^ char ))
s=$(( e << 4 ))
f=$(( e ^ s ))
t1=$(( c >> 8 ))
t2=$(( f << 8 ))
t3=$(( f << 3 ))
t4=$(( f >> 4 ))
r1=$(( t1 ^ t2 ^ t3 ^ t4))
及以后
c=$(( c ^ 0xffff ))
这将导致变量扩展,并且空格不会使内容破灭。
通常,您还应该将-r
传递给read
,其作用请参见help read
。
为什么要在处理$1
之前将其复制为数组?使用
while read -d "" -n 1 ; do astring+=( "$REPLY" ) ; done <<< "$1"
足够。
在处理之前,可能没有必要将输入转换为数组。相反,您可以在循环中从字符串中切出字符,这与C ++版本的操作更接近。替换
char="${astring[$x]}"
with
char="${1:$x:1}"
这直接在函数参数上操作;由于我们不再复制该副本,因此我们还需要以其他方式获取$cnt
cnt=${#1}
但是您确实有比这更大的问题,例如字符不是bash中的整数。要进行转换,您必须使用以下语法:
printf '%d' \'a
其中a
是要转换的字符。将其插入脚本的上下文中将是
char=$(printf '%d' \'"${1:$x:1}")
现在我们要到达某个地方,但是我真的必须请您考虑所有这些是否真的值得。即使您可以使用它,您还能获得什么?
仅供参考,这是我想出的awk脚本。
这和我拥有的C ++代码一样快,基本上是瞬时的。对于相同的字符串,bash大约需要10秒钟才能运行。 awk快得多。
function awk_calc_crc16()
{
output=$(echo $1 | awk 'function ord(c){return chmap[c];}
BEGIN{c=65535; for (i=0; i < 256; i++){ chmap[sprintf("%c", i)] = i;}}
{
split($0, chars, "");
for(i = 1; i <= length(chars); i++)
{
cval=ord(chars[i])
e=and(xor(c, ord(chars[i])), 0x00FF);
s=and(lshift(e, 4), 0x00FF);
f=and(xor(e, s), 0x00FF);
r=xor(xor(xor(rshift(c, 8), lshift(f, 8)), lshift(f, 3)), rshift(f, 4));
c=r;
}
}
END{c=xor(c, 0xFFFF); printf("%hu", c);}')
echo $output;
}
好。在Sorpigal的帮助下,我有了一个工作版本。
我怀疑这都可以在awk脚本中完成,该脚本可能运行得更快。我可以尝试下一个。
谢谢大家的帮助。我不是要在这里窃取解决方案,但我已经在研究它,并认为值得提出。
无论如何,这是一个有效的版本:
function calc_crc16()
{
while read -r -d "" -n 1 ; do astring+=( "$REPLY" ) ; done <<< "$1"
cnt=${#1}
c=65535
for ((x=0;x<$cnt;x++)); do
char=$(printf '%d' \'"${1:$x:1}")
e=$(((c ^ char) & 0x00FF))
s=$(((e << 4) & 0x00FF))
f=$(((e ^ s) & 0x00FF))
r1=$(((c >> 8) ^ (f << 8) ^ (f << 3) ^ (f >> 4)))
c=$r1
done
c=$((c ^ 0xffff))
echo "checksum = $c"
}
较短(更快)的版本变化:-不需要开始时的while循环-不需要r1-不需要cnt-对bash变量使用大写字母-char = $(printf…少一个“ \”(反斜杠)-删除了0xFF的前导0s
function calc_crc16() {
CRC=0xFFFF
for ((X=0; X<${#1}; X++)); do
CHAR=$(printf '%d' "'${1:$X:1}")
E=$(((CRC ^ CHAR) & 0xFF))
S=$(((E << 4) & 0xFF))
F=$(((E ^ S) & 0xFF))
CRC=$(((CRC >> 8) ^ (F << 8) ^ (F << 3) ^ (F >> 4)))
done
let CRC^=0xFFFF
printf "0x%X\n" $CRC
}