编码和解码字符串
printf '%s' 'héllo wörld' | base64 # aMOpbGxvIHfDtnJsZA==
printf '%s' 'aMOpbGxvIHfDtnJsZA==' | base64 -d # héllo wörld
base64 编码的是字节,所以结果取决于终端的编码。在任何现代 Linux 或 macOS 环境中,这都是 UTF-8。
最经典的错误是用 echo,它会在末尾追加一个换行符:
echo 'héllo wörld' | base64 # aMOpbGxvIHfDtnJsZAo= (note the "o=")
echo -n hi | base64 # aGk=
echo -n 在 Bash 中有效,但并非所有 sh 都支持;printf '%s' 才是可移植的写法。here-string(base64 <<< "text")同样会追加换行符。如果有人发给你的 Base64 以 Cg== 或 o= 结尾,里面很可能带着一个结尾换行符,把它粘贴到 Base64 解码器 里看看就知道了。
GNU(Linux)与 BSD(macOS)的参数差异
| 任务 | GNU coreutils(Linux) | macOS |
|---|---|---|
| 解码 | -d、--decode | -D、--decode(新版本也支持 -d) |
| 不换行 | -w 0 | 默认行为 |
| 每 N 个字符换行 | -w N(默认 76) | -b N |
| 输入文件 | base64 file | base64 -i file 或 base64 < file |
| 输出文件 | 用 > 重定向 | -o file 或重定向 |
| 解码时忽略无关字符 | -i、--ignore-garbage | 不支持 |
两个 -i 参数的含义完全不同:在 macOS 上它指定输入文件,在 Linux 上它忽略无关字符。可移植的脚本应该从 stdin 读取、向 stdout 写出:
base64 < in.bin | tr -d '\n' > out.b64 # same output on Linux and macOS
base64 --decode < out.b64 > in-copy.bin
GNU base64 默认在 76 个字符处换行,这会破坏 HTTP 头和环境变量:
head -c 60 /dev/zero | tr '\0' x | base64 -w 0
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
不加 -w 0 时,第一行是 76 个字符,其余部分换到第二行。如果你需要 MIME 风格的分行,默认的换行正合适。
URL 安全的 Base64
GNU coreutils 8.31 及更高版本自带 basenc,支持 Base64URL,并且会保留填充:
printf '%s' '<<???>>' | basenc --base64url # PDw_Pz8-Pg==
printf '%s' 'PDw_Pz8-Pg' | basenc --base64url -d # <<???>>
在 macOS 或较旧的 Linux 上,用 tr 替换字符:
printf '%s' '<<???>>' | base64 | tr '+/' '-_' | tr -d '=' # PDw_Pz8-Pg
解码时需要反向替换;对于严格的解码器,还要把填充补回来:
s=PDw_Pz8-Pg
s=$(printf '%s' "$s" | tr -- '-_' '+/')
while [ $(( ${#s} % 4 )) -ne 0 ]; do s="$s="; done
printf '%s' "$s" | base64 -d # <<???>>
-- 用来阻止 tr 把 -_ 当成选项。JWT 需要的也正是这种转换,JWT 解码器 会帮你自动处理。更多内容见 Base64 与 Base64URL 的区别。
编码和解码文件
base64 -w 0 hello.txt # aGVsbG8K
base64 -d <<< 'aGVsbG8K' > copy.txt
base64 -d payload.b64 > payload.pdf
base64 是流式处理的,它按块读写,所以文件大小无所谓。为图片生成 Data URI:
printf 'data:image/png;base64,%s' "$(base64 -w 0 logo.png)"
生成 HTTP Basic Auth 请求头:
printf 'Basic %s' "$(printf '%s' 'user:pass' | base64)" # Basic dXNlcjpwYXNz
Basic Auth 请求头生成器 也能做同样的事,而且不会在 shell 历史记录里留下你的密码。
校验、错误和退出码
GNU base64 -d 发现无效字符时,会在 stderr 上打印 base64: invalid input,并以状态码 1 退出。但它仍会写出到那一刻为止解码出的所有内容,因此解码失败可能会留下一个不完整的文件:
printf 'aGk=!!' | base64 -d # prints "hi", then: base64: invalid input
echo $? # 1 (without a pipe after it)
printf 'aGk=!!' | base64 -d -i # hi (garbage ignored, exit 0)
coreutils 9.7 下的结果:
| 输入 | 结果 |
|---|---|
aGk | hi,退出码 0(容忍缺失的填充) |
"aGVs\nbG8K" | hello,退出码 0(忽略换行) |
aGl= | hi,然后 invalid input,退出码 1 |
a | invalid input,退出码 1 |
PDw_Pz8-Pg== | 部分输出,invalid input,退出码 1 |
用退出码来做校验,并先解码到临时文件,这样失败时就不会覆盖好的数据:
if printf '%s' "$b64" | base64 -d > /tmp/out.bin 2>/dev/null; then
mv /tmp/out.bin result.bin
else
echo "invalid Base64" >&2
fi
在管道中,$? 是最后一条命令的状态。如果 base64 -d 不在最后,请加上 set -o pipefail。从日志或邮件中复制的字符串解不出来时,修复 Base64 工具会去掉引号、空白和前缀;错误指南 逐一解释了每种失败。
命令行中的其他工具
openssl base64几乎在所有系统上都有,在 Linux 和 macOS 上表现一致。见 用 OpenSSL 处理 Base64。- 没有 coreutils 的地方可以用
python3 -m base64 -d file和python3 -m base64 file,输出每 76 个字符换行。 - 在 Kubernetes 中,常见写法是
kubectl get secret x -o jsonpath='{.data.password}' | base64 -d,也可以用 Kubernetes Secret 解码器。
常见问题
为什么 echo hello | base64 的结果和在线工具不一样?
echo 会追加一个换行符,所以你编码的是 hello\n(aGVsbG8K)而不是 hello(aGVsbG8=)。请用 printf '%s' hello 或 echo -n hello。
怎样让 base64 输出不换行?
Linux 上用 base64 -w 0。macOS 默认就不换行。想让同一条命令在两边表现一致,就再通过管道接一个 tr -d '\n'。
base64: invalid input 是什么意思?
GNU base64 -d 遇到了字母表之外的字符或损坏的填充。它会先输出已经解码的字节,再打印这条错误,并以状态码 1 退出。常见原因是 URL 安全的 - 和 _、引号以及 data: 前缀。
macOS 上的 base64 -d 和 Linux 上的一样吗?
较旧的 macOS 版本只认 -D(或 --decode),新版本也接受 -d。需要在两边都能运行的脚本里,请用 --decode。