Saturday, September 27, 2008

Jarsearch: Bash script to search for class name substrings in archive files

How often have you had to figure out which of numerous jars (or zips) in numerous directories has a particular class (or other kind of file), so you can copy it, reference it, or open it for inspection?

I long ago tired of struggling through this, so I wrote a simple Bash (runs on Unix or Cygwin) script that facilitates this, and is easy to integrate into an "xargs" pipe.

The script is called "jarsearch". It takes at least two parameters, and two options. The first expected parameter is a search string. The second expected parameter is a path to an archive file that "jar" can understand. Additional parameters are additional file paths. It also takes optional flags "-l" (just list the archives it was found in) and "-i" (ignore case in comparisons).

Here are some examples of its usage:
% jarsearch Delivery *.jar
File dsn.jar:
3506 Wed Oct 17 11:06:06 PDT 2007 com/sun/mail/dsn/DeliveryStatus.class

% jarsearch SAXParse apache-ant-1.7.0/lib/*.jar
File apache-ant-1.7.0/lib/xercesImpl.jar:
45 Wed Sep 13 22:13:54 PDT 2006 META-INF/services/javax.xml.parsers.SAXParserFactory
2701 Wed Sep 13 22:16:12 PDT 2006 org/apache/xerces/jaxp/SAXParserFactoryImpl.class
5760 Wed Sep 13 22:16:12 PDT 2006 org/apache/xerces/jaxp/SAXParserImpl$JAXPSAXParser.class
7748 Wed Sep 13 22:16:12 PDT 2006 org/apache/xerces/jaxp/SAXParserImpl.class
564 Wed Sep 13 22:16:06 PDT 2006 org/apache/xerces/parsers/AbstractSAXParser$1.class
564 Wed Sep 13 22:16:06 PDT 2006 org/apache/xerces/parsers/AbstractSAXParser$2.class
2500 Wed Sep 13 22:16:06 PDT 2006 org/apache/xerces/parsers/AbstractSAXParser$AttributesProxy.class
1009 Wed Sep 13 22:16:06 PDT 2006 org/apache/xerces/parsers/AbstractSAXParser$LocatorProxy.class
18424 Wed Sep 13 22:16:06 PDT 2006 org/apache/xerces/parsers/AbstractSAXParser.class
1710 Wed Sep 13 22:16:06 PDT 2006 org/apache/xerces/parsers/SAXParser.class
File apache-ant-1.7.0/lib/xml-apis.jar:
3896 Sat Feb 25 11:28:32 PST 2006 javax/xml/parsers/SAXParser.class
2483 Sat Feb 25 11:28:32 PST 2006 javax/xml/parsers/SAXParserFactory.class
1287 Sat Feb 25 11:28:32 PST 2006 org/xml/sax/SAXParseException.class

% find apache-* castor-* -name "*.jar" | xargs -n100 jarsearch javax.xml.parsers.SAXParser.class
File apache-ant-1.7.0/lib/xml-apis.jar:
3896 Sat Feb 25 11:28:32 PST 2006 javax/xml/parsers/SAXParser.class
File castor-0.9.6/lib/xerces-J_1.4.0.jar:
3518 Tue May 22 17:21:10 PDT 2001 javax/xml/parsers/SAXParser.class
File castor-0.9.7/lib/xerces-J_1.4.0.jar:
3518 Tue May 22 17:21:10 PDT 2001 javax/xml/parsers/SAXParser.class

And following this is the entire "jarsearch" script. Put this in your $HOME/bin and set the execute bit ("chmod +x $HOME/bin/jarsearch").
#! /bin/bash
listflag=0
ignorecase=0
while getopts li opt
do
case $opt in
l) listflag=1;;
i) ignorecase=1;;
esac
done
shift `expr $OPTIND - 1`
sstring="$1"
shift

casestr=""
if [ "$ignorecase" == 1 ]; then
casestr="-i"
fi

# (copied from "ant")
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
case "`uname`" in
CYGWIN*) cygwin=true ;;
Darwin*) darwin=true
if [ -z "$JAVA_HOME" ] ; then
JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Home
fi
;;
esac

tmpfile=${TMPDIR:-/tmp}/js.$$
trap '/bin/rm -f $tmpfile; exit 1' 1 2 3 15
for fn in "$@"; do
if $cygwin; then
fn="$(cygpath -m $fn)"
fi
jar tvf "$(echo $fn)" | 2>&1 grep $casestr "$sstring" > $tmpfile
if [ -s $tmpfile ]; then
if [ "$listflag" == 1 ]; then
echo $fn
else
echo File $fn\:
cat $tmpfile
fi
fi
done
/bin/rm -f $tmpfile