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.