如何在Linux bash中从字符串计算crc32校验和

21

很久以前我使用 crc32 从字符串中计算校验和,但我不记得当时具体是怎么做的了。

echo -n "LongString" | crc32    # no output

我发现了一种使用 Python 计算它们的解决方案[1],但是难道没有一种直接从字符串计算它们的方法吗?


# signed
python -c 'import binascii; print binascii.crc32("LongString")'
python -c 'import zlib; print zlib.crc32("LongString")'
# unsigned
python -c 'import binascii; print binascii.crc32("LongString") % (1<<32)'
python -c 'import zlib; print zlib.crc32("LongString") % (1<<32)'

[1] 如何使用Python计算CRC32以匹配在线结果?

7个回答

32

我自己也遇到了这个问题,不想去“麻烦”安装crc32。我想出了这个方法,虽然有点不好,但在大多数平台上应该可以使用,或者至少在大多数现代Linux系统中可以使用...

echo -n "LongString" | gzip -1 -c | tail -c8 | hexdump -n4 -e '"%u"'

提供一些技术细节,gzip使用crc32在最后8个字节中,并且-c选项会导致输出到标准输出,tail会去掉最后8个字节。(-1建议由@MarkAdler,这样我们就不会浪费时间进行压缩)。 hexdump有点棘手,我必须花费一些时间来搞清楚它,但是这里的格式似乎可以正确解析gzip crc32为单个32位数:
  • -n4只取gzip页脚的前4个字节。
  • '"%u"'是您标准的fprintf格式字符串,将字节格式化为单个无符号32位整数。请注意,在此处嵌套了双引号
如果您想要十六进制校验和,可以将格式字符串更改为'"%08x"'(或者'"%08X"'表示大写十六进制),这将以8个字符(0填充)十六进制格式化校验和。
像我说的那样,这不是最优雅的解决方案,也许不是您想在性能敏感的情况下使用的方法,但是这种方法可能会吸引人们,因为所使用的命令几乎普遍。
跨平台可用性的薄弱点可能是`hexdump`配置,因为我从平台到平台看到了它的变化,并且有点棘手。如果您使用它,建议尝试一些测试值并与在线工具的结果进行比较。
编辑:如评论中@PedroGimeno所建议的那样,您可以将输出导入到`od`而不是`hexdump`中,以获得相同的结果而无需繁琐的选项。对于十六进制,使用`... | od -t x4 -N 4 -A n`,对于十进制,使用`... | od -t d4 -N 4 -A n`。

4
使用od而不是hexdump可以提供更便携的十六进制解决方案:... | od -t x4 -N 4 -A n - Pedro Gimeno
可以确认这个方法非常有效!使用-t x4输出十六进制,使用-t d4输出十进制。 - robert
2
使用 gzip -1 -c 可以使压缩更快,因为你无论如何都会丢弃它。 - Mark Adler
在一些像msys这样的平台上,你需要添加-f来强制gzip压缩... 将"gzip -1 -c"替换为"gzip -1 -f -c" - undefined

30

或者只需使用进程替代:

crc32 <(echo -n "LongString")

(编辑:thx @tor-klingberg)

1
我正在寻找这个,以便也能使用pv。它将文件作为字符串输出,同时生成进度条。crc32 <(pv /some/file) 运行得非常完美。 - George
5
如果您想让管道从左到右,可以使用echo -n "LongString" | crc32 /dev/stdin命令。/dev/stdin是一个特殊的文件,它包含了进程的输入内容。 - Tor Klingberg
只是一个建议,但可能更有意义的是执行 crc32 <(printf "LongString"),这样你就不会得到一个附加的 \n - Peter Frost

9

我使用cksum并使用shell内置的printf将其转换为十六进制:

$ echo -n "LongString"  | cksum | cut -d\  -f1 | xargs echo printf '%0X\\n' | sh
5751BDB2

cksum 命令最初出现在4.4BSD UNIX中,现代系统应该都有这个命令。


我不得不使用 cut -d" " -f1 而不是 cut -d\ -f1(SO 在这里修剪了两个空格中的一个),否则它只会报错。 - Bowi
使用参数替换而不是管道到xargs/echo/sh的方式类似: printf '%X\n' "$(echo -n "LongString" | cksum | cut -d' ' -f1)" - Paul Donohue

8
您的问题已经包含了大部分答案。
echo -n 123456789 | python -c 'import sys;import zlib;print(zlib.crc32(sys.stdin.read())%(1<<32))'

正确的结果应该是 3421780262

我更喜欢使用十六进制:

echo -n 123456789 | python -c 'import sys;import zlib;print("%08x"%(zlib.crc32(sys.stdin.read())%(1<<32)))'
cbf43926

请注意,存在多种CRC-32算法: http://reveng.sourceforge.net/crc-catalogue/all.htm#crc.cat-bits.32


有趣的是,那些列出的没有一个使用EDB88320的“ZIP”多项式。 - silverdr
@silverdr 所有 poly=0x04c11db7refin=true 的都是。那里列出的 CRC-32/ISO-HDLC 是 PKZIP CRC。 - Mark Adler
我可能漏掉了一些明显的东西,但是poly=0x04c11db7如何意味着使用edb88320?我猜这与refin=true有关?老实说,我正在寻找适应校验和例程所需的定义,并发现了(对我来说)冲突的信息。最终,我使用起始种子ffffffff和最终ffffffff EOR使用edb88320以获得与所提到的crc32脚本输出兼容的结果。 - silverdr
@silverdr 0xedb883200x04c11db7 的位反转。refin=true 表示输入位被反射。实际上,这从未被执行过,因为你必须对每个输入字节都进行操作。相反,多项式只被反射一次。 - Mark Adler
Python 3:| python3 -c 'import sys;import zlib;print("{:x}".format(zlib.crc32(sys.stdin.buffer.read())%(1<<32)))' - Jari Turkia
这个实现将整个文件读入内存,对于大文件可能会有问题。 - legolegs

7
在Ubuntu系统上,/usr/bin/crc32至少是一个短小的Perl脚本,你可以从它的源代码中清楚地看到它只能打开文件。它没有从标准输入读取的功能--它没有将-作为文件名或-c参数等特殊处理。
因此,最简单的方法是接受它,并创建一个临时文件。
tmpfile=$(mktemp)
echo -n "LongString" > "$tmpfile"
crc32 "$tmpfile"
rm -f "$tmpfile"

如果你真的不想写入文件(例如,数据量大于文件系统所能承受的情况——如果它真的是一个“长字符串”,这种可能性很小,但为了论证而言...),你可以使用命名管道。对于一个简单的非随机访问读者来说,这与文件无异:

fifo=$(mktemp -u)
mkfifo "$fifo"
echo -n "LongString" > "$fifo" &
crc32 "$fifo"
rm -f "$fifo"

请注意&以使写入fifo的进程后台运行,因为它会阻塞直到下一个命令读取它。
要更加谨慎地创建临时文件,请参考:https://unix.stackexchange.com/questions/181937/how-create-a-temporary-file-in-shell-script
或者,将脚本中的内容作为示例,编写自己的Perl一行代码(您系统上有crc32表示已安装Perl和必要的模块),或者使用您已经找到的Python一行代码。

1
这也可以通过处理先进先出队列(FIFO)来实现:crc32 <(echo -n "LongString")。 - Scott Carlson

7

以下是一个纯 Bash 实现:

#!/usr/bin/env bash

declare -i -a CRC32_LOOKUP_TABLE

__generate_crc_lookup_table() {
  local -i -r LSB_CRC32_POLY=0xEDB88320 # The CRC32 polynomal LSB order
  local -i index byte lsb
  for index in {0..255}; do
    ((byte = 255 - index))
    for _ in {0..7}; do # 8-bit lsb shift
      ((lsb = byte & 0x01, byte = ((byte >> 1) & 0x7FFFFFFF) ^ (lsb == 0 ? LSB_CRC32_POLY : 0)))
    done
    ((CRC32_LOOKUP_TABLE[index] = byte))
  done
}
__generate_crc_lookup_table
typeset -r CRC32_LOOKUP_TABLE

crc32_string() {
  [[ ${#} -eq 1 ]] || return
  local -i i byte crc=0xFFFFFFFF index
  for ((i = 0; i < ${#1}; i++)); do
    byte=$(printf '%d' "'${1:i:1}") # Get byte value of character at i
    ((index = (crc ^ byte) & 0xFF, crc = (CRC32_LOOKUP_TABLE[index] ^ (crc >> 8)) & 0xFFFFFFFF))
  done
  echo $((crc ^ 0xFFFFFFFF))
}

printf 'The CRC32 of: %s\nis: %08x\n' "${1}" "$(crc32_string "${1}")"

# crc32_string "The quick brown fox jumps over the lazy dog"
# yields 414fa339

测试:

bash ./crc32.sh "The quick brown fox jumps over the lazy dog"
The CRC32 of: The quick brown fox jumps over the lazy dog
is: 414fa339

2

2
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接