DEV Community

Andrew (he/him)
Andrew (he/him)

Posted on

2

Easily Merge Multiple Java --classpath Arguments

I always confuse myself as to whether the correct flags for java and the jshell are -cp or --classpath or --class-path or something else, so instead of trying to remember something like a normal human, I instead spent a few hours pulling this script together.

It automatically detects any variation of cp, classpath, and class-path flags in a series of command-line arguments, and concatenates all of the correct arguments to give you a nice, clean classpath. I hope it's useful!

#!/usr/bin/env bash
#-------------------------------------------------------------------------------
#
# array_contains - returns 0 if array contains the specified value, else 1
#
# $1 : term to search for
# $2+ : array to inspect
#
# sources:
# https://stackoverflow.com/a/8574392/2925434
#
#-------------------------------------------------------------------------------
function array_contains {
local match="$1"
shift
for arg; do
[[ "$match" == "$arg" ]] && return 0
done
return 1
}
# usage:
#
# $ if $(array_contains 4 5 7 8); then echo "contains"; else echo "doesn't"; fi
# doesn't
#
# $ if $(array_contains 4 4 5 7 8); then echo "contains"; else echo "doesn't"; fi
# contains
# usage:
#
# $ test[0]=a; test[1]=b; test[2]=c; echo ${test[1]}
# b
#
# $ if $(array_contains c "${test[@]}"); then echo "contains"; else echo "doesn't"; fi
# contains
#
# $ if $(array_contains d "${test[@]}"); then echo "contains"; else echo "doesn't"; fi
# doesn't
#!/usr/bin/env bash
#-------------------------------------------------------------------------------
#
# is_flag - infer whether or not a command-line argument is a flag OR empty
#
# returns 0: arg is flag
# returns 1: arg is not a flag, not empty
# returns 2: arg is empty
#
# sources:
# https://stackoverflow.com/a/10218528/2925434
# https://stackoverflow.com/a/17287097/2925434
#
#-------------------------------------------------------------------------------
function is_flag {
local arg=""
local retval=1
# if is empty, not a flag
if [[ -z "$1" ]]; then
arg="\"\""
retval=2
else
# get first and last characters
local firstchar="${1:0:1}"
local lastchar="${1: -1}"
local short="$1"
# if surrounded by quotes, remove them
if [[ $firstchar == "\"" && $lastchar == "\"" ]] ||
[[ $firstchar == "'" && $lastchar == "'" ]]; then
local lenm2=$((${#1}-2))
short="${1:1:$lenm2}"
fi
# if is empty, not a flag
if [[ -z "$short" ]]; then
arg="\"\"\"\""
retval=2
# if contains spaces, not a flag -- surround with quotes
elif [[ "$short" = *[[:space:]]* ]]; then
arg="\"$short\""
# else, if starts with "-", is a flag -- no quotes
elif [[ "${short:0:1}" == "-" ]]; then
arg="$short"
retval=0
# else, isn't a flag -- surround with quotes
else
arg="\"$short\""
fi
fi
# print arg and return
echo "${arg}"
return $retval
}
# usage:
#
# $ is_flag p # not a flag, surrounded in quotes
# "p"
#
# $ is_flag -p # is a flag, not surrounded in quotes
# -p
#
# $ is_flag "-p" # is a flag, even though surrounded by quotes
# -p
#
# $ is_flag --p # is a flag, any number of - allowed
# --p
#
# $ is_flag "-p a" # not a flag, because quoted and contains spaces
# "-p a"
#
# $ is_flag "-pa" # is a flag, because contains no spaces
# -pa
#
# $ is_flag # no argument given, return empty string
# ""
#
# $ is_flag "" # empty string given, return empty string
# ""
#
# $ is_flag "\"\"" # empty quoted string, return same
# """"
view raw is_flag.sh hosted with ❤ by GitHub
#!/usr/bin/env bash
export JARS_HOME=~/.jenv/jars
# jar locations
export JAVA_JARS_HOME=$JARS_HOME/java
export SCALA_JARS_HOME=$JARS_HOME/scala
# jar lists
export JAVA_JARS_LIST=".:$JAVA_JARS_HOME/\*"
export SCALA_JARS_LIST=".:$JAVA_JARS_HOME/\*:$SCALA_JARS_HOME/\*"
# overrides for jshell, java, etc.
function jshell {
merge_classpath -cp $(eval "echo $JAVA_JARS_LIST") $@ | xargs -o $(whereis jshell)
}
function scala { # replace --class-path with -cp for Scala only
merge_classpath -cp $(eval "echo $SCALA_JARS_LIST") $@ | \
sed 's/--class-path/-cp/' | xargs -o $SCALA_HOME/bin/scala
}
function java {
merge_classpath -cp $(eval "echo $JAVA_JARS_LIST") $@ | xargs -o $JAVA_HOME/bin/java
}
function derby {
java org.apache.derby.tools.ij
}
view raw jvm.sh hosted with ❤ by GitHub
#!/usr/bin/env bash
#-------------------------------------------------------------------------------
#
# merge_classpath - merge '-cp' '-classpath' and '--class-path' flags and args
#
# sources:
# https://stackoverflow.com/a/1498876/2925434
# https://stackoverflow.com/a/18568726/2925434
# https://stackoverflow.com/a/171041/2925434
# https://stackoverflow.com/a/1405641/2925434
# https://stackoverflow.com/a/17573592/2925434
#
#-------------------------------------------------------------------------------
function merge_classpath {
# loop over all arguments and find any '-cp', etc. flags
flags[0]="-cp"
flags[1]="-classpath"
flags[2]="-class-path"
flags[3]="--cp"
flags[4]="--classpath"
flags[5]="--class-path"
# any argument which immediately follows one of these flags is assumed to be
# another jar file (or list of jar files) to be added to the classpath
# UNLESS the next argument is ALSO a flag (or an empty string) in which case,
# the first ('-cp') flag is ignored
# all other flags and their relationships to each other are kept intact,
# quotes are put around every non-flag argument
local classpath=""
local otherargs=""
for ((ii=1; ii<=$#; ++ii)); do
local jj=$((ii+1))
local this="${!ii}" # current flag / argument
local next="${!jj}" # next flag / argument
# if THIS arg is a -cp-like flag
if array_contains "${this}" "${flags[@]}"; then
# check if the next arg is a flag or empty
is_flag "\"${next}\"" > /dev/null 2>&1
# AND IF the NEXT arg is NOT empty AND NOT a flag, then
if [ $? -eq 1 ]; then
# add to classpath:
# first element added to classpath?
if [[ "$classpath" == "" ]]; then
classpath+="${!jj}"
# prepend all following ones with a ":"
else
classpath+=":${!jj}"
fi
# increment ii to skip next element
((ii++))
# if the next arg is invalid or missing, ignore this -cp-like flag
fi
# if this is some other flag / argument, keep intact
else
otherargs+=" $(is_flag \""${this}"\")"
fi
done
# if classpath is empty, return an error code
if [[ -z "$classpath" ]]; then
echo "${otherargs#?}" # strip leading ' '
return 1
fi
# else, send classpath and other flags and args to stdout and return normally
echo "--class-path \"$classpath\"$otherargs"
return 0
}
# usage:
#
# $ merge_classpath -cp . --class-path /path/to/jars -classpath "some:more:jars"
# --class-path ".:/path/to/jars:some:more:jars"
#
# $ merge_classpath -cp -cp /handles/bad/args --classpath /and/forgotten/ones -cp
# --class-path "/handles/bad/args:/and/forgotten/ones"
#
# $ merge_classpath -cp "retains : spaces" --classpath "in quoted args"
# --class-path "retains : spaces:in quoted args"
#
# $ merge_classpath -f "no classpath flags" "so no --class-path added"
# -f "no classpath flags" "so no --class-path added"
#
# $ merge_classpath "-quoted" "flags have quotes removed"
# -quoted "flags have quotes removed"

Top comments (2)

Collapse
 
vlasales profile image

An alternative to the for loop is the while loop with the shift command. It is without indexes and much simpler.

BTW: This script look like in Java. Bash is not Java, Bash is different. Use echo for return values from function.

Collapse
 
awwsmm profile image
Andrew (he/him)

Hi Vlastimil. I used indices because I need the i-th argument and also the i+1-th argument at the same time. shift throws away earlier arguments. There might be a way to refactor it, which I would love to see.

I use exit codes to indicate the status of the method -- i.e. 0 if everything went fine, 1 if there was an error, etc. I also use echo to print strings meant to be "returned" from the functions. See above!

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs