Executing commands within a Shell

In Working with the Client API we briefly covered how to execute a command using the AbstractCommandTask. In the Maverick NG Client API tasks are how we perform operations over SSH. This article outlines the options available for the AbstractCommandTask.

Executing a command using the API can be a little restrictive and users don't particularly utilise this feature as much as they do the shell. The default behaviour of most SSH clients is to log users directly into an interactive shell allowing them to execute command after command, with the environment maintaining state between.

Within the SSH protocol this is all done within a single session. When using the shell you are trying to mimic how the user interacts with the session so you have to write out commands, ensuring you send the correct EOL sequence and have to filter the output of the session to detect when one command ends, when the prompt is displayed and where commands start. This has historically been problematic to users of any SSH API.

With the Maverick NG API you have a choice, start a shell and manage the output yourself using just the standard streams provided or use our Shell implementation that provides useful tools for handling shell input and output, including expect type functionality.

To do either of these you can start the following task template:

    con.addTask(new AbstractShellTask(con) {
        @Override
	protected void onOpenSession(SessionChannel session) {

	}
					
	@Override
        protected void onCloseSession(SessionChannel session) {

} });

The AbstractShellTask has an InputStream implementation that will allow you to read from the command output. You should do this in the onOpenSession method of your implementation ofAbstractShellTask.

    @Override
    protected void onOpenSession(SessionChannel session) {		
	try {
	    int b;
	    byte[] buf = new byte[1024];
	    while((b = getInputStream().read(buf)) > -1) {
	        // Use the buffer as needed
	    }
	} catch (IOException e) {
} }

Similarly if you need to write out to the command you can use getOutputStream method ofAbstractShellTask to write to the remote process. 

Using the Shell Implementation

To manipulate bash type shells we have provided the Shell utility class. Using this you are able to execute a command and receive a ShellProcess object that will filter just the individual command output. 

The Shell class is implemented using various techniques to extract individual command output out of a single stream of data. For example on *nix type systems a bash type shell is expected that supports echo to output specific markers before and after the command. 

When executing a command a ShellProcess object is returned that has an InputStream and OutputStream allowing you to read and write to the process. 

For example to execute a simple ls -l command and read its output we would:

    ShellProcess process = shell.executeCommand("ls -l"); 
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while((line = reader.readLine())!=null) {
System.out.println(line);
}

Once the input has returned EOF the command output has completed and the Shell instance will be ready for another command. We can also check the exit status of the command using

    int exitCode = process.getExitCode();

Whether the exit code is available is dependent upon the platform you have connected to. You should check the exit code return value for the Shell.EXIT_CODE_UNKNOWN and Shell.EXIT_CODE_PROCESS_ACTIVE constants to ensure you have a valid code.

On windows its not possible to get the exact exit code because %errorlevel% does not work on a command line with multiple commands so "0" is returned for a successful command or "1" for a failed command. Similarly on OpenVMS the same applies.

If you want to control the process more such as providing input to a command or reading much like with a BufferedReader we have made this simpler by providing the ShellProcessController class. This provides more control with readLine method already built-in

    ShellProcessController controller = new ShellProcessController(
shell.executeCommand("ls -l"));;
while((line = controller.readLine())!=null) { System.out.println(line); }

 To interact with commands where specific input is required in response to specific output we can script the command using the expect method. In this example we use an interactive version of the rm command to prompt the user.

    ShellProcessController controller = 
new ShellProcessController(
shell.executeCommand("rm -i 123"));
if(controller.expect("remove 123?")) { controller.typeAndReturn("y"); } 

Note how we expect the output, and when its detected and expect returns true, we type the answer and importantly press return too as the user would in an interactive shell.

Using Sudo Commands

In Shell we have built support for executing commands using sudo. This will automatically handle the password prompt and submit the password provided. If the password prompt is not detected it will continue, we recommend that you use the -k switch to ensure the sudo command always prompts for the password.

    shell.sudo("sudo -k apt-get update", "xxxxxx")

The sudo method returns a ShellProcess that you can use in the same way as before. 

Switching User

Within a shell a user can use a command such as su to switch to another users shell. This provides an entirely new shell until the user exits and returns to the previous shell. This is also supported in the Shell implementation

    Shell newShell = shell.su("su lee", "xxxxx")

Here we execute the su command requesting to switch a user and providing the password, which is automatically handled. The su method returns a new Shell object that represents the new shell. You can use this to execute commands within the new shell, and when you exit, return to the previous Shell object to continue in the original shell. 

Allocating a Pseudo Terminal

When using Shell you should configure a pseudo terminal to be allocated using the following code. The dumb terminal type helps to ensure you do not get any terminal escape sequences output in your process output.

    sshContext.setAllocatePseudoTerminal(true);
    sshContext.setTerminalType("dumb");
    sshContext.setTerminalColumns(80);
    sshContext.setTerminalRows(25);

 

Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.