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

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 463e093d336 SOLR-17359: Move Zk Arg parsing into Java Code (#2593)
463e093d336 is described below

commit 463e093d336ff129e7b4fbe736cc3a2bb4725d39
Author: Eric Pugh <[email protected]>
AuthorDate: Wed Aug 14 06:28:22 2024 -0400

    SOLR-17359: Move Zk Arg parsing into Java Code (#2593)
    
    * Move Zk Arg parsing into Java Code from the bin/solr shell and 
bin/solr.cmd command scripts, making testing life easier on Windows.
    
    * Improved the output of -h to make usage information look nicer
    
    * restructure control flow so that we don't check for each script command 
by name and THEN call the java code, instead only look for the three 
exceptions, start, stop, restart.
    
    * Resolve Executing a command incorrectly gives a message which is not 
clear. Eg: >solr zk ls
    
    ---------
    
    Co-authored-by: Rahul Goswami <[email protected]>
---
 solr/CHANGES.txt                                   |   2 +-
 solr/bin/solr                                      | 342 +--------------------
 solr/bin/solr.cmd                                  | 121 +-------
 .../org/apache/solr/cli/ConfigSetDownloadTool.java |  49 ++-
 .../org/apache/solr/cli/ConfigSetUploadTool.java   |  47 ++-
 .../src/java/org/apache/solr/cli/CreateTool.java   |   2 +-
 .../java/org/apache/solr/cli/LinkConfigTool.java   |   5 +
 .../src/java/org/apache/solr/cli/PackageTool.java  |   2 -
 .../core/src/java/org/apache/solr/cli/SolrCLI.java |  58 +++-
 solr/core/src/java/org/apache/solr/cli/Tool.java   |   2 +-
 .../java/org/apache/solr/cli/UpdateACLTool.java    |  18 +-
 .../src/java/org/apache/solr/cli/ZkCpTool.java     |  70 ++++-
 .../src/java/org/apache/solr/cli/ZkLsTool.java     |  17 +-
 .../src/java/org/apache/solr/cli/ZkMkrootTool.java |  27 +-
 .../src/java/org/apache/solr/cli/ZkMvTool.java     |  41 ++-
 .../src/java/org/apache/solr/cli/ZkRmTool.java     |  16 +-
 .../src/java/org/apache/solr/cli/ZkToolHelp.java   |  84 +++++
 .../org/apache/solr/cli/SolrCLIZkToolsTest.java    | 309 +++----------------
 .../org/apache/solr/cli/ZkSubcommandsTest.java     | 110 ++-----
 .../cases/cloud_multi_node_embedded_zk/test.sh     |   2 +-
 solr/packaging/test/test_create_collection.bats    |   2 +-
 solr/packaging/test/test_help.bats                 |   3 +-
 solr/packaging/test/test_ssl.bats                  |  14 +-
 solr/packaging/test/test_zk.bats                   |  29 +-
 .../pages/solr-control-script-reference.adoc       |   4 +-
 .../pages/zookeeper-utilities.adoc                 |   2 +-
 .../getting-started/pages/tutorial-diy.adoc        |   2 +-
 .../getting-started/pages/tutorial-films.adoc      |   2 +-
 .../pages/tutorial-techproducts.adoc               |   2 +-
 29 files changed, 467 insertions(+), 917 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 8328743d647..1ab51dfa331 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -133,7 +133,7 @@ Dependency Upgrades
 
 Other Changes
 ---------------------
-(No changes)
+* SOLR-17359: Move Zk Arg parsing into Java Code from bin/solr scripts.   
(Eric Pugh, Rahul Goswami)
 
 ==================  9.7.0 ==================
 New Features
diff --git a/solr/bin/solr b/solr/bin/solr
index 7e1629724f2..e1873bce4f6 100755
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -443,115 +443,9 @@ function print_usage() {
     echo ""
     echo "  NOTE: To see if any Solr servers are running, do: solr status"
     echo ""
-  elif [ "$CMD" == "zk" ]; then
-    print_short_zk_usage ""
-    echo "         Can be run on remote (non-Solr) hosts, as long as valid 
ZK_HOST information is provided"
-    echo "         Be sure to check the Solr logs in case of errors."
-    echo ""
-    echo "             -z zkHost          Optional Zookeeper connection string 
for all commands. If specified it"
-    echo "                                  overrides the 'ZK_HOST=...'' 
defined in solr.in.sh."
-    echo ""
-    echo "             -s solrUrl         Optional Solr URL to look up the 
correct zkHost connection string via."
-    echo ""
-    echo "             -v/--verbose       Enable more verbose output for this 
script."
-    echo ""
-    echo "         upconfig uploads a configset from the local machine to 
Zookeeper."
-    echo ""
-    echo "         downconfig downloads a configset from Zookeeper to the 
local machine."
-    echo ""
-    echo "             -n <configName>    Name of the configset in Zookeeper 
that will be the destination of"
-    echo "                                  'upconfig' and the source for 
'downconfig'."
-    echo ""
-    echo "             -d <confdir>       The local directory the 
configuration will be uploaded from for"
-    echo "                                  'upconfig' or downloaded to for 
'downconfig'. If 'confdir' is a child of"
-    echo "                                  ...solr/server/solr/configsets' 
then the configs will be copied from/to"
-    echo "                                  that directory. Otherwise it is 
interpreted as a simple local path."
-    echo ""
-    echo "         cp copies files or folders to/from Zookeeper or Zookeeper 
-> Zookeeper"
-    echo ""
-    echo "             -r       Recursively copy <src> to <dst>. Command will 
fail if <src> has children and "
-    echo "                        -r is not specified. Optional"
-    echo ""
-    echo "             <src>, <dest> : [file:][/]path/to/local/file or 
zk:/path/to/zk/node"
-    echo "                             NOTE: <src> and <dest> may both be 
Zookeeper resources prefixed by 'zk:'"
-    echo "             When <src> is a zk resource, <dest> may be '.'"
-    echo "             If <dest> ends with '/', then <dest> will be a local 
folder or parent znode and the last"
-    echo "             element of the <src> path will be appended unless <src> 
also ends in a slash. "
-    echo "             <dest> may be zk:, which may be useful when using the 
cp -r form to backup/restore "
-    echo "             the entire zk state."
-    echo "             You must enclose local paths that end in a wildcard in 
quotes or just"
-    echo "             end the local path in a slash. That is,"
-    echo "             'bin/solr zk cp -r /some/dir/ zk:/ -z localhost:2181' 
is equivalent to"
-    echo "             'bin/solr zk cp -r \"/some/dir/*\" zk:/ -z 
localhost:2181'"
-    echo "             but 'bin/solr zk cp -r /some/dir/* zk:/ -z 
localhost:2181' will throw an error"
-    echo ""
-    echo "             here's an example of backup/restore for a ZK 
configuration:"
-    echo "             to copy to local: 'bin/solr zk cp -r zk:/ /some/dir -z 
localhost:2181'"
-    echo "             to restore to ZK: 'bin/solr zk cp -r /some/dir/ zk:/ -z 
localhost:2181'"
-    echo ""
-    echo "             The 'file:' prefix is stripped, thus 'file:/wherever' 
specifies an absolute local path and"
-    echo "             'file:somewhere' specifies a relative local path. All 
paths on Zookeeper are absolute."
-    echo ""
-    echo "             Zookeeper nodes CAN have data, so moving a single file 
to a parent znode"
-    echo "             will overlay the data on the parent Znode so specifying 
the trailing slash"
-    echo "             can be important."
-    echo ""
-    echo "             Wildcards are supported when copying from local, 
trailing only and must be quoted."
-    echo ""
-    echo "         rm deletes files or folders on Zookeeper"
-    echo ""
-    echo "             -r       Recursively delete if <path> is a directory. 
Command will fail if <path>"
-    echo "                        has children and -r is not specified. 
Optional"
-    echo "             <path> : [zk:]/path/to/zk/node. <path> may not be the 
root ('/')"
-    echo ""
-    echo "         mv moves (renames) znodes on Zookeeper"
-    echo ""
-    echo "             <src>, <dest> : Zookeeper nodes, the 'zk:' prefix is 
optional."
-    echo "             If <dest> ends with '/', then <dest> will be a parent 
znode"
-    echo "             and the last element of the <src> path will be 
appended."
-    echo "             Zookeeper nodes CAN have data, so moving a single file 
to a parent znode"
-    echo "             will overlay the data on the parent Znode so specifying 
the trailing slash"
-    echo "             is important."
-    echo ""
-    echo "         ls lists the znodes on Zookeeper"
-    echo ""
-    echo "             -r       Recursively descends the path listing all 
znodes. Optional"
-    echo "             <path>:  The Zookeeper path to use as the root."
-    echo ""
-    echo "             Only the node names are listed, not data"
-    echo ""
-    echo "         mkroot makes a znode in Zookeeper with no data. Can be used 
to make a path of arbitrary"
-    echo "             depth but primarily intended to create a 'chroot'."
-    echo ""
-    echo "             <path>:  The Zookeeper path to create. Leading slash is 
assumed if not present."
-    echo "                        Intermediate nodes are created as needed if 
not present."
-    echo ""
   fi
 } # end print_usage
 
-function print_short_zk_usage() {
-
-  if [ "$1" != "" ]; then
-    echo -e "\nERROR: $1\n"
-  fi
-
-  echo "  Usage: solr zk upconfig|downconfig -d <confdir> -n <configName> [-z 
zkHost] [-s solrUrl]"
-  echo "         solr zk cp [-r] <src> <dest> [-z zkHost] [-s solrUrl]"
-  echo "         solr zk rm [-r] <path> [-z zkHost] [-s solrUrl]"
-  echo "         solr zk mv <src> <dest> [-z zkHost] [-s solrUrl]"
-  echo "         solr zk ls [-r] <path> [-z zkHost] [-s solrUrl]"
-  echo "         solr zk mkroot <path> [-z zkHost] [-s solrUrl]"
-  echo "         solr zk linkconfig --conf-name <confname> -c <collection> [-z 
zkHost] [-s solrUrl]"
-  echo "         solr zk updateacls <path> [-z zkHost] [-s solrUrl]"
-  echo ""
-
-  if [ "$1" == "" ]; then
-    echo "Type bin/solr zk --help for full usage help"
-  else
-    exit 1
-  fi
-}
-
 # used to show the script is still alive when waiting on work to complete
 function spinner() {
   local pid=$1
@@ -747,42 +641,6 @@ if [ "$SCRIPT_CMD" == "status" ]; then
   exit $?
 fi
 
-# assert tool
-if [ "$SCRIPT_CMD" == "assert" ]; then
-  run_tool assert "$@"
-  exit $?
-fi
-
-# healthcheck tool
-if [ "$SCRIPT_CMD" == "healthcheck" ]; then
-  run_tool healthcheck "$@"
-  exit $?
-fi
-
-# config tool
-if [ "$SCRIPT_CMD" == "config" ]; then
-  run_tool config "$@"
-  exit $?
-fi
-
-# create a core or collection
-if [[ "$SCRIPT_CMD" == "create" ]]; then
-  run_tool $SCRIPT_CMD $@
-  exit $?
-fi
-
-# delete a core or collection
-if [[ "$SCRIPT_CMD" == "delete" ]]; then
-  run_tool $SCRIPT_CMD $@
-  exit $?
-fi
-
-# manage packages
-if [[ "$SCRIPT_CMD" == "package" ]]; then
-  run_tool $SCRIPT_CMD $@
-  exit $?
-fi
-
 # configure authentication
 if [[ "$SCRIPT_CMD" == "auth" ]]; then
   : "${SOLR_SERVER_DIR:=$DEFAULT_SERVER_DIR}"
@@ -817,208 +675,20 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
   exit $?
 fi
 
-# Prevent any zk subcommands from going through with out invoking zk command
-if [[ "$SCRIPT_CMD" == "upconfig" || $SCRIPT_CMD == "downconfig" || 
$SCRIPT_CMD == "cp" || $SCRIPT_CMD == "rm" || $SCRIPT_CMD == "mv" || 
$SCRIPT_CMD == "ls" || $SCRIPT_CMD == "mkroot" || $SCRIPT_CMD == "linkconfig" 
|| $SCRIPT_CMD == "updateacls" ]]; then
-  print_short_zk_usage "You must invoke this subcommand using the zk command.  
 bin/solr zk $SCRIPT_CMD."
-  exit $?
-fi
-
-ZK_RECURSE=false
-# Zookeeper file maintenance (upconfig, downconfig, files up/down etc.)
-# It's a little clumsy to have the parsing go round and round for upconfig and 
downconfig, but that's
-# necessary for back-compat
-if [[ "$SCRIPT_CMD" == "zk" ]]; then
-
-  VERBOSE=""
-  ZK_SOLR_URL=""
-
-  if [ $# -gt 0 ]; then
-    while true; do
-      case "${1:-}" in
-        upconfig|downconfig|cp|rm|mv|ls|mkroot|linkconfig|updateacls)
-            ZK_OP=$1
-            shift 1
-        ;;
-        -z|--zk-host|-zkHost|--zkHost)
-            if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
-              print_short_zk_usage "$SCRIPT_CMD" "ZooKeeper connection string 
is required when using the $1 option!"
-            fi
-            ZK_HOST="$2"
-            shift 2
-        ;;
-        -s|--solr-url|-solrUrl)
-            if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
-              print_short_zk_usage "$SCRIPT_CMD" "Solr Url is required when 
using the $1 option!"
-            fi
-            ZK_SOLR_URL="$2"
-            shift 2
-        ;;
-        -n|--confname|--conf-name)
-            if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
-              print_short_zk_usage "$SCRIPT_CMD" "Configuration name is 
required when using the $1 option!"
-            fi
-            CONFIGSET_CONFNAME="$2"
-            shift 2
-        ;;
-        -d|--confdir|--conf-dir)
-            if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
-              print_short_zk_usage "$SCRIPT_CMD" "Configuration directory is 
required when using the $1 option!"
-            fi
-            CONFIGSET_CONFDIR="$2"
-            shift 2
-        ;;
-        --solr-home)
-            ZK_SOLR_HOME="$2"
-            shift 2
-        ;;
-        -r)
-            ZK_RECURSE="true"
-            shift
-        ;;
-        -c|-collection)
-            ZK_COLLECTION="$2"
-            shift
-        ;;
-        -V|-verbose|--verbose)
-            VERBOSE="--verbose"
-            shift
-        ;;
-        --help|-h|-help)
-            print_usage "$SCRIPT_CMD"
-            exit 0
-        ;;
-        --)
-            shift
-            break
-        ;;
-        *)  # Pick up <src> <dst> or <path> params for rm, ls, cp, mv, mkroot.
-            if [ -z "${1:-}" ]; then
-              break # out-of-args, stop looping
-            fi
-            if [ -z "${ZK_SRC:-}" ]; then
-              ZK_SRC=$1
-            else
-              if [ -z "${ZK_DST:-}" ]; then
-                ZK_DST=$1
-              else
-                print_short_zk_usage "Unrecognized or misplaced command $1. 
'cp' with trailing asterisk requires quoting, see help text."
-              fi
-            fi
-            shift
-        ;;
-      esac
-    done
-  fi
-
-  if [ -z "$ZK_OP" ]; then
-    print_short_zk_usage "Zookeeper operation (one of 'upconfig', 
'downconfig', 'rm', 'mv', 'cp', 'ls', 'mkroot', 'linkconfig', 'updateacls') is 
required!"
-  fi
-
-  if [[ "$ZK_OP" == "upconfig" ||  "$ZK_OP" == "downconfig" ]]; then
-    if [ -z "$CONFIGSET_CONFDIR" ]; then
-      print_short_zk_usage "Local directory of the configset (-d) argument is 
required!"
-    fi
-
-    if [ -z "$CONFIGSET_CONFNAME" ]; then
-      print_short_zk_usage "Configset name on Zookeeper (-n) argument is 
required!"
-    fi
-  fi
-
-  if [[ "$ZK_OP" == "cp" || "$ZK_OP" == "mv" ]]; then
-    if [[ -z "$ZK_SRC" || -z "$ZK_DST" ]]; then
-      print_short_zk_usage "<source> and <destination> must be specified when 
using either the 'mv' or 'cp' commands."
-    fi
-    if [[ "$ZK_OP" == "cp" && "${ZK_SRC:0:3}" != "zk:" && "${ZK_DST:0:3}" != 
"zk:" ]]; then
-      print_short_zk_usage "One of the source or destination paths must be 
prefixed by 'zk:' for the 'cp' command."
-    fi
-  fi
-
-  if [[ "$ZK_OP" == "mkroot" ]]; then
-    if [[ -z "$ZK_SRC" ]]; then
-      print_short_zk_usage "<path> must be specified when using the 'mkroot' 
command."
-    fi
-  fi
-
-  CONNECTION_PARAMS=""
-
-  if [[ -z "$ZK_HOST" ]]; then
-    if [[ -z "$ZK_SOLR_URL" ]]; then
-      CONNECTION_PARAMS=""
-    else
-      CONNECTION_PARAMS="--solr-url ${ZK_SOLR_URL}"
-    fi
-
-  else
-    CONNECTION_PARAMS="--zk-host ${ZK_HOST}"
-  fi
-
-  case "$ZK_OP" in
-    upconfig)
-      run_tool "$ZK_OP" --conf-name "$CONFIGSET_CONFNAME" --conf-dir 
"$CONFIGSET_CONFDIR" $CONNECTION_PARAMS $VERBOSE
-    ;;
-    downconfig)
-      run_tool "$ZK_OP" --conf-name "$CONFIGSET_CONFNAME" --conf-dir 
"$CONFIGSET_CONFDIR" $CONNECTION_PARAMS $VERBOSE
-    ;;
-    rm)
-      if [ -z "$ZK_SRC" ]; then
-        print_short_zk_usage "Zookeeper path to remove must be specified when 
using the 'rm' command"
-      fi
-      run_tool "$ZK_OP" --path "$ZK_SRC" $CONNECTION_PARAMS --recurse 
"$ZK_RECURSE" $VERBOSE
-    ;;
-    mv)
-      run_tool "$ZK_OP" --source "$ZK_SRC" --destination "$ZK_DST" 
$CONNECTION_PARAMS $VERBOSE
-    ;;
-    cp)
-      if [ -z "$ZK_SOLR_HOME" ]; then
-        run_tool "$ZK_OP" --source "$ZK_SRC" --destination "$ZK_DST" 
$CONNECTION_PARAMS --recurse "$ZK_RECURSE" $VERBOSE
-      else
-        run_tool "$ZK_OP" --source "$ZK_SRC" --destination "$ZK_DST" 
$CONNECTION_PARAMS --recurse "$ZK_RECURSE" $VERBOSE --solr-home "$ZK_SOLR_HOME"
-      fi
-
-    ;;
-    ls)
-      if [ -z "$ZK_SRC" ]; then
-        print_short_zk_usage "Zookeeper path to list must be specified when 
using the 'ls' command"
-      fi
-      run_tool "$ZK_OP" --path "$ZK_SRC" --recurse "$ZK_RECURSE" 
$CONNECTION_PARAMS $VERBOSE
-    ;;
-    mkroot)
-      if [ -z "$ZK_SRC" ]; then
-        print_short_zk_usage "Zookeeper path to list must be specified when 
using the 'mkroot' command"
-      fi
-      run_tool "$ZK_OP" --path "$ZK_SRC" $CONNECTION_PARAMS $VERBOSE
-    ;;
-    linkconfig)
-      run_tool "$ZK_OP" --conf-name "$CONFIGSET_CONFNAME" -c "$ZK_COLLECTION" 
$CONNECTION_PARAMS $VERBOSE
-    ;;
-    updateacls)
-      run_tool "$ZK_OP" --path "$ZK_SRC" $CONNECTION_PARAMS $VERBOSE
-    ;;
-    *)
-      print_short_zk_usage "Unrecognized Zookeeper operation $ZK_OP"
-    ;;
-  esac
-
-  exit $?
-fi
-
-if [[ "$SCRIPT_CMD" == "api" ]]; then
-  run_tool api "$@"
-  exit $?
-fi
-
-# verify the command given is supported
-if [ "$SCRIPT_CMD" != "stop" ] && [ "$SCRIPT_CMD" != "start" ] && [ 
"$SCRIPT_CMD" != "restart" ] && [ "$SCRIPT_CMD" != "status" ]; then
-  # handoff this command to the SolrCLI and let it handle the option parsing 
and validation
+# at this point all tools that have a custom run process, like "status" and 
"auth" have been run and exited.  
+# Unless a command is one of the ones in the if clause below, we will just run 
it with the default run_tool function and then exit.
+if [ "$SCRIPT_CMD" != "start" ] && [ "$SCRIPT_CMD" != "stop" ] && [ 
"$SCRIPT_CMD" != "restart" ]; then
+  # hand off the command to the SolrCLI and let it handle the option parsing 
and validation
   run_tool "$SCRIPT_CMD" "$@"
   ret=$?
   if [ $ret -ne 0 ]; then
-    print_usage ""
     exit $ret
   fi
   exit 0
 fi
 
+# Everything below here is to support start, stop and restart.
+
 #Check current Ulimits for Open Files and Max Processes.  Warn if they are 
below the recommended values.
 
 : "${SOLR_RECOMMENDED_MAX_PROCESSES:=65000}"
diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd
index 58fb4e6599e..a07b4575c7d 100755
--- a/solr/bin/solr.cmd
+++ b/solr/bin/solr.cmd
@@ -258,6 +258,7 @@ IF "%1"=="version" goto run_solrcli
 IF "%1"=="-v" goto run_solrcli
 IF "%1"=="-version" goto run_solrcli
 IF "%1"=="assert" goto run_solrcli
+IF "%1"=="zk" goto run_solrcli
 IF "%1"=="export" goto run_solrcli
 IF "%1"=="package" goto run_solrcli
 IF "%1"=="auth" goto run_solrcli
@@ -272,12 +273,7 @@ IF "%1"=="healthcheck" goto run_solrcli
 IF "%1"=="create" goto run_solrcli
 IF "%1"=="delete" goto run_solrcli
 IF "%1"=="postlogs" goto run_solrcli
-IF "%1"=="zk" (
-  set SCRIPT_CMD=zk
-  SHIFT
-  set ZK_RECURSE=false
-  goto parse_zk_args
-)
+
 IF "%1"=="auth" (
   set SCRIPT_CMD=auth
   SHIFT
@@ -302,7 +298,7 @@ IF "%SCRIPT_CMD%"=="healthcheck" goto run_solrcli
 IF "%SCRIPT_CMD%"=="create" goto run_solrcli
 IF "%SCRIPT_CMD%"=="delete" goto run_solrcli
 IF "%SCRIPT_CMD%"=="cluster" goto run_solrcli
-IF "%SCRIPT_CMD%"=="zk" goto zk_usage
+IF "%SCRIPT_CMD%"=="zk" goto run_solrcli
 IF "%SCRIPT_CMD%"=="auth" goto run_solrcli
 IF "%SCRIPT_CMD%"=="package" goto run_solrcli
 IF "%SCRIPT_CMD%"=="status" goto run_solrcli
@@ -388,110 +384,6 @@ goto done
 @echo.
 goto done
 
-:zk_usage
-set ZK_FULL=true
-goto zk_short_usage
-:zk_full_usage
-echo         Can be run on remote (non-Solr^) hosts, as long as valid ZK_HOST 
information is provided.
-echo         Be sure to check the Solr logs in case of errors.
-echo.
-echo             -z zkHost       Optional Zookeeper connection string for all 
commands. If specified it
-echo                             overrides the 'ZK_HOST=...'' defined in 
solr.in.cmd.
-echo.
-echo             -s solrUrl      Optional Solr URL to look up the correct 
zkHost connection string via.
-echo.
-echo             -V              Enable more verbose output.
-echo.
-echo         upconfig uploads a configset from the local machine to Zookeeper.
-echo.
-echo         downconfig downloads a configset from Zookeeper to the local 
machine.
-echo.
-echo             -n configName   Name of the configset in Zookeeper that will 
be the destination of
-echo                             'upconfig' and the source for 'downconfig'.
-echo.
-echo             -d confdir      The local directory the configuration will be 
uploaded from for
-echo                             'upconfig' or downloaded to for 'downconfig'. 
If 'confdir' is a child of
-echo                             ...solr/server/solr/configsets' then the 
configs will be copied from/to
-echo                             that directory. Otherwise it is interpreted 
as a simple local path.
-echo.
-echo         cp copies files or folders to/from Zookeeper or Zookeeper -^> 
Zookeeper
-echo             -r              Recursively copy ^<src^> to ^<dst^>. Command 
will fail if ^<src^> has children and
-echo                             -r is not specified. Optional
-echo.
-echo.             ^<src^>, ^<dest^> : [file:][/]path/to/local/file or 
zk:/path/to/zk/node
-echo                              NOTE: ^<src^> and ^<dest^> may both be 
Zookeeper resources prefixed by 'zk:'
-echo             When ^<src^> is a zk resource, ^<dest^> may be '.'
-echo             If ^<dest^> ends with '/', then ^<dest^> will be a local 
folder or parent znode and the last
-echo             element of the ^<src^> path will be appended unless ^<src^> 
also ends in a slash.
-echo             ^<dest^> may be zk:, which may be useful when using the cp -r 
form to backup/restore
-echo             the entire zk state.
-echo             You must enclose local paths that end in a wildcard in quotes 
or just
-echo             end the local path in a slash. That is,
-echo             'bin/solr zk cp -r /some/dir/ zk:/ -z localhost:2181' is 
equivalent to
-echo             'bin/solr zk cp -r ^"/some/dir/*^" zk:/ -z localhost:2181'
-echo             but 'bin/solr zk cp -r /some/dir/* zk:/ -z localhost:2181' 
will throw an error.
-echo.
-echo             Here's an example of backup/restore for a ZK configuration:
-echo             to copy to local: 'bin/solr zk cp -r zk:/ /some/dir -z 
localhost:2181'
-echo             to restore to ZK: 'bin/solr zk cp -r /some/dir/ zk:/ -z 
localhost:2181'
-echo.
-echo             The 'file:' prefix is stripped, thus 'file:/wherever' 
specifies an absolute local path and
-echo             'file:somewhere' specifies a relative local path. All paths 
on Zookeeper are absolute.
-echo.
-echo             Zookeeper nodes CAN have data, so moving a single file to a 
parent znode
-echo             will overlay the data on the parent Znode so specifying the 
trailing slash
-echo             can be important.
-echo.
-echo             Wildcards are supported when copying from local, trailing 
only and must be quoted.
-echo.
-echo         rm deletes files or folders on Zookeeper
-echo             -r     Recursively delete if ^<path^> is a directory. Command 
will fail if ^<path^>
-echo                    has children and -r is not specified. Optional
-echo             ^<path^> : [zk:]/path/to/zk/node. ^<path^> may not be the 
root ('/')
-echo.
-echo         mv moves (renames) znodes on Zookeeper
-echo             ^<src^>, ^<dest^> : Zookeeper nodes, the 'zk:' prefix is 
optional.
-echo             If ^<dest^> ends with '/', then ^<dest^> will be a parent 
znode
-echo             and the last element of the ^<src^> path will be appended.
-echo             Zookeeper nodes CAN have data, so moving a single file to a 
parent znode
-echo             will overlay the data on the parent Znode so specifying the 
trailing slash
-echo             is important.
-echo.
-echo         ls lists the znodes on Zookeeper
-echo             -r recursively descends the path listing all znodes. Optional
-echo             ^<path^>: The Zookeeper path to use as the root.
-echo.
-echo             Only the node names are listed, not data
-echo.
-echo         mkroot makes a znode in Zookeeper with no data. Can be used to 
make a path of arbitrary
-echo                depth but primarily intended to create a 'chroot'.
-echo.
-echo             ^<path^>: The Zookeeper path to create. Leading slash is 
assumed if not present.
-echo                       Intermediate nodes are created as needed if not 
present.
-echo.
-
-goto done
-
-:zk_short_usage
-IF NOT "!ERROR_MSG!"=="" (
-  echo  ERROR: !ERROR_MSG!
-  echo.
-)
-echo  Usage: solr zk upconfig^|downconfig -d ^<confdir^> -n ^<configName^> [-z 
zkHost] [-s solrUrl]
-echo         solr zk cp [-r] ^<src^> ^<dest^> [-z zkHost] [-s solrUrl]
-echo         solr zk rm [-r] ^<path^> [-z zkHost] [-s solrUrl]
-echo         solr zk mv ^<src^> ^<dest^> [-z zkHost] [-s solrUrl]
-echo         solr zk ls [-r] ^<path^> [-z zkHost] [-s solrUrl]
-echo         solr zk mkroot ^<path^> [-z zkHost] [-s solrUrl]
-echo         solr zk linkconfig --conf-name ^<confname^> -c ^<collection^> [-z 
zkHost] [-s solrUrl]
-echo         solr zk updateacls ^<path^> [-z zkHost] [-s solrUrl]
-echo.
-IF "%ZK_FULL%"=="true" (
-  goto zk_full_usage
-) ELSE (
-  echo Type bin/solr zk --help for full usage help
-)
-goto done
 
 REM Really basic command-line arg parsing
 :parse_args
@@ -512,6 +404,7 @@ IF "%1"=="-v" goto set_debug
 IF "%1"=="-q" goto set_warn
 IF "%1"=="-c" goto set_cloud_mode
 IF "%1"=="-cloud" goto set_cloud_mode
+IF "%1"=="--cloud" goto set_cloud_mode
 IF "%1"=="-d" goto set_server_dir
 IF "%1"=="--dir" goto set_server_dir
 IF "%1"=="-s" goto set_solr_home_dir
@@ -1411,12 +1304,6 @@ IF "%1"=="-V" (
   goto set_collection_zk
 ) ELSE IF "%1"=="-z" (
   goto set_config_zk
-) ELSE IF "%1"=="/?" (
-  goto zk_usage
-) ELSE IF "%1"=="-h" (
-  goto zk_usage
-) ELSE IF "%1"=="-help" (
-  goto zk_usage
 ) ELSE IF "!ZK_SRC!"=="" (
   if not "%~1"=="" (
     goto set_zk_src
diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java 
b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java
index 87ca00c1705..d9322485dc7 100644
--- a/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetDownloadTool.java
@@ -23,6 +23,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DeprecatedAttributes;
 import org.apache.commons.cli.Option;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.slf4j.Logger;
@@ -43,18 +44,44 @@ public class ConfigSetDownloadTool extends ToolBase {
   @Override
   public List<Option> getOptions() {
     return List.of(
-        Option.builder()
+        Option.builder("n")
             .longOpt("conf-name")
             .hasArg()
             .argName("NAME")
-            .required(true)
+            .required(false) // should be true, but we have deprecated option 
as well.
             .desc("Configset name in ZooKeeper.")
             .build(),
         Option.builder()
+            .longOpt("confname")
+            .hasArg()
+            .argName("NAME")
+            .deprecated(
+                DeprecatedAttributes.builder()
+                    .setForRemoval(true)
+                    .setSince("9.8")
+                    .setDescription("Use --conf-name instead")
+                    .get())
+            .required(false)
+            .desc("Configset name in ZooKeeper.")
+            .build(),
+        Option.builder("d")
             .longOpt("conf-dir")
             .hasArg()
             .argName("DIR")
-            .required(true)
+            .required(false) // should be true, but we have deprecated option 
as well.
+            .desc("Local directory with configs.")
+            .build(),
+        Option.builder()
+            .longOpt("confdir")
+            .hasArg()
+            .argName("NAME")
+            .deprecated(
+                DeprecatedAttributes.builder()
+                    .setForRemoval(true)
+                    .setSince("9.8")
+                    .setDescription("Use --conf-dir instead")
+                    .get())
+            .required(false)
             .desc("Local directory with configs.")
             .build(),
         SolrCLI.OPTION_SOLRURL,
@@ -69,6 +96,11 @@ public class ConfigSetDownloadTool extends ToolBase {
     return "downconfig";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk downconfig [-d <DIR>] [-n <NAME>] [-s <HOST>] [-u 
<credentials>] [-z <HOST>]";
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
@@ -76,8 +108,15 @@ public class ConfigSetDownloadTool extends ToolBase {
 
     try (SolrZkClient zkClient = SolrCLI.getSolrZkClient(cli, zkHost)) {
       echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
-      String confName = cli.getOptionValue("conf-name");
-      String confDir = cli.getOptionValue("conf-dir");
+      String confName =
+          cli.hasOption("conf-name")
+              ? cli.getOptionValue("conf-name")
+              : cli.getOptionValue("confname");
+      String confDir =
+          cli.hasOption("conf-dir")
+              ? cli.getOptionValue("conf-dir")
+              : cli.getOptionValue("confdir");
+
       Path configSetPath = Paths.get(confDir);
       // we try to be nice about having the "conf" in the directory, and we 
create it if it's not
       // there.
diff --git a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java 
b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java
index bc0dde9c3f4..84faa2f2d2d 100644
--- a/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ConfigSetUploadTool.java
@@ -22,6 +22,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DeprecatedAttributes;
 import org.apache.commons.cli.Option;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkMaintenanceUtils;
@@ -49,14 +50,40 @@ public class ConfigSetUploadTool extends ToolBase {
             .longOpt("conf-name")
             .hasArg()
             .argName("NAME")
-            .required(true)
+            .required(false) // should be true, but we have deprecated option 
as well.
+            .desc("Configset name in ZooKeeper.")
+            .build(),
+        Option.builder()
+            .longOpt("confname")
+            .hasArg()
+            .argName("NAME")
+            .deprecated(
+                DeprecatedAttributes.builder()
+                    .setForRemoval(true)
+                    .setSince("9.8")
+                    .setDescription("Use --conf-name instead")
+                    .get())
+            .required(false)
             .desc("Configset name in ZooKeeper.")
             .build(),
         Option.builder("d")
             .longOpt("conf-dir")
             .hasArg()
             .argName("DIR")
-            .required(true)
+            .required(false) // should be true, but we have deprecated option 
as well.
+            .desc("Local directory with configs.")
+            .build(),
+        Option.builder()
+            .longOpt("confdir")
+            .hasArg()
+            .argName("DIR")
+            .deprecated(
+                DeprecatedAttributes.builder()
+                    .setForRemoval(true)
+                    .setSince("9.8")
+                    .setDescription("Use --conf-dir instead")
+                    .get())
+            .required(false)
             .desc("Local directory with configs.")
             .build(),
         SolrCLI.OPTION_SOLRURL,
@@ -71,6 +98,11 @@ public class ConfigSetUploadTool extends ToolBase {
     return "upconfig";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk upconfig [-d <DIR>] [-n <NAME>] [-s <HOST>] [-u 
<credentials>] [-z <HOST>]";
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
@@ -79,14 +111,17 @@ public class ConfigSetUploadTool extends ToolBase {
     final String solrInstallDir = System.getProperty("solr.install.dir");
     Path solrInstallDirPath = Paths.get(solrInstallDir);
 
-    String confName = cli.getOptionValue("conf-name");
+    String confName =
+        cli.hasOption("conf-name")
+            ? cli.getOptionValue("conf-name")
+            : cli.getOptionValue("confname");
+    String confDir =
+        cli.hasOption("conf-dir") ? cli.getOptionValue("conf-dir") : 
cli.getOptionValue("confdir");
     try (SolrZkClient zkClient = SolrCLI.getSolrZkClient(cli, zkHost)) {
       echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
 
       final Path configsetsDirPath = 
SolrCLI.getConfigSetsDir(solrInstallDirPath);
-      Path confPath =
-          ConfigSetService.getConfigsetPath(
-              cli.getOptionValue("conf-dir"), configsetsDirPath.toString());
+      Path confPath = ConfigSetService.getConfigsetPath(confDir, 
configsetsDirPath.toString());
 
       echo(
           "Uploading "
diff --git a/solr/core/src/java/org/apache/solr/cli/CreateTool.java 
b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
index 61031ae5cc9..e678e44d725 100644
--- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
@@ -76,7 +76,7 @@ public class CreateTool extends ToolBase {
             .required(true)
             .desc("Name of collection or core to create.")
             .build(),
-        Option.builder("s")
+        Option.builder("sh")
             .longOpt("shards")
             .hasArg()
             .argName("#")
diff --git a/solr/core/src/java/org/apache/solr/cli/LinkConfigTool.java 
b/solr/core/src/java/org/apache/solr/cli/LinkConfigTool.java
index f94cd5a2b7f..1e48a890f3b 100644
--- a/solr/core/src/java/org/apache/solr/cli/LinkConfigTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/LinkConfigTool.java
@@ -42,6 +42,11 @@ public class LinkConfigTool extends ToolBase {
     return "linkconfig";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk linkconfig -c <NAME> -n <NAME> [-z <HOST>]";
+  }
+
   @Override
   public List<Option> getOptions() {
     return List.of(
diff --git a/solr/core/src/java/org/apache/solr/cli/PackageTool.java 
b/solr/core/src/java/org/apache/solr/cli/PackageTool.java
index c3f4a3a9e96..26362ddf41a 100644
--- a/solr/core/src/java/org/apache/solr/cli/PackageTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/PackageTool.java
@@ -245,7 +245,6 @@ public class PackageTool extends ToolBase {
   @Override
   public String getHeader() {
     StringBuilder sb = new StringBuilder();
-    format(sb, "\n");
     format(sb, "Package Manager\n---------------");
     formatGreen(sb, "bin/solr package add-repo <repository-name> 
<repository-url>");
     format(sb, "Add a repository to Solr.");
@@ -293,7 +292,6 @@ public class PackageTool extends ToolBase {
         sb,
         "      (b) Please make sure that all Solr nodes are started with 
'-Denable.packages=true' parameter.");
     format(sb, "\n");
-    format(sb, "\n");
     format(sb, "List of options:");
     return sb.toString();
   }
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java 
b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
index 07582cf3a29..b7a74bb21af 100755
--- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
@@ -37,6 +37,7 @@ import java.util.Enumeration;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.TreeSet;
@@ -153,14 +154,11 @@ public class SolrCLI implements CLIO {
   public static final Option OPTION_HELP =
       Option.builder("h").longOpt("help").required(false).desc("Print this 
message.").build();
 
-  // should this be boolean or just an option?
   public static final Option OPTION_RECURSE =
       Option.builder("r")
           .longOpt("recurse")
-          .argName("recurse")
-          .hasArg()
           .required(false)
-          .desc("Recurse (true|false), default is false.")
+          .desc("Apply the command recursively.")
           .build();
 
   public static final Option OPTION_CREDENTIALS =
@@ -198,7 +196,38 @@ public class SolrCLI implements CLIO {
       // select the version tool to be run
       args[0] = "version";
     }
-
+    if (Arrays.asList(
+            "upconfig", "downconfig", "cp", "rm", "mv", "ls", "mkroot", 
"linkconfig", "updateacls")
+        .contains(args[0])) {
+      // remap our arguments to invoke the zk short tool help
+      args = new String[] {"zk-tool-help", "--print-zk-subcommand-usage", 
args[0]};
+    }
+    if (Objects.equals(args[0], "zk")) {
+      if (args.length == 1) {
+        // remap our arguments to invoke the ZK tool help.
+        args = new String[] {"zk-tool-help", "--print-long-zk-usage"};
+      } else if (args.length == 2) {
+        if (Arrays.asList("-h", "--help", "/?").contains(args[1])) {
+          // remap our arguments to invoke the ZK tool help.
+          args = new String[] {"zk-tool-help", "--print-long-zk-usage"};
+        } else {
+          // remap our arguments to invoke the zk sub command with help
+          String[] trimmedArgs = new String[args.length - 1];
+          System.arraycopy(args, 1, trimmedArgs, 0, trimmedArgs.length);
+          args = trimmedArgs;
+
+          String[] remappedArgs = new String[args.length + 1];
+          System.arraycopy(args, 0, remappedArgs, 0, args.length);
+          remappedArgs[remappedArgs.length - 1] = "--help";
+          args = remappedArgs;
+        }
+      } else {
+        // chop the leading zk argument so we invoke the correct zk sub tool
+        String[] trimmedArgs = new String[args.length - 1];
+        System.arraycopy(args, 1, trimmedArgs, 0, trimmedArgs.length);
+        args = trimmedArgs;
+      }
+    }
     SSLConfigurationsFactory.current().init();
 
     Tool tool = null;
@@ -295,6 +324,7 @@ public class SolrCLI implements CLIO {
     else if ("run_example".equals(toolType)) return new RunExampleTool();
     else if ("upconfig".equals(toolType)) return new ConfigSetUploadTool();
     else if ("downconfig".equals(toolType)) return new ConfigSetDownloadTool();
+    else if ("zk-tool-help".equals(toolType)) return new ZkToolHelp();
     else if ("rm".equals(toolType)) return new ZkRmTool();
     else if ("mv".equals(toolType)) return new ZkMvTool();
     else if ("cp".equals(toolType)) return new ZkCpTool();
@@ -339,7 +369,6 @@ public class SolrCLI implements CLIO {
   /** Parses the command-line arguments passed by the user. */
   public static CommandLine processCommandLineArgs(Tool tool, String[] args) {
     List<Option> customOptions = tool.getOptions();
-    String toolName = tool.getName();
     Options options = new Options();
 
     options.addOption(OPTION_HELP);
@@ -384,9 +413,8 @@ public class SolrCLI implements CLIO {
   }
 
   /** Prints tool help for a given tool */
-  private static void printToolHelp(Tool tool) {
-    HelpFormatter formatter = HelpFormatter.builder().get();
-    formatter.setWidth(120);
+  public static void printToolHelp(Tool tool) {
+    HelpFormatter formatter = getFormatter();
     Options optionsNoDeprecated = new Options();
     tool.getOptions().stream()
         .filter(option -> !option.isDeprecated())
@@ -394,7 +422,17 @@ public class SolrCLI implements CLIO {
     String usageString = tool.getUsage() == null ? "bin/solr " + 
tool.getName() : tool.getUsage();
     boolean autoGenerateUsage = tool.getUsage() == null;
     formatter.printHelp(
-        usageString, tool.getHeader(), optionsNoDeprecated, tool.getFooter(), 
autoGenerateUsage);
+        usageString,
+        "\n" + tool.getHeader(),
+        optionsNoDeprecated,
+        tool.getFooter(),
+        autoGenerateUsage);
+  }
+
+  public static HelpFormatter getFormatter() {
+    HelpFormatter formatter = HelpFormatter.builder().get();
+    formatter.setWidth(120);
+    return formatter;
   }
 
   /** Scans Jar files on the classpath for Tool implementations to activate. */
diff --git a/solr/core/src/java/org/apache/solr/cli/Tool.java 
b/solr/core/src/java/org/apache/solr/cli/Tool.java
index aec76be8083..0edde7cdae2 100644
--- a/solr/core/src/java/org/apache/solr/cli/Tool.java
+++ b/solr/core/src/java/org/apache/solr/cli/Tool.java
@@ -39,7 +39,7 @@ public interface Tool {
    * Optional header to display before the options in help output. Defaults to 
'List of options:'
    */
   default String getHeader() {
-    return "\nList of options:";
+    return "List of options:";
   }
 
   /**
diff --git a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java 
b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java
index 66804cecb48..f98959a89c7 100644
--- a/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/UpdateACLTool.java
@@ -47,25 +47,21 @@ public class UpdateACLTool extends ToolBase {
     return "updateacls";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk updateacls [-z <HOST>";
+  }
+
   @Override
   public List<Option> getOptions() {
-    return List.of(
-        Option.builder()
-            .longOpt("path")
-            .argName("PATH")
-            .hasArg()
-            .required(true)
-            .desc("The path to update.")
-            .build(),
-        SolrCLI.OPTION_ZKHOST,
-        SolrCLI.OPTION_ZKHOST_DEPRECATED);
+    return List.of(SolrCLI.OPTION_ZKHOST, SolrCLI.OPTION_ZKHOST_DEPRECATED);
   }
 
   @Override
   public void runImpl(CommandLine cli) throws Exception {
 
-    String path = cli.getOptionValue("path");
     String zkHost = SolrCLI.getZkHost(cli);
+    String path = cli.getArgs()[0];
 
     if (!ZkController.checkChrootPath(zkHost, true)) {
       throw new IllegalStateException(
diff --git a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java 
b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java
index fcac11dbc00..d7e52b7772b 100644
--- a/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ZkCpTool.java
@@ -16,6 +16,8 @@
  */
 package org.apache.solr.cli;
 
+import static org.apache.solr.packagemanager.PackageUtils.format;
+
 import java.io.PrintStream;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.InvocationTargetException;
@@ -53,20 +55,6 @@ public class ZkCpTool extends ToolBase {
   @Override
   public List<Option> getOptions() {
     return List.of(
-        Option.builder()
-            .longOpt("source")
-            .hasArg()
-            .argName("SRC")
-            .required(true)
-            .desc("Source file or directory, may be local or a Znode.")
-            .build(),
-        Option.builder()
-            .longOpt("destination")
-            .hasArg()
-            .argName("DST")
-            .required(true)
-            .desc("Destination of copy, may be local or a Znode.")
-            .build(),
         Option.builder()
             .longOpt("solr-home")
             .argName("DIR")
@@ -87,15 +75,63 @@ public class ZkCpTool extends ToolBase {
     return "cp";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk cp [-r ] [-s <HOST>] [--solr-home <DIR>] [-u 
<credentials>] [-z <HOST>] source destination";
+  }
+
+  @Override
+  public String getHeader() {
+    StringBuilder sb = new StringBuilder();
+    format(sb, "cp copies files or folders to/from Zookeeper or Zookeeper -> 
Zookeeper");
+    format(sb, "");
+    format(sb, "<src>, <dest> : [file:][/]path/to/local/file or 
zk:/path/to/zk/node");
+    format(
+        sb,
+        "                NOTE: <src> and <dest> may both be Zookeeper 
resources prefixed by 'zk:'");
+    format(sb, "When <src> is a zk resource, <dest> may be '.'");
+    format(
+        sb,
+        "If <dest> ends with '/', then <dest> will be a local folder or parent 
znode and the last");
+    format(sb, "element of the <src> path will be appended unless <src> also 
ends in a slash. ");
+    format(
+        sb, "<dest> may be zk:, which may be useful when using the cp -r form 
to backup/restore ");
+    format(sb, "the entire zk state.");
+    format(sb, "You must enclose local paths that end in a wildcard in quotes 
or just");
+    format(sb, "end the local path in a slash. That is,");
+    format(sb, "'bin/solr zk cp -r /some/dir/ zk:/ -z localhost:2181' is 
equivalent to");
+    format(sb, "'bin/solr zk cp -r \"/some/dir/*\" zk:/ -z localhost:2181'");
+    format(sb, "but 'bin/solr zk cp -r /some/dir/* zk:/ -z localhost:2181' 
will throw an error");
+    format(sb, "");
+    format(sb, "to copy to local: 'bin/solr zk cp -r zk:/ /some/dir -z 
localhost:2181'");
+    format(sb, "to restore to ZK: 'bin/solr zk cp -r /some/dir/ zk:/ -z 
localhost:2181'");
+    format(sb, "");
+    format(
+        sb,
+        "The 'file:' prefix is stripped, thus 'file:/wherever' specifies an 
absolute local path and");
+    format(
+        sb,
+        "'file:somewhere' specifies a relative local path. All paths on 
Zookeeper are absolute.");
+    format(sb, "");
+    format(sb, "Zookeeper nodes CAN have data, so moving a single file to a 
parent znode");
+    format(sb, "will overlay the data on the parent Znode so specifying the 
trailing slash");
+    format(sb, "can be important.");
+    format(sb, "");
+    format(
+        sb, "Wildcards are supported when copying from local, trailing only 
and must be quoted.");
+    format(sb, "\nList of options:");
+    return sb.toString();
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
     String zkHost = SolrCLI.getZkHost(cli);
 
     echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
-    String src = cli.getOptionValue("source");
-    String dst = cli.getOptionValue("destination");
-    Boolean recurse = Boolean.parseBoolean(cli.getOptionValue("recurse"));
+    String src = cli.getArgs()[0];
+    String dst = cli.getArgs()[1];
+    boolean recurse = cli.hasOption("recurse");
     echo("Copying from '" + src + "' to '" + dst + "'. ZooKeeper at " + 
zkHost);
 
     boolean srcIsZk = src.toLowerCase(Locale.ROOT).startsWith("zk:");
diff --git a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java 
b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java
index dfd004ef7df..27907c691c7 100644
--- a/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ZkLsTool.java
@@ -40,13 +40,6 @@ public class ZkLsTool extends ToolBase {
   @Override
   public List<Option> getOptions() {
     return List.of(
-        Option.builder()
-            .longOpt("path")
-            .argName("PATH")
-            .hasArg()
-            .required(true)
-            .desc("Path to list.")
-            .build(),
         SolrCLI.OPTION_RECURSE,
         SolrCLI.OPTION_SOLRURL,
         SolrCLI.OPTION_SOLRURL_DEPRECATED,
@@ -61,16 +54,22 @@ public class ZkLsTool extends ToolBase {
     return "ls";
   }
 
+  @Override
+  public String getUsage() {
+    // very brittle.  Maybe add a getArgsUsage to append the "path"?
+    return "bin/solr zk ls [-r ] [-s <HOST>] [-u <credentials>] [-v] [-z 
<HOST>] path";
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
     String zkHost = SolrCLI.getZkHost(cli);
+    String znode = cli.getArgs()[0];
 
     try (SolrZkClient zkClient = SolrCLI.getSolrZkClient(cli, zkHost)) {
       echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
 
-      String znode = cli.getOptionValue("path");
-      Boolean recurse = Boolean.parseBoolean(cli.getOptionValue("recurse"));
+      boolean recurse = cli.hasOption("recurse");
       echoIfVerbose(
           "Getting listing for ZooKeeper node "
               + znode
diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java 
b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java
index 436eb93de27..8fb343a4e65 100644
--- a/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ZkMkrootTool.java
@@ -16,6 +16,8 @@
  */
 package org.apache.solr.cli;
 
+import static org.apache.solr.packagemanager.PackageUtils.format;
+
 import java.io.PrintStream;
 import java.lang.invoke.MethodHandles;
 import java.util.List;
@@ -41,19 +43,11 @@ public class ZkMkrootTool extends ToolBase {
   public List<Option> getOptions() {
     return List.of(
         Option.builder()
-            .longOpt("path")
-            .argName("PATH")
-            .hasArg()
-            .required(true)
-            .desc("Path to create.")
-            .build(),
-        Option.builder() // This appears not to be wired into bin/solr 
commands.
             .longOpt("fail-on-exists")
             .hasArg()
             .required(false)
             .desc("Raise an error if the root exists.  Defaults to false.")
             .build(),
-        SolrCLI.OPTION_RECURSE,
         SolrCLI.OPTION_SOLRURL,
         SolrCLI.OPTION_SOLRURL_DEPRECATED,
         SolrCLI.OPTION_ZKHOST,
@@ -67,16 +61,31 @@ public class ZkMkrootTool extends ToolBase {
     return "mkroot";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk mkroot [--fail-on-exists <arg>] [-s <HOST>] [-u 
<credentials>] [-v] [-z <HOST>] path";
+  }
+
+  @Override
+  public String getHeader() {
+    StringBuilder sb = new StringBuilder();
+    format(
+        sb,
+        "mkroot makes a znode in Zookeeper with no data. Can be used to make a 
path of arbitrary");
+    format(sb, "depth but primarily intended to create a 'chroot'.\n\nList of 
options:");
+    return sb.toString();
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
     String zkHost = SolrCLI.getZkHost(cli);
+    String znode = cli.getArgs()[0];
     boolean failOnExists = cli.hasOption("fail-on-exists");
 
     try (SolrZkClient zkClient = SolrCLI.getSolrZkClient(cli, zkHost)) {
       echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
 
-      String znode = cli.getOptionValue("path");
       echo("Creating ZooKeeper path " + znode + " on ZooKeeper at " + zkHost);
       zkClient.makePath(znode, failOnExists, true);
     } catch (Exception e) {
diff --git a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java 
b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java
index 00a159a70bd..65e2fdc511b 100644
--- a/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ZkMvTool.java
@@ -16,6 +16,8 @@
  */
 package org.apache.solr.cli;
 
+import static org.apache.solr.packagemanager.PackageUtils.format;
+
 import java.io.PrintStream;
 import java.lang.invoke.MethodHandles;
 import java.util.List;
@@ -42,21 +44,6 @@ public class ZkMvTool extends ToolBase {
   @Override
   public List<Option> getOptions() {
     return List.of(
-        Option.builder()
-            .argName("SRC")
-            .longOpt("source")
-            .hasArg()
-            .required(true)
-            .desc("Source Znode to move from.")
-            .build(),
-        Option.builder()
-            .argName("DST")
-            .longOpt("destination")
-            .hasArg()
-            .required(true)
-            .desc("Destination Znode to move to.")
-            .build(),
-        SolrCLI.OPTION_RECURSE,
         SolrCLI.OPTION_SOLRURL,
         SolrCLI.OPTION_SOLRURL_DEPRECATED,
         SolrCLI.OPTION_ZKHOST,
@@ -70,6 +57,26 @@ public class ZkMvTool extends ToolBase {
     return "mv";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk mv [-s <HOST>] [-u <credentials>] [-v] [-z <HOST>] 
source destination";
+  }
+
+  @Override
+  public String getHeader() {
+    StringBuilder sb = new StringBuilder();
+    format(sb, "mv moves (renames) znodes on Zookeeper.");
+    format(sb, "");
+    format(sb, "<src>, <dest> : Zookeeper nodes, the 'zk:' prefix is 
optional.");
+    format(sb, "If <dest> ends with '/', then <dest> will be a parent znode");
+    format(sb, "and the last element of the <src> path will be appended.");
+    format(sb, "Zookeeper nodes CAN have data, so moving a single file to a 
parent znode");
+    format(sb, "will overlay the data on the parent Znode so specifying the 
trailing slash");
+    format(sb, "is important.");
+    format(sb, "\nList of options:");
+    return sb.toString();
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
@@ -77,8 +84,8 @@ public class ZkMvTool extends ToolBase {
 
     try (SolrZkClient zkClient = SolrCLI.getSolrZkClient(cli, zkHost)) {
       echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
-      String src = cli.getOptionValue("source");
-      String dst = cli.getOptionValue("destination");
+      String src = cli.getArgs()[0];
+      String dst = cli.getArgs()[1];
 
       if (src.toLowerCase(Locale.ROOT).startsWith("file:")
           || dst.toLowerCase(Locale.ROOT).startsWith("file:")) {
diff --git a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java 
b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java
index b00b769e33d..fbedfc77868 100644
--- a/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ZkRmTool.java
@@ -42,13 +42,6 @@ public class ZkRmTool extends ToolBase {
   @Override
   public List<Option> getOptions() {
     return List.of(
-        Option.builder()
-            .longOpt("path")
-            .argName("PATH")
-            .hasArg()
-            .required(true)
-            .desc("Path to remove.")
-            .build(),
         SolrCLI.OPTION_RECURSE,
         SolrCLI.OPTION_SOLRURL,
         SolrCLI.OPTION_SOLRURL_DEPRECATED,
@@ -63,13 +56,18 @@ public class ZkRmTool extends ToolBase {
     return "rm";
   }
 
+  @Override
+  public String getUsage() {
+    return "bin/solr zk rm [-r ] [-s <HOST>] [-u <credentials>] [-v] [-z 
<HOST>] path";
+  }
+
   @Override
   public void runImpl(CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
     String zkHost = SolrCLI.getZkHost(cli);
 
-    String target = cli.getOptionValue("path");
-    boolean recurse = Boolean.parseBoolean(cli.getOptionValue("recurse"));
+    String target = cli.getArgs()[0];
+    boolean recurse = cli.hasOption("recurse");
 
     String znode = target;
     if (target.toLowerCase(Locale.ROOT).startsWith("zk:")) {
diff --git a/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java 
b/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java
new file mode 100644
index 00000000000..14b541d3b33
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cli/ZkToolHelp.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.solr.cli;
+
+import static org.apache.solr.cli.SolrCLI.print;
+
+import java.io.PrintStream;
+import java.util.List;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+/** Supports zk help information in the bin/solr script. */
+public class ZkToolHelp extends ToolBase {
+
+  public ZkToolHelp() {
+    this(CLIO.getOutStream());
+  }
+
+  public ZkToolHelp(PrintStream stdout) {
+    super(stdout);
+  }
+
+  @Override
+  public List<Option> getOptions() {
+    return List.of(
+        Option.builder()
+            .longOpt("print-zk-subcommand-usage")
+            .desc("Reminds user to prepend zk to invoke the command.")
+            .required(false)
+            .build(),
+        Option.builder()
+            .longOpt("print-long-zk-usage")
+            .required(false)
+            .desc("Invokes the detailed help for zk commands.")
+            .build());
+  }
+
+  @Override
+  public String getName() {
+    return "zk-tool-help";
+  }
+
+  @Override
+  public void runImpl(CommandLine cli) throws Exception {
+    SolrCLI.raiseLogLevelUnlessVerbose(cli);
+
+    if (cli.hasOption("print-zk-subcommand-usage")) {
+      String scriptCommand = cli.getArgs()[0];
+      print(
+          "You must invoke this subcommand using the zk command.   bin/solr zk 
"
+              + scriptCommand
+              + ".");
+    }
+    if (cli.hasOption("print-long-zk-usage")) {
+      print("usage:");
+      print(new ZkLsTool().getUsage());
+      print(new ZkCpTool().getUsage());
+      print(new ZkMvTool().getUsage());
+
+      print(new ConfigSetUploadTool().getUsage());
+      print(new ConfigSetDownloadTool().getUsage());
+      print(new ZkMkrootTool().getUsage());
+      print(new LinkConfigTool().getUsage());
+      print(new UpdateACLTool().getUsage());
+      print("");
+      print(
+          "Pass --help or -h after any COMMAND to see command-specific usage 
information such as: ./solr zk ls --help");
+    }
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/cli/SolrCLIZkToolsTest.java 
b/solr/core/src/test/org/apache/solr/cli/SolrCLIZkToolsTest.java
index 2470d01dbf8..60883139e88 100644
--- a/solr/core/src/test/org/apache/solr/cli/SolrCLIZkToolsTest.java
+++ b/solr/core/src/test/org/apache/solr/cli/SolrCLIZkToolsTest.java
@@ -172,13 +172,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     AbstractDistribZkTestBase.copyConfigUp(configSet, "cloud-subdirs", "cp1", 
zkAddr);
 
     // Now copy it somewhere else on ZK.
-    String[] args =
-        new String[] {
-          "--source", "zk:/configs/cp1",
-          "--destination", "zk:/cp2",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    String[] args = new String[] {"--recurse", "--zk-host", zkAddr, 
"zk:/configs/cp1", "zk:/cp2"};
 
     ZkCpTool cpTool = new ZkCpTool();
 
@@ -190,14 +184,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     Path tmp = createTempDir("tmpNewPlace2");
     args =
         new String[] {
-          "--source",
-          "zk:/configs/cp1",
-          "--destination",
-          "file:" + tmp.toAbsolutePath(),
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
+          "--recurse", "--zk-host", zkAddr, "zk:/configs/cp1", "file:" + 
tmp.toAbsolutePath()
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -208,14 +195,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     tmp = createTempDir("tmpNewPlace3");
     args =
         new String[] {
-          "--source",
-          "zk:/configs/cp1",
-          "--destination",
-          tmp.toAbsolutePath().toString(),
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
+          "--recurse", "--zk-host", zkAddr, "zk:/configs/cp1", 
tmp.toAbsolutePath().toString()
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -225,14 +205,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     // try with local->zk
     args =
         new String[] {
-          "--source",
-          srcPathCheck.toAbsolutePath().toString(),
-          "--destination",
-          "zk:/cp3",
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
+          "--recurse", "--zk-host", zkAddr, 
srcPathCheck.toAbsolutePath().toString(), "zk:/cp3"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -242,67 +215,27 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
     // try with local->zk, file: specified
     args =
         new String[] {
-          "--source",
-          "file:" + srcPathCheck.toAbsolutePath(),
-          "--destination",
-          "zk:/cp4",
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
+          "--recurse", "--zk-host", zkAddr, "file:" + 
srcPathCheck.toAbsolutePath(), "zk:/cp4"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy should have succeeded.", 0, res);
     verifyZkLocalPathsMatch(srcPathCheck, "/cp4");
 
-    // try with recurse not specified
+    // try with recurse not specified, and therefore not happening
     args =
-        new String[] {
-          "--source",
-          "file:" + srcPathCheck.toAbsolutePath(),
-          "--destination",
-          "zk:/cp5Fail",
-          "--zk-host",
-          zkAddr,
-        };
+        new String[] {"--zk-host", zkAddr, "file:" + 
srcPathCheck.toAbsolutePath(), "zk:/cp5Fail"};
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertTrue("Copy should NOT have succeeded, recurse not specified.", 0 != 
res);
 
-    // try with recurse = false
-    args =
-        new String[] {
-          "--source",
-          "file:" + srcPathCheck.toAbsolutePath(),
-          "--destination",
-          "zk:/cp6Fail",
-          "--recurse",
-          "false",
-          "--zk-host",
-          zkAddr,
-        };
-
-    res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
-    assertTrue("Copy should NOT have succeeded, recurse set to false.", 0 != 
res);
-
     // NOTE: really can't test copying to '.' because the test framework 
doesn't allow altering the
     // source tree and at least IntelliJ's CWD is in the source tree.
 
     // copy to local ending in separator
     // src and cp3 and cp4 are valid
     String localSlash = tmp.normalize() + File.separator + "cpToLocal" + 
File.separator;
-    args =
-        new String[] {
-          "--source",
-          "zk:/cp3/schema.xml",
-          "--destination",
-          localSlash,
-          "--recurse",
-          "false",
-          "--zk-host",
-          zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "zk:/cp3/schema.xml", 
localSlash};
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy should nave created intermediate directory locally.", 
0, res);
@@ -314,14 +247,10 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
     // src and cp3 are valid
     args =
         new String[] {
-          "--source",
-          "file:" + srcPathCheck.normalize().toAbsolutePath() + File.separator 
+ "solrconfig.xml",
-          "--destination",
-          "zk:/powerup/",
-          "--recurse",
-          "false",
           "--zk-host",
           zkAddr,
+          "file:" + srcPathCheck.normalize().toAbsolutePath() + File.separator 
+ "solrconfig.xml",
+          "zk:/powerup/"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -334,14 +263,10 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
     // src and cp3 are valid
     args =
         new String[] {
-          "--source",
-          "file:" + srcPathCheck.normalize().toAbsolutePath() + File.separator 
+ "solrconfig.xml",
-          "--destination",
-          "zk:/copyUpFile.xml",
-          "--recurse",
-          "false",
           "--zk-host",
           zkAddr,
+          "file:" + srcPathCheck.normalize().toAbsolutePath() + File.separator 
+ "solrconfig.xml",
+          "zk:/copyUpFile.xml"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -355,17 +280,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
 
     String localNamed =
         tmp.normalize() + File.separator + "localnamed" + File.separator + 
"renamed.txt";
-    args =
-        new String[] {
-          "--source",
-          "zk:/cp4/solrconfig.xml",
-          "--destination",
-          "file:" + localNamed,
-          "--recurse",
-          "false",
-          "--zk-host",
-          zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "zk:/cp4/solrconfig.xml", 
"file:" + localNamed};
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy to local named file should have succeeded.", 0, res);
@@ -383,13 +298,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     assertTrue("Should have found Apache Software Foundation in the file! ", 
foundApache);
 
     // Test copy from somwehere in ZK to the root of ZK.
-    args =
-        new String[] {
-          "--source", "zk:/cp4/solrconfig.xml",
-          "--destination", "zk:/",
-          "--recurse", "false",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "zk:/cp4/solrconfig.xml", 
"zk:/"};
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy from somewhere in ZK to ZK root should have 
succeeded.", 0, res);
@@ -400,14 +309,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     // source path to the dst
     args =
         new String[] {
-          "--source",
-          "file:" + srcPathCheck.toAbsolutePath(),
-          "--destination",
-          "zk:/cp7/",
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
+          "--recurse", "--zk-host", zkAddr, "file:" + 
srcPathCheck.toAbsolutePath(), "zk:/cp7/"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -423,14 +325,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     // First, just copy the data up the cp7 since it's a directory.
     args =
         new String[] {
-          "--source",
-          "file:" + file.toAbsolutePath(),
-          "--destination",
-          "zk:/cp7/conf/stopwords/",
-          "--recurse",
-          "false",
-          "--zk-host",
-          zkAddr,
+          "--zk-host", zkAddr, "file:" + file.toAbsolutePath(), 
"zk:/cp7/conf/stopwords/"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -446,16 +341,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
 
     tmp = createTempDir("cp8");
     args =
-        new String[] {
-          "--source",
-          "zk:/cp7",
-          "--destination",
-          "file:" + tmp.toAbsolutePath(),
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
-        };
+        new String[] {"--recurse", "--zk-host", zkAddr, "zk:/cp7", "file:" + 
tmp.toAbsolutePath()};
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy should have succeeded.", 0, res);
 
@@ -465,16 +351,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
 
     // Finally, copy up to cp8 and verify that the data is up there.
     args =
-        new String[] {
-          "--source",
-          "file:" + tmp.toAbsolutePath(),
-          "--destination",
-          "zk:/cp9",
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
-        };
+        new String[] {"--recurse", "--zk-host", zkAddr, "file:" + 
tmp.toAbsolutePath(), "zk:/cp9"};
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy should have succeeded.", 0, res);
@@ -490,14 +367,10 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
 
     args =
         new String[] {
-          "--source",
-          "file:" + emptyFile.toAbsolutePath(),
-          "--destination",
-          "zk:/cp7/conf/stopwords/emptyfile",
-          "--recurse",
-          "false",
           "--zk-host",
           zkAddr,
+          "file:" + emptyFile.toAbsolutePath(),
+          "zk:/cp7/conf/stopwords/emptyfile"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -507,14 +380,10 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
     Path emptyDest = Paths.get(tmp2.toAbsolutePath().toString(), "emptyfile");
     args =
         new String[] {
-          "--source",
-          "zk:/cp7/conf/stopwords/emptyfile",
-          "--destination",
-          "file:" + emptyDest.toAbsolutePath(),
-          "--recurse",
-          "false",
           "--zk-host",
           zkAddr,
+          "zk:/cp7/conf/stopwords/emptyfile",
+          "file:" + emptyDest.toAbsolutePath()
         };
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy should have succeeded.", 0, res);
@@ -525,14 +394,11 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
 
     args =
         new String[] {
-          "--source",
-          "file:" + emptyFile.getParent().getParent().toString(),
-          "--destination",
-          "zk:/cp10",
           "--recurse",
-          "true",
           "--zk-host",
           zkAddr,
+          "file:" + emptyFile.getParent().getParent().toString(),
+          "zk:/cp10"
         };
 
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
@@ -542,14 +408,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     tmp2 = createTempDir("cp10");
     args =
         new String[] {
-          "--source",
-          "zk:/cp10",
-          "--destination",
-          "file:" + tmp2.toAbsolutePath(),
-          "--recurse",
-          "true",
-          "--zk-host",
-          zkAddr,
+          "--recurse", "--zk-host", zkAddr, "zk:/cp10", "file:" + 
tmp2.toAbsolutePath()
         };
     res = cpTool.runTool(SolrCLI.processCommandLineArgs(cpTool, args));
     assertEquals("Copy should have succeeded.", 0, res);
@@ -569,12 +428,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     AbstractDistribZkTestBase.copyConfigUp(configSet, "cloud-subdirs", "mv1", 
zkAddr);
 
     // Now move it somewhere else.
-    String[] args =
-        new String[] {
-          "--source", "zk:/configs/mv1",
-          "--destination", "zk:/mv2",
-          "--zk-host", zkAddr,
-        };
+    String[] args = new String[] {"--zk-host", zkAddr, "zk:/configs/mv1", 
"zk:/mv2"};
 
     ZkMvTool mvTool = new ZkMvTool();
 
@@ -588,27 +442,14 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
 
     // Files are in mv2
     // Now fail if we specify "file:". Everything should still be in /mv2
-    args =
-        new String[] {
-          "--source",
-          "file:" + File.separator + "mv2",
-          "--destination",
-          "/mv3",
-          "--zk-host",
-          zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "file:" + File.separator + 
"mv2", "/mv3"};
 
     // Still in mv2
     res = mvTool.runTool(SolrCLI.processCommandLineArgs(mvTool, args));
     assertTrue("Move should NOT have succeeded with file: specified.", 0 != 
res);
 
     // Let's move it to yet another place with no zk: prefix.
-    args =
-        new String[] {
-          "--source", "/mv2",
-          "--destination", "/mv4",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "/mv2", "/mv4"};
 
     res = mvTool.runTool(SolrCLI.processCommandLineArgs(mvTool, args));
     assertEquals("Move should have succeeded.", 0, res);
@@ -618,12 +459,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     // Now does the moved directory match the original on disk?
     verifyZkLocalPathsMatch(srcPathCheck, "/mv4");
 
-    args =
-        new String[] {
-          "--source", "/mv4/solrconfig.xml",
-          "--destination", "/testmvsingle/solrconfig.xml",
-          "-z", zkAddr,
-        };
+    args = new String[] {"-z", zkAddr, "/mv4/solrconfig.xml", 
"/testmvsingle/solrconfig.xml"};
 
     res = mvTool.runTool(SolrCLI.processCommandLineArgs(mvTool, args));
     assertEquals("Move should have succeeded.", 0, res);
@@ -634,12 +470,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     zkClient.makePath("/parentNode", true);
 
     // what happens if the destination ends with a slash?
-    args =
-        new String[] {
-          "--source", "/mv4/schema.xml",
-          "--destination", "/parentnode/",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "/mv4/schema.xml", 
"/parentnode/"};
 
     res = mvTool.runTool(SolrCLI.processCommandLineArgs(mvTool, args));
     assertEquals("Move should have succeeded.", 0, res);
@@ -661,10 +492,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     AbstractDistribZkTestBase.copyConfigUp(configSet, "cloud-subdirs", 
"lister", zkAddr);
 
     // Should only find a single level.
-    String[] args =
-        new String[] {
-          "--path", "/configs", "--zk-host", zkAddr,
-        };
+    String[] args = new String[] {"--zk-host", zkAddr, "/configs"};
 
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     PrintStream ps = new PrintStream(baos, false, 
StandardCharsets.UTF_8.name());
@@ -677,13 +505,8 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     assertTrue("Return should contain the conf directory", 
content.contains("lister"));
     assertFalse("Return should NOT contain a child node", 
content.contains("solrconfig.xml"));
 
-    // simple ls recurse=false
-    args =
-        new String[] {
-          "--path", "/configs",
-          "--recurse", "false",
-          "--zk-host", zkAddr,
-        };
+    // simple ls with no recursion
+    args = new String[] {"--zk-host", zkAddr, "/configs"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     content = baos.toString(StandardCharsets.UTF_8);
@@ -692,13 +515,8 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     assertTrue("Return should contain the conf directory", 
content.contains("lister"));
     assertFalse("Return should NOT contain a child node", 
content.contains("solrconfig.xml"));
 
-    // recurse=true
-    args =
-        new String[] {
-          "--path", "/configs",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    // ls with recursion
+    args = new String[] {"--recurse", "--zk-host", zkAddr, "/configs"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     content = baos.toString(StandardCharsets.UTF_8);
@@ -708,12 +526,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     assertTrue("Return should contain a child node", 
content.contains("solrconfig.xml"));
 
     // Saw a case where going from root didn't work, so test it.
-    args =
-        new String[] {
-          "--path", "/",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--recurse", "--zk-host", zkAddr, "/"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     content = baos.toString(StandardCharsets.UTF_8);
@@ -722,10 +535,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     assertTrue("Return should contain the conf directory", 
content.contains("lister"));
     assertTrue("Return should contain a child node", 
content.contains("solrconfig.xml"));
 
-    args =
-        new String[] {
-          "--path", "/", "--zk-host", zkAddr,
-        };
+    args = new String[] {"--zk-host", zkAddr, "/"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     content = baos.toString(StandardCharsets.UTF_8);
@@ -733,12 +543,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
     assertFalse("Return should not contain /zookeeper", 
content.contains("/zookeeper"));
 
     // Saw a case where ending in slash didn't work, so test it.
-    args =
-        new String[] {
-          "--path", "/configs/",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--recurse", "--zk-host", zkAddr, "/configs/"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     content = baos.toString(StandardCharsets.UTF_8);
@@ -758,40 +563,26 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase 
{
     AbstractDistribZkTestBase.copyConfigUp(configSet, "cloud-subdirs", "rm2", 
zkAddr);
 
     // Should fail if recurse not set.
-    String[] args =
-        new String[] {
-          "--path", "/configs/rm1", "--zk-host", zkAddr,
-        };
+    String[] args = new String[] {"--zk-host", zkAddr, "/configs/rm1"};
 
     ZkRmTool tool = new ZkRmTool();
 
     int res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
 
-    assertTrue(
-        "Should have failed to remove node with children unless --recurse is 
set to true",
-        res != 0);
+    assertTrue("Should have failed to remove node with children unless 
--recurse is set", res != 0);
 
     // Are we sure all the znodes are still there?
     verifyZkLocalPathsMatch(srcPathCheck, "/configs/rm1");
 
-    args =
-        new String[] {
-          "--path", "zk:/configs/rm1",
-          "--recurse", "false",
-          "--zk-host", zkAddr,
-        };
+    // run without recurse specified
+    args = new String[] {"--zk-host", zkAddr, "zk:/configs/rm1"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
 
     assertTrue(
         "Should have failed to remove node with children if --recurse is set 
to false", res != 0);
 
-    args =
-        new String[] {
-          "--path", "/configs/rm1",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--recurse", "--zk-host", zkAddr, "/configs/rm1"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     assertEquals("Should have removed node /configs/rm1", res, 0);
@@ -799,12 +590,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
         "Znode /configs/toremove really should be gone", 
zkClient.exists("/configs/rm1", true));
 
     // Check that zk prefix also works.
-    args =
-        new String[] {
-          "--path", "zk:/configs/rm2",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--recurse", "--zk-host", zkAddr, "zk:/configs/rm2"};
 
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
     assertEquals("Should have removed node /configs/rm2", res, 0);
@@ -812,12 +598,7 @@ public class SolrCLIZkToolsTest extends SolrCloudTestCase {
         "Znode /configs/toremove2 really should be gone", 
zkClient.exists("/configs/rm2", true));
 
     // This should silently just refuse to do anything to the / or /zookeeper
-    args =
-        new String[] {
-          "--path", "zk:/",
-          "--recurse", "true",
-          "--zk-host", zkAddr,
-        };
+    args = new String[] {"--recurse", "--zk-host", zkAddr, "zk:/"};
 
     AbstractDistribZkTestBase.copyConfigUp(configSet, "cloud-subdirs", "rm3", 
zkAddr);
     res = tool.runTool(SolrCLI.processCommandLineArgs(tool, args));
diff --git a/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java 
b/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java
index 250cee1a955..5cf76769d99 100644
--- a/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java
+++ b/solr/core/src/test/org/apache/solr/cli/ZkSubcommandsTest.java
@@ -128,13 +128,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     String[] args =
         new String[] {
-          "cp",
-          "--source",
-          localFile.getAbsolutePath(),
-          "--destination",
-          "zk:/data.txt",
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), localFile.getAbsolutePath(), 
"zk:/data.txt"
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -179,13 +173,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     String[] args =
         new String[] {
-          "cp",
-          "--source",
-          localFile.getAbsolutePath(),
-          "--destination",
-          "zk:/state.json",
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), localFile.getAbsolutePath(), 
"zk:/state.json"
         };
     ZkCpTool tool = new ZkCpTool();
     assertEquals(0, runTool(args, tool));
@@ -209,13 +197,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     args =
         new String[] {
-          "cp",
-          "--source",
-          localFile.getAbsolutePath(),
-          "--destination",
-          "zk:/state.json",
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), localFile.getAbsolutePath(), 
"zk:/state.json"
         };
     assertEquals(0, runTool(args, tool));
 
@@ -234,12 +216,10 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
     String[] args =
         new String[] {
           "cp",
-          "--source",
-          SOLR_HOME + File.separator + "solr-stress-new.xml",
-          "--destination",
-          "zk:/foo.xml",
           "-z",
-          zkServer.getZkAddress()
+          zkServer.getZkAddress(),
+          SOLR_HOME + File.separator + "solr-stress-new.xml",
+          "zk:/foo.xml"
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -258,12 +238,10 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
     String[] args =
         new String[] {
           "cp",
-          "--source",
-          SOLR_HOME + File.separator + "solr-stress-new.xml",
-          "--destination",
-          "zk:foo.xml",
           "-z",
-          zkServer.getZkAddress()
+          zkServer.getZkAddress(),
+          SOLR_HOME + File.separator + "solr-stress-new.xml",
+          "zk:foo.xml"
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -285,12 +263,10 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
     String[] args =
         new String[] {
           "cp",
-          "--source",
-          SOLR_HOME + File.separator + "solr-stress-new.xml",
-          "--destination",
-          "zk:/state.json",
           "-z",
-          zkServer.getZkAddress()
+          zkServer.getZkAddress(),
+          SOLR_HOME + File.separator + "solr-stress-new.xml",
+          "zk:/state.json"
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -316,12 +292,10 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
     String[] args =
         new String[] {
           "cp",
-          "--source",
-          SOLR_HOME + File.separator + "not-there.xml",
-          "--destination",
-          "zk:/foo.xml",
           "-z",
-          zkServer.getZkAddress()
+          zkServer.getZkAddress(),
+          SOLR_HOME + File.separator + "not-there.xml",
+          "zk:/foo.xml"
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -332,7 +306,8 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
   public void testLs() throws Exception {
     zkClient.makePath("/test/path", true);
 
-    String[] args = new String[] {"ls", "--path", "/", "-r", "true", "-z", 
zkServer.getZkAddress()};
+    // test what happens when path arg "/" isn't the last one.
+    String[] args = new String[] {"ls", "/", "-r", "true", "-z", 
zkServer.getZkAddress()};
 
     ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
     final PrintStream myOut = new PrintStream(byteStream, false, 
StandardCharsets.UTF_8);
@@ -461,10 +436,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
     }
 
     // test reset zk
-    args =
-        new String[] {
-          "rm", "--path", "zk:/configs/confsetone", "-r", "true", "-z", 
zkServer.getZkAddress()
-        };
+    args = new String[] {"rm", "-r", "-z", zkServer.getZkAddress(), 
"zk:/configs/confsetone"};
 
     ZkRmTool zkRmTool = new ZkRmTool();
     assertEquals(0, runTool(args, zkRmTool));
@@ -482,13 +454,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     String[] args =
         new String[] {
-          "cp",
-          "--source",
-          "zk:" + getNode,
-          "--destination",
-          localFile.getAbsolutePath(),
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, 
localFile.getAbsolutePath()
         };
 
     ByteArrayOutputStream byteStream2 = new ByteArrayOutputStream();
@@ -521,13 +487,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     String[] args =
         new String[] {
-          "cp",
-          "--source",
-          "zk:" + getNode,
-          "--destination",
-          localFile.getAbsolutePath(),
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, 
localFile.getAbsolutePath()
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -549,13 +509,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     String[] args =
         new String[] {
-          "cp",
-          "--source",
-          "zk:" + getNode,
-          "--destination",
-          file.toAbsolutePath().toString(),
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, 
file.toAbsolutePath().toString()
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -581,13 +535,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
 
     String[] args =
         new String[] {
-          "cp",
-          "--source",
-          "zk:" + getNode,
-          "--destination",
-          file.toAbsolutePath().toString(),
-          "-z",
-          zkServer.getZkAddress()
+          "cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, 
file.toAbsolutePath().toString()
         };
 
     ZkCpTool tool = new ZkCpTool();
@@ -603,15 +551,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
     File file = createTempFile("newfile", null).toFile();
 
     String[] args =
-        new String[] {
-          "cp",
-          "--source",
-          "zk:" + getNode,
-          "--destination",
-          file.getAbsolutePath(),
-          "-z",
-          zkServer.getZkAddress()
-        };
+        new String[] {"cp", "-z", zkServer.getZkAddress(), "zk:" + getNode, 
file.getAbsolutePath()};
 
     ZkCpTool tool = new ZkCpTool();
     assertEquals(1, runTool(args, tool));
@@ -620,7 +560,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
   @Test
   public void testInvalidZKAddress() throws Exception {
 
-    String[] args = new String[] {"ls", "--path", "/", "-r", "true", "-z", 
"----------:33332"};
+    String[] args = new String[] {"ls", "/", "-r", "-z", "----------:33332"};
 
     ByteArrayOutputStream byteStream2 = new ByteArrayOutputStream();
     final PrintStream myOut2 = new PrintStream(byteStream2, false, 
StandardCharsets.UTF_8);
@@ -667,7 +607,7 @@ public class ZkSubcommandsTest extends SolrTestCaseJ4 {
           
VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME,
           "pass");
 
-      String[] args = new String[] {"updateacls", "--path", "/", "-z", 
zkServer.getZkAddress()};
+      String[] args = new String[] {"updateacls", "/", "-z", 
zkServer.getZkAddress()};
       UpdateACLTool tool = new UpdateACLTool();
       assertEquals(0, runTool(args, tool));
     } finally {
diff --git a/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh 
b/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh
index 2ee47103264..888b755e422 100755
--- a/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh
+++ b/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh
@@ -46,7 +46,7 @@ if ! grep -q "${solr_ip}:8983" <<<"$data"; then
 fi
 
 echo "Creating distributed collection"
-data=$(docker exec --user=solr "$container_name" solr create -c test -rf 1 -s 
2)
+data=$(docker exec --user=solr "$container_name" solr create -c test -rf 1 
--shards 2)
 
 if ! grep -q "Created collection 'test'" <<<"$data"; then
   echo "Test $TEST_NAME $tag failed; could not create distributed collection"
diff --git a/solr/packaging/test/test_create_collection.bats 
b/solr/packaging/test/test_create_collection.bats
index 3ffa6bd0ff3..2bee792c4c3 100644
--- a/solr/packaging/test/test_create_collection.bats
+++ b/solr/packaging/test/test_create_collection.bats
@@ -99,7 +99,7 @@ teardown() {
 }
 
 @test "create multisharded collections when s provided" {
-  run -0 solr create -c COLL_NAME -s 2 --solr-url http://localhost:${SOLR_PORT}
+  run -0 solr create -c COLL_NAME --shards 2 --solr-url 
http://localhost:${SOLR_PORT}
   assert_output --partial "Created collection 'COLL_NAME'"
   assert_output --partial "2 shard(s)"
 }
diff --git a/solr/packaging/test/test_help.bats 
b/solr/packaging/test/test_help.bats
index fe9c87866f1..f131cba0bce 100644
--- a/solr/packaging/test/test_help.bats
+++ b/solr/packaging/test/test_help.bats
@@ -92,7 +92,8 @@ setup() {
 
 @test "zk help flag prints help" {
   run solr zk --help
-  assert_output --partial 'Usage: solr zk'
+  assert_output --partial 'usage:'
+  assert_output --partial 'bin/solr zk ls'
   refute_output --partial 'ERROR'
 }
 
diff --git a/solr/packaging/test/test_ssl.bats 
b/solr/packaging/test/test_ssl.bats
index d114c15e25d..702133e3fa7 100644
--- a/solr/packaging/test/test_ssl.bats
+++ b/solr/packaging/test/test_ssl.bats
@@ -54,7 +54,7 @@ teardown() {
   solr start -c
   solr assert --started https://localhost:${SOLR_PORT} --timeout 5000
 
-  run solr create -c test -s 2
+  run solr create -c test --shards 2
   assert_output --partial "Created collection 'test'"
 
   run solr api --solr-url 
"https://localhost:${SOLR_PORT}/solr/test/select?q=*:*";
@@ -92,7 +92,7 @@ teardown() {
   solr start -c
   solr assert --started https://localhost:${SOLR_PORT} --timeout 5000
 
-  run solr create -c test -s 2
+  run solr create -c test --shards 2
   assert_output --partial "Created collection 'test'"
 
   run solr api --solr-url 
"https://localhost:${SOLR_PORT}/solr/test/select?q=*:*";
@@ -217,7 +217,7 @@ teardown() {
   export SOLR_SSL_TRUST_STORE=
   export SOLR_SSL_TRUST_STORE_PASSWORD=
 
-  run solr create -c test -s 2
+  run solr create -c test --shards 2
   assert_output --partial "Created collection 'test'"
 
   run solr api --solr-url 
"https://localhost:${SOLR_PORT}/solr/admin/collections?action=CLUSTERSTATUS";
@@ -335,7 +335,7 @@ teardown() {
     solr assert --started https://localhost:${SOLR_PORT} --timeout 5000
     solr assert --started https://localhost:${SOLR2_PORT} --timeout 5000
 
-    run solr create -c test -s 2
+    run solr create -c test --shards 2
     assert_output --partial "Created collection 'test'"
 
     run solr api --solr-url 
"https://localhost:${SOLR_PORT}/solr/admin/collections?action=CLUSTERSTATUS";
@@ -472,7 +472,7 @@ teardown() {
   solr assert --started https://localhost:${SOLR_PORT} --timeout 5000
   solr assert --started https://localhost:${SOLR2_PORT} --timeout 5000
 
-  run solr create -c test -s 2
+  run solr create -c test --shards 2
   assert_output --partial "Created collection 'test'"
 
   run solr api --solr-url 
"https://localhost:${SOLR_PORT}/solr/admin/collections?action=CLUSTERSTATUS";
@@ -536,11 +536,11 @@ teardown() {
   solr assert --started https://localhost:${SOLR2_PORT} --timeout 5000
 
   # "test" collection is two shards, meaning there must be communication 
between shards for queries (handled by http shard handler factory)
-  run solr create -c test -s 2
+  run solr create -c test --shards 2
   assert_output --partial "Created collection 'test'"
 
   # "test-single-shard" is one shard and one replica, this means that one of 
the nodes will have to forward requests to the other
-  run solr create -c test-single-shard -s 1
+  run solr create -c test-single-shard --shards 1
   assert_output --partial "Created collection 'test-single-shard'"
 
   run solr api --solr-url 
"https://localhost:${SOLR_PORT}/solr/test/select?q=*:*";
diff --git a/solr/packaging/test/test_zk.bats b/solr/packaging/test/test_zk.bats
index 817a33e1054..87c2501beba 100644
--- a/solr/packaging/test/test_zk.bats
+++ b/solr/packaging/test/test_zk.bats
@@ -36,6 +36,23 @@ teardown() {
   save_home_on_failure
 }
 
+@test "short help" {
+ run solr zk ls -h
+ assert_output --partial "usage: bin/solr zk"
+}
+
+@test "short help is inferred" {
+ run solr zk ls
+ assert_output --partial "usage: bin/solr zk"
+}
+
+@test "long help" {
+ run solr zk -h
+ assert_output --partial "bin/solr zk ls"
+ assert_output --partial "bin/solr zk updateacls"
+ assert_output --partial "Pass --help or -h after any COMMAND"
+}
+
 @test "running subcommands with zk is prevented" {
  run solr ls / -z localhost:${ZK_PORT}
  assert_output --partial "You must invoke this subcommand using the zk command"
@@ -54,7 +71,7 @@ teardown() {
   # We do mapping in bin/solr script from -solrUrl to --solr-url that prevents 
deprecation warning
   #assert_output --partial "Deprecated for removal since 9.7: Use --solr-url 
instead"
 
-  run solr zk ls / -s http://localhost:${SOLR_PORT}
+  run solr zk ls / -url http://localhost:${SOLR_PORT}
   assert_output --partial "aliases.json"
   # We do mapping in bin/solr script from -solrUrl to --solr-url that prevents 
deprecation warning
   #assert_output --partial "Deprecated for removal since 9.7: Use --solr-url 
instead"
@@ -98,6 +115,11 @@ teardown() {
   sleep 1
   run solr zk ls / -z localhost:${ZK_PORT}
   assert_output --partial "myfile3.txt"
+  
+  run solr zk cp zk:/ -r "${BATS_TEST_TMPDIR}/recursive_download/"
+  [ -e "${BATS_TEST_TMPDIR}/recursive_download/myfile.txt" ]
+  [ -e "${BATS_TEST_TMPDIR}/recursive_download/myfile2.txt" ]
+  [ -e "${BATS_TEST_TMPDIR}/recursive_download/myfile3.txt" ]
 
   rm myfile.txt
   rm myfile2.txt
@@ -115,7 +137,12 @@ teardown() {
   sleep 1
   run curl "http://localhost:${SOLR_PORT}/api/cluster/configs?omitHeader=true";
   assert_output --partial '"configSets":["_default","techproducts2"]'
+}
 
+@test "downconfig" {
+  run solr zk downconfig -z localhost:${ZK_PORT} -n _default -d 
"${BATS_TEST_TMPDIR}/downconfig"
+  assert_output --partial "Downloading"
+  refute_output --partial "ERROR"
 }
 
 
diff --git 
a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
 
b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
index 3a517224747..4f15f8de677 100644
--- 
a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
+++ 
b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
@@ -706,7 +706,7 @@ This defaults to the same name as the core or collection.
 Number of shards to split a collection into.
 Only applies when Solr is running in SolrCloud mode.
 +
-*Example*: `bin/solr create -s 2`
+*Example*: `bin/solr create --shards 2`
 
 `-rf <replicas>` or `--replication-factor <replicas>`::
 +
@@ -1139,7 +1139,7 @@ This parameter is unnecessary if `SOLR_AUTH_TYPE` is 
defined in `solr.in.sh` or
 == ZooKeeper Operations
 
 The `bin/solr` script allows certain operations affecting ZooKeeper.
-These operations are for SolrCloud mode only.
+These operations are for SolrCloud mode only.  For more information see 
xref:zookeeper-utilities.adoc[ZooKeeper Utilities].
 
 The operations are available as sub-commands, which each have their own set of 
options.
 
diff --git 
a/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-utilities.adoc 
b/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-utilities.adoc
index 864ccb1b1dd..fb2a54b0478 100644
--- 
a/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-utilities.adoc
+++ 
b/solr/solr-ref-guide/modules/deployment-guide/pages/zookeeper-utilities.adoc
@@ -26,7 +26,7 @@ The ZooKeeper specific commands are provided by the 
xref:solr-control-script-ref
 
 == Using Solr's CLI with ZooKeeper
 
-Use the `help` option to get a list of available ZooKeeper specifc commands 
from the script itself, as in `bin/solr zk -h`.
+Use the `help` option to get a list of available ZooKeeper specific commands 
from the script itself, as in `bin/solr zk -h`.
 
 == Solr CLI Examples
 
diff --git 
a/solr/solr-ref-guide/modules/getting-started/pages/tutorial-diy.adoc 
b/solr/solr-ref-guide/modules/getting-started/pages/tutorial-diy.adoc
index 6a1fc1d26d7..14d7d98adb2 100644
--- a/solr/solr-ref-guide/modules/getting-started/pages/tutorial-diy.adoc
+++ b/solr/solr-ref-guide/modules/getting-started/pages/tutorial-diy.adoc
@@ -37,7 +37,7 @@ In this example, the collection will be named "localDocs"; 
replace that name wit
 
 [,console]
 ----
-$ bin/solr create -c localDocs -s 2 -rf 2
+$ bin/solr create -c localDocs --shards 2 -rf 2
 ----
 
 Again, as we saw from Exercise 2 above, this will use the `_default` configset 
and all the schemaless features it provides.
diff --git 
a/solr/solr-ref-guide/modules/getting-started/pages/tutorial-films.adoc 
b/solr/solr-ref-guide/modules/getting-started/pages/tutorial-films.adoc
index 69622b5c7cc..503f1fa8034 100644
--- a/solr/solr-ref-guide/modules/getting-started/pages/tutorial-films.adoc
+++ b/solr/solr-ref-guide/modules/getting-started/pages/tutorial-films.adoc
@@ -75,7 +75,7 @@ The data you're going to index is related to movies, so start 
by creating a coll
 
 [,console]
 ----
-$ bin/solr create -c films -s 2 -rf 2
+$ bin/solr create -c films --shards 2 -rf 2
 ----
 
 Whoa, wait.
diff --git 
a/solr/solr-ref-guide/modules/getting-started/pages/tutorial-techproducts.adoc 
b/solr/solr-ref-guide/modules/getting-started/pages/tutorial-techproducts.adoc
index 77b4aaa4ca0..6877e453d90 100644
--- 
a/solr/solr-ref-guide/modules/getting-started/pages/tutorial-techproducts.adoc
+++ 
b/solr/solr-ref-guide/modules/getting-started/pages/tutorial-techproducts.adoc
@@ -488,7 +488,7 @@ And then create a new collection:
 
 [,console]
 ----
-$ bin/solr create -c <yourCollection> -s 2 -rf 2
+$ bin/solr create -c <yourCollection> --shards 2 -rf 2
 ----
 
 To stop both of the Solr nodes we started, issue the command:

Reply via email to