You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Bash进程替换在JDK11 keytool可用却在JDK17报错?

问题现象
  • JDK11环境下执行Bash进程替换命令读取证书,可正常输出预期的证书详情:
/opt/jdk11/bin/keytool -printcert -v -file <(cat mycert.pem)
  • 完全相同的进程替换命令切换到JDK17环境执行时,抛出Illegal seek异常,异常堆栈如下:
keytool error: java.io.IOException: Illegal seek
java.io.IOException: Illegal seek
    at java.base/java.io.FileInputStream.position0(Native Method)
    at java.base/java.io.FileInputStream.position(FileInputStream.java:363)
    at java.base/java.io.FileInputStream.readAllBytes(FileInputStream.java:281)
    at java.base/sun.security.tools.keytool.Main.generateCertificates(Main.java:2804)
    at java.base/sun.security.tools.keytool.Main.printCertFromStream(Main.java:2762)
    at java.base/sun.security.tools.keytool.Main.doPrintCert(Main.java:2961)
    at java.base/sun.security.tools.keytool.Main.doCommands(Main.java:1286)
    at java.base/sun.security.tools.keytool.Main.run(Main.java:415)
    at java.base/sun.security.tools.keytool.Main.main(Main.java:408)
  • JDK17环境下直接传入证书文件路径,命令可正常运行:
/opt/jdk17/bin/keytool -printcert -v -file mycert.pem

解答

1、Bash层面的修复方案

Bash进程替换<(command)生成的是命名管道(FIFO)类型的虚拟文件,属于不支持随机寻址的流式文件,本身不支持seek系统调用,针对这个特性有以下可直接生效的解决方案:

  • 优先使用标准输入传递证书内容,避免keytool直接打开管道文件,命令写法如下:
cat mycert.pem | /opt/jdk17/bin/keytool -printcert -v -file /dev/stdin
  • 如果场景要求必须通过-file参数传递实体文件路径,可以先将进程替换的输出写入临时普通文件,传入临时文件路径执行命令后再清理临时文件即可。
  • 也可以使用进程替换搭配输入重定向的写法,让keytool从标准输入读取内容,效果和管道传递一致:
/opt/jdk17/bin/keytool -printcert -v -file /dev/stdin < <(cat mycert.pem)

2、JDK17 keytool问题判定与Java编码规避规则

该现象属于JDK17 keytool的实现缺陷:
根因是JDK17对FileInputStream.readAllBytes方法做了性能优化,针对文件输入流新增了seek到文件末尾获取总长度、一次性分配对应大小缓冲区的逻辑,减少逐块读取的内存拷贝开销,但该逻辑没有做文件类型校验,直接对管道、套接字、字符设备这类不支持seek的特殊文件执行了seek系统调用,最终抛出Illegal seek错误。JDK11版本的readAllBytes采用逐块读取、动态扩容缓冲区的实现,没有调用seek,因此可以兼容进程替换的管道文件。

编写Java程序时要兼容Bash进程替换这类流式输入场景,需要规避以下错误写法:

  • 不要假设命令行传入的文件路径一定对应磁盘上的普通文件,读取前先通过Files.isRegularFile()判断文件类型:仅对普通文件使用依赖seek的快速读取逻辑,对非普通文件统一使用无随机寻址的流式逐块读取逻辑。
  • 不要直接对传入的文件路径调用Files.readAllBytes()、FileInputStream.readAllBytes()这类默认带seek优化的JDK内置方法读取可能为流类型的输入,若输入源类型不确定,可自行包装为BufferedInputStream后逐段读取,动态扩容缓冲区直到读到EOF,避免触发不支持的系统调用。
  • 命令行工具类程序建议显式支持/dev/stdin标准输入路径,读取标准输入流时全程不要调用position、大跨度skip(底层会触发seek实现)这类随机寻址相关方法。

内容的提问来源于stack exchange,提问作者Marinos An

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 05:06:18