Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 137 additions & 6 deletions bin/hbase
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,21 @@
# HBASE_SHELL_OPTS Extra options passed to the hbase shell.
# Empty by default.
#
# HBASE_JSHELL_ARGS Additional arguments passed to the jshell.
# Defaults to `--startup DEFAULT --startup PRINTING --startup hbase_startup.jsh`
# HBASE_JSHELL_ARGS Additional arguments passed to jshell (interactive `jshell`
# and `*.jsh` script dispatch). When unset, defaults to
# `--startup DEFAULT --startup PRINTING` plus
# `--startup ${HBASE_HOME}/bin/hbase_startup.jsh` when that
# file exists, and `--execution local` unless --execution is
# already set here (so System.exit in scripts propagates).
# Split on whitespace like HBASE_OPTS -- individual argument
# values (e.g. a --startup file path) cannot contain spaces.
#
# HBASE_JSH_ARG_N / HBASE_JSH_ARG_COUNT
# Positional args for `hbase path/to/script.jsh [args...]`.
# JShell script mode has no String[] args, so bin/hbase
# exports HBASE_JSH_ARG_0..HBASE_JSH_ARG_{N-1} and
# HBASE_JSH_ARG_COUNT=N for the script to read (see
# hbase_startup.jsh for an example).
#
# HBASE_HBCK_OPTS Extra options passed to hbck.
# Defaults to HBASE_SERVER_JAAS_OPTS if specified, or HBASE_REGIONSERVER_OPTS.
Expand Down Expand Up @@ -112,6 +125,8 @@ show_usage() {
echo " credential Run the Hadoop Credential Shell"
echo " copyreppeers Run CopyReplicationPeers tool"
echo " CLASSNAME Run the class named CLASSNAME"
echo " <path>.jsh Run <path> as a JShell script non-interactively (args via HBASE_JSH_ARG_N)"
echo " <path>.java Run <path> as a single-file .java source (JEP 330) with HBase on the classpath"
}

if [ "--help" = "$1" ] || [ "-h" = "$1" ]; then
Expand Down Expand Up @@ -174,6 +189,50 @@ add_size_suffix() {
fi
}

# Resolve a script under bin/: keep paths that already contain a slash, otherwise
# look in HBASE_HOME/bin. Sets RESOLVED_BIN_SCRIPT or exits.
resolve_bin_script() {
local name="$1"
local kind="$2"
if [[ "$name" == */* ]]; then
RESOLVED_BIN_SCRIPT="$name"
else
RESOLVED_BIN_SCRIPT="${HBASE_HOME}/bin/${name}"
fi
if [ ! -f "$RESOLVED_BIN_SCRIPT" ]; then
echo "${kind} not found: $RESOLVED_BIN_SCRIPT"
exit 1
fi
}

# Populate JSHELL_ARGS for both interactive `jshell` and `*.jsh` script dispatch.
# Shared so the two paths cannot drift. Honors HBASE_JSHELL_ARGS when set; otherwise
# DEFAULT + PRINTING + optional hbase_startup.jsh. Defaults to --execution local
# unless the user already passed --execution / --execution=<spec>.
init_jshell_args() {
if [ -n "${HBASE_JSHELL_ARGS}" ]; then
read -r -a JSHELL_ARGS <<< "${HBASE_JSHELL_ARGS}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please either:

  • explicitly document that HBASE_JSHELL_ARGS cannot contain whitespace-bearing arguments, or
  • add a parsing approach that supports values such as a startup file path containing spaces.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented in 6cd8aa3: HBASE_JSHELL_ARGS is split on whitespace the same way HBASE_OPTS is, so an argument value (e.g. a --startup file path) cannot contain spaces. Considered supporting quoted values via eval-based array parsing, but decided to defer that behavior change to a follow-up rather than introduce eval on an env var into bin/hbase — happy to revisit if you feel the quoting support is worth it here.

else
JSHELL_ARGS=(--startup DEFAULT --startup PRINTING)
if [ -f "${HBASE_HOME}/bin/hbase_startup.jsh" ]; then
JSHELL_ARGS+=(--startup "${HBASE_HOME}/bin/hbase_startup.jsh")
fi
fi
# --execution local runs snippets in-process so System.exit() propagates as the
# process exit code (JDI remote mode only kills the forked VM).
local has_execution=false
local arg
for arg in "${JSHELL_ARGS[@]}"; do
if [ "$arg" = "--execution" ] || [[ "$arg" == --execution=* ]]; then
has_execution=true
break
fi
done
if [ "$has_execution" = false ]; then
JSHELL_ARGS+=(--execution local)
fi
}

if [[ -n "$HBASE_HEAPSIZE" ]]; then
JAVA_HEAP_MAX="-Xmx$(add_size_suffix $HBASE_HEAPSIZE)"
fi
Expand Down Expand Up @@ -579,8 +638,7 @@ if [ "$COMMAND" = "shell" ] ; then
HBASE_OPTS="$HBASE_OPTS $HBASE_SHELL_OPTS"
elif [ "$COMMAND" = 'jshell' ] ; then
CLASS='jdk.internal.jshell.tool.JShellToolProvider'
# set default values for HBASE_JSHELL_ARGS
read -r -a JSHELL_ARGS <<< "${HBASE_JSHELL_ARGS:-"--startup DEFAULT --startup PRINTING --startup ${HBASE_HOME}/bin/hbase_startup.jsh"}"
init_jshell_args
HBASE_OPTS="$HBASE_OPTS $HBASE_JSHELL_OPTS"
elif [ "$COMMAND" = "hbck" ] ; then
# Look for the -j /path/to/HBCK2.jar parameter. Else pass through to hbck.
Expand Down Expand Up @@ -795,6 +853,54 @@ elif [ "$COMMAND" = "credential" ] ; then
CLASS='org.apache.hadoop.security.alias.CredentialShell'
elif [ "$COMMAND" = "copyreppeers" ] ; then
CLASS='org.apache.hadoop.hbase.replication.CopyReplicationPeers'
elif [[ "$COMMAND" == *.jsh ]] ; then
# No JDK version guard here: hbase-config.sh already requires JDK 17+.
resolve_bin_script "$COMMAND" "jshell script"
JSH_SCRIPT="$RESOLVED_BIN_SCRIPT"
# pass positional args to the script via env vars: jshell script mode has no
# String[] args equivalent, and re-joining/re-splitting args into one string
# breaks on embedded spaces, so each arg gets its own env var instead.
# See HBASE_JSH_ARG_N / HBASE_JSH_ARG_COUNT in the header and hbase_startup.jsh.
i=0
for a in "$@"; do
export HBASE_JSH_ARG_$i="$a"
i=$((i+1))
done
export HBASE_JSH_ARG_COUNT=$i
set --
CLASS='jdk.internal.jshell.tool.JShellToolProvider'
init_jshell_args
# Default to -q (concise feedback) for non-interactive scripts, but jshell
# allows only one feedback option (--feedback / -q / -s / -v); honor any the
# user already set via HBASE_JSHELL_ARGS.
has_feedback=false
for arg in "${JSHELL_ARGS[@]}"; do
if [ "$arg" = "--feedback" ] || [[ "$arg" == --feedback=* ]] \
|| [ "$arg" = "-q" ] || [ "$arg" = "-s" ] || [ "$arg" = "-v" ]; then
has_feedback=true
break
fi
done
if [ "$has_feedback" = false ]; then
JSHELL_ARGS+=(-q)
fi
JSHELL_ARGS+=("$JSH_SCRIPT")
HBASE_OPTS="$HBASE_OPTS $HBASE_JSHELL_OPTS"
# run the script and exit rather than dropping into an interactive REPL
HBASE_JSH_SCRIPT_MODE=true
elif [[ "$COMMAND" == *.java ]] ; then
# JEP 330 single-file source launch: java forwards the remaining args
# directly to main(String[] args), so no env-var passthrough is needed here
# (unlike the .jsh case above). No JDK version guard: hbase-config.sh already
# requires JDK 17+, which covers JEP 330 (JDK 11+).
#
# Keep the source path out of $CLASS: the java invocation expands $CLASS
# unquoted (so multi-token CLASS values like the JRuby shell launcher still
# word-split), which would break paths containing spaces. Prepend the path
# onto "$@" instead; it is then carried via the quoted "${CMD_ARGS[@]}" array.
resolve_bin_script "$COMMAND" "java source script"
set -- "$RESOLVED_BIN_SCRIPT" "$@"
CLASS=
else
CLASS=$COMMAND
fi
Expand Down Expand Up @@ -855,9 +961,34 @@ if [ "${#JSHELL_ARGS[@]}" -gt 0 ] ; then
CMD_ARGS=("${JSHELL_ARGS[@]}" "${CMD_ARGS[@]}")
fi

# Keep -Dproc_* as a plain keyword even when COMMAND is a .jsh/.java path.
case "$COMMAND" in
*.jsh) HBASE_PROC_NAME=jsh ;;
*.java) HBASE_PROC_NAME=java ;;
*) HBASE_PROC_NAME="$COMMAND" ;;
esac

# JShell script mode: detach stdin so an interactive tty does not keep jshell
# open after the script finishes. Do this once here rather than duplicating the
# java invocation for the redirect.
if [ "${HBASE_JSH_SCRIPT_MODE}" = "true" ]; then
exec < /dev/null
fi

# $CLASS is left unquoted historically so multi-token values (rare) still
# word-split, but for .java JEP 330 we keep CLASS empty and put the source
# path in CMD_ARGS (quoted). Build the trailing argv so an empty CLASS does
# not become a spurious "" argument the way quoting "$CLASS" would.
JAVA_MAIN=()
if [ -n "$CLASS" ]; then
# shellcheck disable=SC2206
JAVA_MAIN+=($CLASS)
fi
JAVA_MAIN+=("${CMD_ARGS[@]}")

if [ "${HBASE_NOEXEC}" != "" ]; then
"$JAVA" -Dproc_$COMMAND -XX:OnOutOfMemoryError="kill -9 %p" $HEAP_SETTINGS $HBASE_OPTS $CLASS "${CMD_ARGS[@]}"
"$JAVA" "-Dproc_${HBASE_PROC_NAME}" -XX:OnOutOfMemoryError="kill -9 %p" $HEAP_SETTINGS $HBASE_OPTS "${JAVA_MAIN[@]}"
else
export JVM_PID="$$"
exec "$JAVA" -Dproc_$COMMAND -XX:OnOutOfMemoryError="kill -9 %p" $HEAP_SETTINGS $HBASE_OPTS $CLASS "${CMD_ARGS[@]}"
exec "$JAVA" "-Dproc_${HBASE_PROC_NAME}" -XX:OnOutOfMemoryError="kill -9 %p" $HEAP_SETTINGS $HBASE_OPTS "${JAVA_MAIN[@]}"
fi
13 changes: 13 additions & 0 deletions bin/hbase_startup.jsh
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@

import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;

/*
* Positional args for non-interactive scripts launched as:
* hbase path/to/script.jsh arg0 arg1 ...
*
* JShell has no String[] args equivalent, so bin/hbase exports:
* HBASE_JSH_ARG_COUNT - number of args (decimal string)
* HBASE_JSH_ARG_0 .. HBASE_JSH_ARG_{N-1} - each positional arg
*
* Example in a .jsh script:
* int n = Integer.parseInt(System.getenv().getOrDefault("HBASE_JSH_ARG_COUNT", "0"));
* String first = System.getenv("HBASE_JSH_ARG_0");
*/
4 changes: 3 additions & 1 deletion conf/hbase-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,7 @@
#
# JDK11+ JShell
#
# Additional arguments passed to jshell invocation
# Additional arguments passed to jshell (interactive and *.jsh script dispatch).
# When unset, bin/hbase uses DEFAULT + PRINTING (+ hbase_startup.jsh when present)
# and --execution local unless --execution is already set here.
# export HBASE_JSHELL_ARGS="--startup DEFAULT --startup PRINTING --startup hbase_startup.jsh"