You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ni...@apache.org on 2026/10/06 15:46:01 UTC

(hbase) branch branch-3 updated: HBASE-30419 Add .jsh/.java single-file script dispatch to bin/hbase (#8703)

This is an automated email from the ASF dual-hosted git repository.

NihalJain pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-3 by this push:
     new d71e275c50f HBASE-30419 Add .jsh/.java single-file script dispatch to bin/hbase (#8703)
d71e275c50f is described below

commit d71e275c50fec47a1ecbd509a32eab3764bc488c
Author: Aman Poonia <am...@gmail.com>
AuthorDate: Tue Oct 6 21:05:22 2026 +0530

    HBASE-30419 Add .jsh/.java single-file script dispatch to bin/hbase (#8703)
    
    Signed-off-by: Andrew Purtell <ap...@apache.org>
    Signed-off-by: Nihal Jain <ni...@apache.org>
    
    (cherry picked from commit 8813918aa98b3e3313529af48fbd5a2679cf83fe)
---
 bin/hbase             | 143 +++++++++++++++++++++++++++++++++++++++++++++++---
 bin/hbase_startup.jsh |  13 +++++
 conf/hbase-env.sh     |   4 +-
 3 files changed, 153 insertions(+), 7 deletions(-)

diff --git a/bin/hbase b/bin/hbase
index 045b6171fa6..72c53081823 100755
--- a/bin/hbase
+++ b/bin/hbase
@@ -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.
@@ -111,6 +124,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
@@ -173,6 +188,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}"
+  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
@@ -578,8 +637,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.
@@ -778,6 +836,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
@@ -838,9 +944,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
diff --git a/bin/hbase_startup.jsh b/bin/hbase_startup.jsh
index 0f5dd9c25bb..794f230eb3a 100644
--- a/bin/hbase_startup.jsh
+++ b/bin/hbase_startup.jsh
@@ -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");
+ */
diff --git a/conf/hbase-env.sh b/conf/hbase-env.sh
index d80bfaeeddb..c69421b61f2 100644
--- a/conf/hbase-env.sh
+++ b/conf/hbase-env.sh
@@ -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"