DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • How to Introduce a New API Quickly Using Micronaut
  • Javac and Java Katas, Part 2: Module Path
  • Keep Your Application Secrets Secret
  • Rapidly Develop Java Microservices on Kubernetes With Telepresence

Trending

  • The Future of Java and AI: Coding in 2025
  • How to Create a Successful API Ecosystem
  • Event-Driven Microservices: How Kafka and RabbitMQ Power Scalable Systems
  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  1. DZone
  2. Coding
  3. Languages
  4. A Better Java Shell Script Wrapper

A Better Java Shell Script Wrapper

By 
Zemian Deng user avatar
Zemian Deng
·
Sep. 11, 12 · Interview
Likes (0)
Comment
Save
Tweet
Share
14.5K Views

Join the DZone community and get the full member experience.

Join For Free

In many Java projects, you often see wrapper shell script to invoke the java command with its custom application parameters. For example, $ANT_HOME/bin/ant, $GROOVY_HOME/bin/groovy, or even in our TimeMachine Scheduler you will see $TIMEMACHINE_HOME/bin/scheduler.sh.

Writing these wrapper script is boring and error prone. Most of the problems come from setting the correct classpath for the application. If you're working on an in-house project for a company, then you can get away with hardcoding paths and your environment vars. But for open source projects, folks have to make the wrapper more flexible and generic. Most of them even provide a .bat version of it. Windows DOS is really a brutal and limited terminal to script away your project need. For this reason, I often encourage others to use Cygwin as much as they can. It at least has a real bash shell to work with. Another common problem with these wrappers is it can quickly get out of hand and have too many duplication of similar scripts liter every where in your project.

In this post, I will show you a Java wrapper script that I've written. It's simple to use and very flexible for running just about any Java program. Let's see how it's used first, and then I will print its content at the bottom of the post.

Introducing the run-java wrapper script

If you take a look at $TIMEMACHINE_HOME/bin/scheduler.sh, you will see that it in turns calls a run-java script that comes in the same directory.

DIR=$(dirname $0)
SCHEDULER_HOME=$DIR/..
$DIR/run-java -Dscheduler.home="$SCHEDULER_HOME" timemachine.scheduler.tool.SchedulerServer "$@"

As you can see, our run-java can take -D options. Not only this, it can also take -cp option as well! What's more is that you can specify these options even after the main class! This makes the run-java re-wrappable by other script, and still be able to add additional system properties and classpath.

For examples, the TimeMachine comes with Groovy library, so instead of downloading it's full distribution again, you can simply invoke the groovy like this

$TIMEMACHINE_HOME/bin/run-java groovy.ui.GroovyMain test.groovy

You can use run-java in any directory you're in, so it's convenient. It will resolve it's own directory and load any jars in the lib directory automatically. Now if you want Groovy to run with more additional jars, you can use the -cp option like this:

$TIMEMACHINE_HOME/bin/run-java -cp "$HOME/apps/my-app/lib/*" groovy.ui.GroovyMain test.groovy

Often times things will go wrong if you are not careful with Java classpath, but with run-java script you can perform a dry run first:

RUN_JAVA_DRY=1 $TIMEMACHINE_HOME/bin/run-java -cp "$HOME/apps/my-app/lib/*" groovy.ui.GroovyMain test.groovy

You would run the above all in single line on a command prompt. It should print out your full java command with all options and arguments for you to inspect.

There are many more options to the script, which you can find out more by reading the comments in it. The current script will work on any Linux bash or on a Windows Cygwin terminal.

Using run-java during development with Maven

Above examples are assuming you are in a released project structure such as this

$TIMEMACHINE_HOME
  +- bin/run-java
  +- lib/*.jar

 

But what about during development? A frequent use case is that you want to be able to run your latest compiled classes under target/classes without have to package up or release the entire project. You can use our run-java in these scenario as well. First, simply add bin/run-java in your project, then you run mvn compile dependency:copy-dependencies that will generate all the jar files into target/dependency. That's all. The run-java will automatically detect these directories and create the correct classpath to run your main class.

If you use Eclipse IDE for development, then your target/classes will be always up-to-date, and the run-java can be a great gem to have in your project even for development.

Get the run-java wrapper script now

#!/usr/bin/env bash
#
# Copyright 2012 Zemian Deng
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#    http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# A wrapper script that run any Java6 application in unix/cygwin env.
#
# This script is assumed to be located in an application's "bin" directory. It will
# auto resolve any symbolic link and always run in relative to this application 
# directory (which is one parent up from the script.) Therefore, this script can be
# run any where in the file system and it will still reference this application 
# directory.
#
# This script will by default auto setup a Java classpath that picks up any "config"
# and "lib" directories under the application directory. It also will also add a 
# any typical Maven project output directories such as "target/test-classes", 
# "target/classes", and "target/dependency" into classpath. This can be disable by 
# setting RUN_JAVA_NO_PARSE=1.
#
# If the "Default parameters" section bellow doesn't match to user's env, then user
# may override these variables in their terminal session or preset them in shell's 
# profile startup script. The values of all path should be in cygwin/unix path, 
# and this script will auto convert them into Windows path where is needed.
#
# User may customize the Java classpath by setting RUN_JAVA_CP, which will prefix to existing
# classpath, or use the "-cp" option, which will postfix to existing classpath.
# 
# Usage:
#   run-java [java_opts] <java_main_class> [-cp /more/classpath] [-Dsysprop=value]
#
# Example:
#   run-java example.Hello
#   run-java example.Hello -Dname=World
#   run-java org.junit.runner.JUnitCore example.HelloTest -cp "C:\apps\lib\junit4.8.2\*"
#
# Created by: Zemian Deng 03/09/2012

# This run script dir (resolve to absolute path)
SCRIPT_DIR=$(cd $(dirname $0) && pwd)    # This dir is where this script live.
APP_DIR=$(cd $SCRIPT_DIR/.. && pwd)      # Assume the application dir is one level up from script dir.

# Default parameters
JAVA_HOME=${JAVA_HOME:=/apps/jdk}        # This is the home directory of Java development kit.
RUN_JAVA_CP=${RUN_JAVA_CP:=$CLASSPATH}     # A classpath prefix before -classpath option, default to $CLASSPATH
RUN_JAVA_OPTS=${RUN_JAVA_OPTS:=}           # Java options (-Xmx512m -XX:MaxPermSize=128m etc)
RUN_JAVA_DEBUG=${RUN_JAVA_DEBUG:=}         # If not empty, print the full java command line before executing it.
RUN_JAVA_NO_PARSE=${RUN_JAVA_NO_PARSE:=}   # If not empty, skip the auto parsing of -D and -cp options from script arguments.
RUN_JAVA_NO_AUTOCP=${RUN_JAVA_NO_AUTOCP:=} # If not empty, do not auto setup Java classpath
RUN_JAVA_DRY=${RUN_JAVA_DRY:=}             # If not empty, do not exec Java command, but just print

# OS specific support.  $var _must_ be set to either true or false.
CYGWIN=false;
case "`uname`" in
  CYGWIN*) CYGWIN=true ;;
esac

# Define where is the java executable is
JAVA_CMD=java
if [ -d "$JAVA_HOME" ]; then
	JAVA_CMD="$JAVA_HOME/bin/java"
fi

# Auto setup applciation's Java Classpath (only if they exists)
if [ -z "$RUN_JAVA_NO_AUTOCP" ]; then
	if $CYGWIN; then
		# Provide Windows directory conversion
		JAVA_HOME_WIN=$(cygpath -aw "$JAVA_HOME")
		APP_DIR_WIN=$(cygpath -aw "$APP_DIR")

		if [ -d "$APP_DIR_WIN\config" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\config" ; fi
		if [ -d "$APP_DIR_WIN\target\test-classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\target\test-classes" ; fi
		if [ -d "$APP_DIR_WIN\target\classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\target\classes" ; fi
		if [ -d "$APP_DIR_WIN\target\dependency" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\target\dependency\*" ; fi
		if [ -d "$APP_DIR_WIN\lib" ]; then RUN_JAVA_CP="$RUN_JAVA_CP;$APP_DIR_WIN\lib\*" ; fi
	else
		if [ -d "$APP_DIR/config" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/config" ; fi
		if [ -d "$APP_DIR/target/test-classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/target/test-classes" ; fi
		if [ -d "$APP_DIR/target/classes" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/target/classes" ; fi
		if [ -d "$APP_DIR/target/dependency" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/target/dependency/*" ; fi
		if [ -d "$APP_DIR/lib" ]; then RUN_JAVA_CP="$RUN_JAVA_CP:$APP_DIR/lib/*" ; fi
	fi
fi

# Parse addition "-cp" and "-D" after the Java main class from script arguments
#   This is done for convenient sake so users do not have to export RUN_JAVA_CP and RUN_JAVA_OPTS
#   saparately, but now they can pass into end of this run-java script instead.
#   This can be disable by setting RUN_JAVA_NO_PARSE=1.
if [ -z "$RUN_JAVA_NO_PARSE" ]; then	
	# Prepare variables for parsing
	FOUND_CP=
	declare -a NEW_ARGS
	IDX=0
	
	# Parse all arguments and look for "-cp" and "-D"
	for ARG in "$@"; do
		if [[ -n $FOUND_CP ]]; then	
			if [ "$OS" = "Windows_NT" ]; then
				# Can't use cygpath here, because cygpath will auto expand "*", which we do not
				# want. User will just have to use OS path when specifying "-cp" option.	
				#ARG=$(cygpath -w -a $ARG)
				RUN_JAVA_CP="$RUN_JAVA_CP;$ARG"
			else
				RUN_JAVA_CP="$RUN_JAVA_CP:$ARG"
			fi
			FOUND_CP=
		else
			case $ARG in
			'-cp')
				FOUND_CP=1
				;;
			'-D'*)
				RUN_JAVA_OPTS="$RUN_JAVA_OPTS $ARG"
				;;
			*)
				NEW_ARGS[$IDX]="$ARG"
				let IDX=$IDX+1
				;;
			esac
		fi
	done
		
	# Display full Java command.
	if [ -n "$RUN_JAVA_DEBUG" ] || [ -n "$RUN_JAVA_DRY" ]; then
		echo "$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "${NEW_ARGS[@]}"
	fi
	
	# Run Java Main class using parsed variables
	if [ -z "$RUN_JAVA_DRY" ]; then
		"$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "${NEW_ARGS[@]}"
	fi
else
	# Display full Java command.
	if [ -n "$RUN_JAVA_DEBUG" ] || [ -n "$RUN_JAVA_DRY" ]; then
		echo "$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "$@"
	fi
	
	# Run Java Main class
	if [ -z "$RUN_JAVA_DRY" ]; then
		"$JAVA_CMD" $RUN_JAVA_OPTS -cp "$RUN_JAVA_CP" "$@"
	fi
fi
Java (programming language) Shell script shell

Published at DZone with permission of Zemian Deng, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How to Introduce a New API Quickly Using Micronaut
  • Javac and Java Katas, Part 2: Module Path
  • Keep Your Application Secrets Secret
  • Rapidly Develop Java Microservices on Kubernetes With Telepresence

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!