Search code examples
javabashprocessbuilder

Can ProcessBuilder run a bash function?


After installing sdkman, and adding the following two lines to my ~/.bashrc:

export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

and reloading the environment, a function named sdk becomes available in the environment:

$ type sdk
sdk is a function
sdk () 
{ 
    COMMAND="$1";
    QUALIFIER="$2";
    case "$COMMAND" in 
        l)
            COMMAND="list"
        ;;
        ls)
            COMMAND="list"
        ;;
        v)
            COMMAND="version"
        ;;
        u)
            COMMAND="use"
        ;;
        i)
            COMMAND="install"
        ;;
        rm)
            COMMAND="uninstall"
        ;;
        c)
            COMMAND="current"
        ;;
        ug)
            COMMAND="upgrade"
        ;;
        d)
            COMMAND="default"
        ;;
        b)
            COMMAND="broadcast"
        ;;
        h)
            COMMAND="home"
        ;;
        e)
            COMMAND="env"
        ;;
    esac;
    if [[ "$COMMAND" == "home" ]]; then
        __sdk_home "$QUALIFIER" "$3";
        return $?;
    fi;
    if [[ "$COMMAND" == "completion" ]]; then
        return 0;
    fi;
    if [[ "$COMMAND" != "update" ]]; then
        ___sdkman_check_candidates_cache "$SDKMAN_CANDIDATES_CACHE" || return 1;
    fi;
    ___sdkman_check_version_cache;
    SDKMAN_AVAILABLE="true";
    if [ -z "$SDKMAN_OFFLINE_MODE" ]; then
        SDKMAN_OFFLINE_MODE="false";
    fi;
    __sdkman_update_broadcast_and_service_availability;
    if [ -f "${SDKMAN_DIR}/etc/config" ]; then
        source "${SDKMAN_DIR}/etc/config";
    fi;
    if [[ -z "$COMMAND" ]]; then
        __sdk_help;
        return 1;
    fi;
    CMD_FOUND="";
    CMD_TARGET="${SDKMAN_DIR}/src/sdkman-${COMMAND}.sh";
    if [[ -f "$CMD_TARGET" ]]; then
        CMD_FOUND="$CMD_TARGET";
    fi;
    CMD_TARGET="${SDKMAN_DIR}/ext/sdkman-${COMMAND}.sh";
    if [[ -f "$CMD_TARGET" ]]; then
        CMD_FOUND="$CMD_TARGET";
    fi;
    if [[ -z "$CMD_FOUND" ]]; then
        echo "";
        __sdkman_echo_red "Invalid command: $COMMAND";
        echo "";
        __sdk_help;
    fi;
    local sdkman_valid_candidate=$(echo ${SDKMAN_CANDIDATES[@]} | grep -w "$QUALIFIER");
    if [[ -n "$QUALIFIER" && "$COMMAND" != "help" && "$COMMAND" != "offline" && "$COMMAND" != "flush" && "$COMMAND" != "selfupdate" && "$COMMAND" != "env" && "$COMMAND" != "completion" && "$COMMAND" != "edit" && -z "$sdkman_valid_candidate" ]]; then
        echo "";
        __sdkman_echo_red "Stop! $QUALIFIER is not a valid candidate.";
        return 1;
    fi;
    if [[ "$COMMAND" == "offline" && -n "$QUALIFIER" && -z $(echo "enable disable" | grep -w "$QUALIFIER") ]]; then
        echo "";
        __sdkman_echo_red "Stop! $QUALIFIER is not a valid offline mode.";
    fi;
    local final_rc=0;
    local native_command="${SDKMAN_DIR}/libexec/${COMMAND}";
    local converted_command_name=$(echo "$COMMAND" | tr '-' '_');
    if [ -f "$native_command" ]; then
        if [ -z "$QUALIFIER" ]; then
            "$native_command";
        else
            if [ -z "$3" ]; then
                "$native_command" "$QUALIFIER";
            else
                if [ -z "$4" ]; then
                    "$native_command" "$QUALIFIER" "$3";
                else
                    "$native_command" "$QUALIFIER" "$3" "$4";
                fi;
            fi;
        fi;
        final_rc=$?;
    else
        if [ -n "$CMD_FOUND" ]; then
            __sdk_"$converted_command_name" "$QUALIFIER" "$3" "$4";
            final_rc=$?;
        fi;
    fi;
    if [[ "$COMMAND" != "selfupdate" && "$sdkman_selfupdate_enable" == true ]]; then
        __sdkman_auto_update "$SDKMAN_REMOTE_VERSION" "$SDKMAN_VERSION";
    fi;
    return $final_rc
}

And you can use it like a normal command from the shell. For example:

$ sdk install java 17.0.2-tem

Is there a way to run sdk from inside Java using ProcessBuilder/Process?

Unless I create a script like this and call it from Java:

#!/bin/bash

set -e

export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"


sdk install java 17.0.2-tem

I can't run the sdk command like this via ProcessBuilder:

final var processBuilder = ...;
processBuilder.command("sdk", "install", "java", "17.0.2-tem")
// or
processBuilder.command("/bin/bash", "-c", "sdk", "install", "java", "17.0.2-tem")

Are there any other alternatives that would allow calling the functions defined in the parent environment from Java?


Solution

  • Are there any other alternatives that would allow calling the functions defined in the parent environment from Java?

    Yes there is but you'll have to source the .bashrc file first.

    BufferedReader in = null;
    OutputStreamWriter out = null;
    try {
      ProcessBuilder pb = new ProcessBuilder("/bin/bash").redirectErrorStream(true);
      Process p = pb.start();
      in = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8));
      out = new OutputStreamWriter(p.getOutputStream(), StandardCharsets.UTF_8);
      out.write("source ~/.bashrc\n");
      out.write("sdkman\n");
      out.write("exit\n");
      out.flush();
      String str;
      while((str = in.readLine()) != null) {
            System.out.println(str);
      }
      int response = p.waitFor();
      System.out.println("response: " + response);
      } catch(IOException e) {
          e.printStackTrace();
      } catch(InterruptedException e) {
          e.printStackTrace();
      } finally {
          try { in.close(); } catch(Exception e) {}
          try { out.close(); } catch(Exception e) {}
      }
    }