-2

This post is a follow-up to a previous post of mine. As has been mentioned in that post, certain Linux "commands" are built-in and give the java.io.IOException when invoked using the Runtime.getRuntime().exec(...) approach.

history is the only command with this issue that I have been able to find so far but I have no reason to believe that it is the only one.


Question: Is there a way to invoke "built-in" Linux commands, like history from Java?

Sandeep
  • 1,245
  • 1
  • 13
  • 33
  • Does this answer your question? [Invoking command history from a Java program](https://stackoverflow.com/questions/68149792/invoking-command-history-from-a-java-program) – OldProgrammer Jun 27 '21 at 19:53
  • As others have noted, "built-in" refers to built into the shell (bash) - so you'll need to execute the command within a bash shell. Here's one of many examples: https://stackoverflow.com/a/26830876/2711811 . –  Jun 27 '21 at 19:58
  • Does this answer your question? [Running Bash commands in Java](https://stackoverflow.com/questions/26830617/running-bash-commands-in-java) –  Jun 27 '21 at 20:00

3 Answers3

3

Certain Linux "commands" are built-in

This is incorrect. No such thing.

You're presumably talking about shell built-ins. There are a million linux variants and a lot of shell variants. Each shell gets to decide their own builtins. Often, things are built-in that nevertheless also exist as an actual executable. For example, I bet on your linux system /bin/ls exists, but ls is also a built-in in most shells. Try running man builtin, it should list the built-ins, probably (all of this is festooned with 'maybe' and 'should' and 'probably' - linux isn't a single idea, there are many distros, many ways of packing it up. No linux spec demands that man builtin works).

You can run builtins, of course. But, /bin/bash has the code for these builtins, not 'linux'. You can for example try this code, which is exceedingly likely to work on just about every imaginable flavour of posix, and should do what you actually intend, and should not have significant security issues.

A few things to keep in mind:

  • Do not, ever, use relative paths in Runtime.exec. Just don't - it's unlikely to work, you're relying on JAVA's ability to decode $PATH (when you type, say, curl, and that this runs /usr/bin/curl because /usr/bin is on the path? Yeah that's bash, not linux. Linux doesn't know what PATH is.

  • Always use ProcessBuilder, and use the String... or List<String> version, you don't want to rely on java splitting on spaces (splitting on spaces? Yup - bash, not linux)

  • Remember that star expansion is also a shell-ism, except on windows where it isn't. /usr/bin/imgconvert *.jpg doesn't work with process builder, because unpacking that * is a thing bash does and java's process builder won't do that for you. Thus, pass only the most obvious simple thing possible for arguments. Don't pass wildcards, don't pass var substitutions, of any sort.

  • Anytime you can't do the job unless you rely on the shell (i.e. you want to run builtins or you need that shell expansion stuff to work), run the shell and ask IT to run the command!

Path p = Paths.get("/bin/ls");
List<String> cmd;
if (Files.isExecutable(p)) {
    cmd = List.of("/bin/ls");
} else {
    // run bash, and tell it to run something immediately instead
    // of running an interactive prompt.
    cmd = List/of("/bin/bash", "-c", "ls");
}

ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.start();

This will run /bin/X if it exists (many builtins do also show up as an executable, even if they are a builtin in most shells), and otherwise, bash. Don't be tempted to run /bin/sh - whilst that is generally there, you don't know which shell it is, and it is extremely difficult to write 'linux shell command-ese' in a way that it works in all shells. It can be done, but, oof. Takes special skills and considerable testing. /bin/bash exists virtually everywhere, use it. Even if bash as a shell is kinda shite.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
1

I wouldn't recommend it but you could run shell built-in commands directly from Java ProcessBuilder by passing the built-in command via STD input.

This is quite an untidy way to achieve history.

private static void run(String[] cmd, byte[] stdin) throws IOException, InterruptedException  {
    ProcessBuilder pb = new ProcessBuilder(cmd);

    // No STDERR => merge to STDOUT
    pb.redirectErrorStream(true);

    // Launch and wait:
    Process p = pb.start();

    // Send + close STDIN stream
    try(OutputStream os = p.getOutputStream()) {
        if (stdin != null) os.write(stdin);
    }
    // Print STDOUT+ERR stream
    try(var stdo = p.getInputStream()) {
        stdo.transferTo(System.out);
    }

    int rc = p.waitFor();
    System.out.println("END "+Arrays.toString(cmd)+" rc="+rc);
}

You'd need to filter out the bash reply / prompt if doing anything meaningful with the contents this way:

run(new String[] { "bash", "-i"}, "history".getBytes());

Or save history to a file, then read the command output without shell prompts:

run(new String[] { "bash", "-i"}, "history > history.txt".getBytes());
System.out.println("HISTORY:");
System.out.println(Files.readString(Path.of("history.txt")));

You could also try reading directly from the history file.

DuncG
  • 12,137
  • 2
  • 21
  • 33
0

History is is a command within Bash, so not directly. You would need to call a shell script and make it interactive, as in...

localhost:~/tmp> cat g.sh

#!/bin/bash
history

bash -li g.sh

Bib
  • 922
  • 1
  • 5
  • 10