Beyond the Terminal: The Unlimited Potential of the Java Language in a Command-Line Environment

Hi, hello I am vnjohn, I work as a Java developer in an Internet company, and I am a high-quality CSDN creator.
Recommended columns: Spring, MySQL, Nacos, Java. Other columns will continue to be optimized and updated in the future.
The column where the article is located: Linux
I am currently learning about architecture and principles in the microservices field, cloud native field, message middleware and other fields.
Ask me anything you want, ID: vnjohn
I think the blogger’s article is OK and can help you. Thank you Sanlian for supporting the blog
Pronoun: vnjohn
? Fun facts: music, running, movies, games

Table of Contents

  • Preface
  • Practice
    • rely
    • Establish a session connection
    • Execute command processing stream results
    • Call entry
    • Demo
  • Summarize

Foreword

Combined with the front-end and back-end interactions, in order to be able to display all the package files in the specified directory of the server on the front-end page (the files are generated by Jenkins automatic compilation and packaging) and provide the download copy function

The apk and ipa files generated based on Android and IOS packaging will be stored in our server directory for backup, and then our package files can be versioned and planned in the PC background, so such a function needs to be implemented

Practice

Dependencies

For the operation of this part of the work, third-party dependencies need to be introduced for collaborative development, mainly to provide us with the sshd tool to realize Linux host interconnection and read accessible file directories.

<dependency>
    <groupId>ch.ethz.ganymed</groupId>
    <artifactId>ganymed-ssh2</artifactId>
    <version>build210</version>
</dependency>

Establish session connection

First, you need to open a login session with the Linux server and establish a connection between both parties before you can read the specified directory file on the server.

private static Connection CONN = null;

private static String DEFAULT_CHART = "UTF-8";

private static String HOSTNAME;

private static String USERNAME;

private static String PASSWORD;

@Value("${ssh.hostname:}")
public void setHostname(String name) {<!-- -->
    HOSTNAME = name;
}

@Value("${ssh.username:}")
public void setUsername(String user) {<!-- -->
    USERNAME = user;
}

@Value("${ssh.password:}")
public void setPassword(String pwd) {<!-- -->
    PASSWORD = pwd;
}

@PostConstruct
public void init() {<!-- -->
    //Create a remote connection. The default connection port is 22. If you do not use the default, you can use the method Connection(String hostname, int port)
    CONN = new Connection(HOSTNAME);
    try {<!-- -->
        //Connect to remote server
        CONN.connect();
        // Log in with username and password
        CONN.authenticateWithPassword(USERNAME, PASSWORD);
    } catch (IOException e) {<!-- -->
        log.info("Host name: {}, User name: {}, Password: {}", HOSTNAME, USERNAME, PASSWORD);
        log.error("Failed to connect to host,", e);
    }
}

The two knowledge points presented above:

The @Value annotation constructs a static variable. If the @Value annotation is placed directly on the attribute, it will not take effect, such as:

@Value("${ssh.hostname}")
private static String hostname;

Adding static modification will not take effect. The correct way to write it is:

@Value("${ssh.hostname}")
public void setHostname(String name) {<!-- -->
hostname = name;
}

It is best to encrypt the host name, user name, and password, save them in the configuration file, and then decrypt them using the encryption algorithm when establishing a connection to ensure that the account information is not leaked and the data is protected. safety

Execute command processing stream results

/**
 * The current command to be executed
 *
 * @param cmd command instruction
 * @return Execute command and return result
 */
public static String execute(String cmd) {<!-- -->
    String result = "";
    Session session = null;
    try {<!-- -->
        if (CONN != null) {<!-- -->
            //Open a session
            session = CONN.openSession();
            // Excuting an order
            session.execCommand(cmd);
            result = processStdout(session.getStdout(), DEFAULT_CHART);
            log.info("Command executed: {}", cmd);
        }
        return result;
    } catch (IOException e) {<!-- -->
        log.info("Failed to execute command, link conn:{}, command executed: {}, result after execution: {}", CONN, cmd, e.getMessage());
        e.printStackTrace();
        return "Execution failed";
    } finally {<!-- -->
        if (session != null) {<!-- -->
            session.close();
        }
    }
}

/**
 * The result returned by executing the command (the return result needs to wait for all results to be returned before returning)
 *
 * @param in stream object
 * @param charset encoding format
 * @return
 */
private static String processStdout(InputStream in, String charset) {<!-- -->
    InputStream stdout = new StreamGobbler(in);
    StringBuffer buffer = new StringBuffer();
    BufferedReader br = null;
    try {<!-- -->
        br = new BufferedReader(new InputStreamReader(stdout, charset));
        String line;
        while ((line = br.readLine()) != null) {<!-- -->
            buffer.append(line).append("\
");
        }
    } catch (IOException e) {<!-- -->
        log.error("Execution command exception,", e);
    } finally {<!-- -->
        try {<!-- -->
            stdout.close();
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }
        try {<!-- -->
            if (br != null) {<!-- -->
                br.close();
            }
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }
    }
    return buffer.toString();
}

Execute the command to be executed through the previously established session, and process and return the output stream result returned after Linux execution. The format is a stream object established by a channel: ChannelInputStream. We only need to focus on processing the result returned by it.

Access permissions should also be configured for accessible file directories. Each designated file or directory should be managed by a specific user to avoid excessive permissions, misoperation of other information on the server, and affecting the global security of the server.

Calling entrance

/**
 * Get a list of all subdirectories and files in the specified directory
 *
 * @param cmd command executed
 * @param firstPath The parent directory to filter
 * @return The list of directories and files that can be viewed
 */
public static List<ServerFile> getFolderAndFile(String cmd, String firstPath) {<!-- -->
    String result = execute(cmd);
    String[] resultArray = result.split("\
");
    String[] newArray = delete(0, resultArray);
    if (newArray.length == 0) {<!-- -->
        return Collections.emptyList();
    }
    List<ServerFile> readResult = Arrays.stream(newArray).map(str -> {<!-- -->
        ServerFile serverFile = new ServerFile();
        // Determine whether the current line traversal is directory level
        boolean flag = str.startsWith("d");
        String fileName = str.substring(str.lastIndexOf(" ") + 1);
        serverFile.setFolderFlag(flag);
        serverFile.setFileName(fileName);
        serverFile.setPath(firstPath + "/" + fileName);
        if (flag) {<!-- -->
            // If the current line is a directory, you need to continue to recurse the current method to find the files or directories below it.
            List<ServerFile> folderAndFile = getFolderAndFile(cmd + "/" + fileName, serverFile.getPath());
            serverFile.setChildren(folderAndFile);
        }
        return serverFile;
    }).collect(Collectors.toList());
    return readResult;
}

/**
 * This method reconstructs the array and returns it in order to delete the first element in the array. The first row of data is the statistical quantity, so it needs to be removed.
 * <p>
 * [root@trench android]# ls -l
 * total 0
 * -rw-r--r-- 1 root root 0 Nov 1 10:30 1
 * -rw-r--r-- 1 root root 0 Nov 1 10:30 2
 *</p>
 *
 * @return Returns directory and file array
 */
public static String[] delete(int index, String[] array) {<!-- -->
    // Deletion of the array actually means overwriting the previous bit
    String[] arrNew = new String[array.length - 1];
    for (int i = 0; i < array.length - 1; i + + ) {<!-- -->
        if (i < index) {<!-- -->
            arrNew[i] = array[i];
        } else {<!-- -->
            arrNew[i] = array[i + 1];
        }
    }
    return arrNew;
}

This is the method ultimately called by the interface or unit test class, passing in the command to be executed and the first path to be rendered: the directory to be rendered.

Demo

Pass the attributes in the configuration, hotstname, username, password three parameters, as the public IP, username, and password of your virtual machine or real cloud server

Create a demo directory and two blank files as follows:

[root@trench android]# pwd
/var/android
[root@trench android]# ls -l
total 0
-rw-r--r-- 1 root root 0 Nov 1 10:30 1
-rw-r--r-- 1 root root 0 Nov 1 10:30 2

Then the parameters I passed in the unit test are: 1-execute the command “ls -l” 2-the path to be filtered “/var/android”

After execution, the results are as follows:

The following content will be output in the command line window:

The red box is the output content. The output directories and files have specific identifiers. There will be a d keyword in front of the directory. The first line of the output content is the total size. This line needs to be ignored. This is the function of the delete method of the tool class. effect

getFolderAndFile This method organizes the directories and files found by the command into a tree menu. When displayed on the front end, an interface needs to be written to call this method.

Summary

This blog post is a simple practical operation. To operate the Linux command line through Java code, you must first rely on a corresponding dependency, then establish a session connection with the Linux client, and execute the command through the session-query directory files. Application scenarios : Manage Android and IOS packaged files through the PC backend, and standardize version management. I hope this blog post can help you learn from it and apply it to other scenarios.

I hope you and I can warm each other and grow together in the cold winter. Only by constantly accumulating and precipitating ourselves, we will naturally be able to break the ice when we have the opportunity later!

The blog post is placed in the Linux column. Welcome to subscribe and it will be continuously updated!

If you think the blog post is good, follow me vnjohn, and there will be more practical, source code, and architecture information to share in the future!

Recommended columns: Spring, MySQL, subscribe and never get lost again

Everyone’s “Follow + Like + Collection” is the biggest motivation for my creation! Thank you all for your support, see you below!