有时我需要在jar文件清单中的类路径条目中搜索依赖项。具有类路径的jar清单看起来像这样:




 Manifest-Version: 1.0
Created-By: 1.6.0_29 (Sun Microsystems Inc.)
Class-Path: spring-aop-3.2.0.jar spring-aspects-3.2.0.jar spring-beans
 -3.2.0.jar spring-context-3.2.0.jar spring-context-support-3.2.0.jar
 spring-core-3.2.0.jar spring-expression-3.2.0.jar spring-instrument-3
 .2.0.jar spring-instrument-tomcat-3.2.0.jar spring-jdbc-3.2.0.jar spr
 ing-jms-3.2.0.jar spring-orm-3.2.0.jar spring-oxm-3.2.0.jar spring-st
 ruts-3.2.0.jar spring-test-3.2.0.jar spring-tx-3.2.0.jar spring-web-3
 .2.0.jar spring-webmvc-3.2.0.jar spring-webmvc-portlet-3.2.0.jar
 



在清单中搜索jar很麻烦:


从jar中提取META-INF/MANIFEST.MF文件
重新连接分割线以进行搜索人道化的操作

由于断行,因此必须执行步骤2。例如,在上面我找不到“ struts”,因为这个单词在中间被打断了。

我使用此shell脚本使此过程更容易,我称之为jar-manifest-classpath.sh



 #!/bin/bash

if test -d "$TMPDIR"; then
    :
elif test -d "$TMP"; then
    TMPDIR=$TMP
elif test -d /var/tmp; then
    TMPDIR=/var/tmp
else
    TMPDIR=/tmp
fi
workdir=$TMPDIR/$(basename "spring-aop-3.2.0.jar
spring-aspects-3.2.0.jar
spring-beans-3.2.0.jar
spring-context-3.2.0.jar
spring-context-support-3.2.0.jar
spring-core-3.2.0.jar
spring-expression-3.2.0.jar
spring-instrument-3.2.0.jar
spring-instrument-tomcat-3.2.0.jar
spring-jdbc-3.2.0.jar
spring-jms-3.2.0.jar
spring-orm-3.2.0.jar
spring-oxm-3.2.0.jar
spring-struts-3.2.0.jar
spring-test-3.2.0.jar
spring-tx-3.2.0.jar
spring-web-3.2.0.jar
spring-webmvc-3.2.0.jar
spring-webmvc-portlet-3.2.0.jar
")-work-$$

cleanup() {
    rm -fr "$workdir"
}

mkdir -p "$workdir"
trap 'cleanup' 1 2 3 15

for jar; do
    if ! test -f $jar; then
        echo warning: not a file: $jar
        continue
    fi
    [[ $jar = /* ]] || jar=$PWD/$jar
    (
        cd "$workdir" || exit 1
        jar xf "$jar"
        sed -ne '/^Class-Path:/,$p' META-INF/MANIFEST.MF | sed -e 's/^Class-Path: //' -e 's/^ //' | tr -d '\n' | tr ' ' '\n'
    )
done

cleanup
 


对于具有上述清单的jar文件,这将输出:




 grep 



这更令人高兴,并且可以完美地实现。

让我知道您是否有任何想法可以改进或简化!
(脚本是我收藏的一部分GitHub)

评论

在Java中执行此操作会容易得多,因为标准库已经具有用于处理JAR清单的类。

#1 楼

在完成需要清除的操作之前,我将创建清除陷阱,因此请更改命令顺序。我也希望信号名称而不是数字。如果将EXIT添加到信号列表中,则退出shell时将调用清除操作,因此您可以删除脚本的最后一行(cleanup

trap 'cleanup' EXIT SIGHUP SIGINT SIGQUIT SIGTERM
mkdir -p "$workdir"


如果调用脚本时不带参数,或者使用-?显示使用情况消息。警告和错误消息应写入stderr(2)而不是stdout(1)。这是程序的Unix标准行为。否则,如果通过grep命令通过管道传输输出,则不会看到错误消息。

评论


\ $ \ begingroup \ $
优点,谢谢!希望您能更多地参加Code Review!
\ $ \ endgroup \ $
– janos
15年2月19日在12:23

#2 楼

您正在努力创建一个临时文件夹,但您应该使用正确的工具:mktemp

workdir=$(mktemp -d)


这是一种安全的创建方式

请注意,mktemp使用$TMPDIR中的值(如果已设置)作为创建文件夹的位置,因此它将使用之前在代码中设置的任何值正在运行mktemp

评论


\ $ \ begingroup \ $
+1 mktemp是正确的工具,但是$ TMPDIR是默认的工具。所以应该使用mktemp -d
\ $ \ endgroup \ $
– Miracle173
2014年12月18日在18:33

\ $ \ begingroup \ $
如果没有-t mktemp,则不会使用$ TMPDIR吗?
\ $ \ endgroup \ $
– Etan Reisner
2014年12月19日,0:35

\ $ \ begingroup \ $
@EtanReisner-手册页上说:“如果未指定TEMPLATE,则使用tmp.XXXXXXXXXX,并暗示--tmpdir。”
\ $ \ endgroup \ $
–rolfl
2014-12-19在0:42



\ $ \ begingroup \ $
也许对于GNU / coreutils mktemp。不适用于BSD mktemp(至少根据我看到的文档)。
\ $ \ endgroup \ $
– Etan Reisner
2014-12-19在2:38



#3 楼

从提取和文本处理方面的困难可以证明,Bash可能不是工作的最佳工具。尽管jar命令不支持将内容提取到标准输出,但是Java确实具有对清单文件解析的内置支持。考虑编写最小的外壳包装程序来调用Java程序:



 public static void main(String[] args) throws IOException {
    Manifest mf = (args.length > 0) ? (new JarFile(args[0])).getManifest()
                                    : (new JarInputStream(System.in)).getManifest();
    String classPath = mf.getMainAttributes().getValue("Class-Path");
    if (classPath != null) {
        for (String dependency : classPath.split(" ")) {
            System.out.println(dependency);
        }
    }
}
 


评论


\ $ \ begingroup \ $
这里引用了使用这种通用方法的相关示例。
\ $ \ endgroup \ $
–垃圾神
2014年12月20日在3:43



#4 楼

使用Jar文件时,请记住它们只是具有某些限制的zip文件。

因此,无需遍历整个extract-to-temp文件夹,而您可以“简单路径”:

unzip -q -c $jar META-INF/MANIFEST.MF


这将从zip存档中仅提取MANIFEST.MF文件,并将其输出到标准输出。

因此,您可以完全跳过整个temp-dir过程。

#5 楼

尽管此脚本在给定的示例和一般的类路径jar(仅包含清单且不包含类的jar)中都可以正常工作,但
对于包含类的jar来说效率很低。
罪魁祸首是步骤提取jar的文件:


    [[ $jar = /* ]] || jar=$PWD/$jar
    (
        cd "$workdir" || exit 1
        jar xf "$jar"
        ^^^^ not so good



这样的jar xf步骤将提取整个jar文件,当清单文件时这样就足够了:

        jar xf "$jar" META-INF/MANIFEST.MF



还有另一个晦涩的错误:
尽管在示例中Class-Path字段是最后一个,
不能保证那样。
例如,这是一个完全有效的清单,
,某些实现可能以这种方式生成清单:


Manifest-Version: 1.0
Class-Path: spring-aop-3.2.0.jar spring-aspects-3.2.0.jar spring-beans
 -3.2.0.jar spring-context-3.2.0.jar spring-context-support-3.2.0.jar
 spring-core-3.2.0.jar spring-expression-3.2.0.jar
Created-By: 1.6.0_29 (Sun Microsystems Inc.)



问题中的sed脚本无法很好地处理这种情况,
Created-By行的内容将最终出现在输出中。
尽管这不会破坏您在类路径中命名的能力,
添加的行是垃圾,很明显不是预期的。


而不是乏味的倍数grep,临时目录:

for TMPDIR in "$TMPDIR" "$TMP" /var/tmp /tmp; do
    test -d "$TMPDIR" && break
done


(此技术在我其他相关问题的另一个答案中,由@frostschutz提出)

#6 楼

我找到这行:

sed -ne '/^Class-Path:/,$p' META-INF/MANIFEST.MF | sed -e 's/^Class-Path: //' -e 's/^ //' | tr -d '\n' | tr ' ' '\n'


令人难以理解的阅读。当然,必须有一种更简单/更轻松的方式来做到这一点吗?我不懂bash,但无论如何它会跳出代码气味...

#7 楼

JAR提取

很多麻烦来自处理临时文件。您必须找到一个临时目录(应该使用mktemp(1)来完成),然后即使在发生故障的情况下也可以清理混乱。

您只需要处理META-INF/MANIFEST.MF的内容。理想情况下,您应该将文本提取到标准输出中,以便通过管道进行进一步处理。不幸的是,Java的jar命令没有这种能力。

幸运的是,JAR文件规范说该格式基于流行的ZIP文件格式。因此,可以使用不支持unzip(1)选项的-c提取到标准输出。

文本处理

您的代码在Class-Path:之后打印每个单词,直到文件末尾。如果在MANIFEST.MF行之后的其他条目出现在Class-Path中,它们的内容也将被打印!

我建议编写一个简短的AWK脚本来展开连续行。然后找到Class-Path条目,将其每个单词分成一行,然后省略第一行。您可以尝试在一个AWK脚本中完成所有操作,但是我建议您将关注点分开。

建议的解决方案

unzip -q -c "$jar" META-INF/MANIFEST.MF |

        # Unfold continuation lines
        # (This prepends an empty line, but that won't matter)
        awk '/^[^ ]/ { print BUF; BUF=q4312078q }
             END     { print BUF }
             /^ /    { sub(" *", ""); BUF = BUF q4312078q }' |

        # Get Class-Path entry
        grep '^Class-Path: ' |

        # Split into one line per word
        tr ' ' '\n' |

        # Omit the "Class-Path:" entry name itself
        tail +2