Unable to use Bash's "-i" option from ProcessBuilder - java

The goal of my program is to run an interactive command line executable from Java, so I can add input partway through when required. Basically redirecting input.
I couldn't find anything that worked online because the -c flag does not allow interactivity, but then I saw that the -i flag in the terminal allowed me to run commands with interactive input if I fed it a .sh file.
However, when I tried using this flag in java, it didn't work. I have separate input and output threads, so if I could get this to work it seems like it would be easy.
Relevant code:
ProcessBuilder pb = new ProcessBuilder()
.directory(new File(testDir))
.inheritIO()
.command("bash", "-i"
,"executor.sh");
proc = pb.start();
this is the error i get:
bash: cannot set terminal process group (1469): Inappropriate ioctl for device
bash: no job control in this shell
If there's way I could get this -i option working, then I'd appreciate pointers to something else that would allow me to get interactive input working because nothing else that I've tried seems to solve this problem.

bash -i is completely unrelated to ability to read from the TTY.
Rather, redirect from the TTY, after your script already started:
#!/usr/bin/env bash
exec </dev/tty || { echo "ERROR: Unable to connect stdin to /dev/tty" >&2; exit 1; }
read -r -p "Fill out this prompt please: " value
echo "Read from TTY: $value"
The command exec </dev/tty replaces the script's stdin (FD 0) with a read handle on /dev/tty. If you wanted to do this just for a single command, rather than for the whole script, put </dev/tty on the end of that command.
Of course, this only works if your process is run in a context where it has a controlling terminal at all -- but if that weren't the case, you couldn't read from the user without getting some kind of handle on an I/O device regardless.

Related

open terminal failed: missing or unsuitable terminal: unknown when running shell script from Java program

I had a Java program running a shell script with a Process, but for some reason when I try to run it it throws an error open terminal failed: missing or unsuitable terminal: unknown. From other SO questions, I think this is a tmux problem, but I'm not really sure how to solve it. Here's the code calling the script:
ProcessBuilder pb = new ProcessBuilder("/Users/user/eclipse-workspace/project/start.sh");
Process p = pb.start();
BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
System.out.println("output: ");
String s;
while ((s = stdInput.readLine()) != null) {
System.out.println(s);
}
And here's the shell script:
#! /bin/sh
ssh -tt -i ~/.ssh/ssh-key.key opc#___._.___.___ tmux attach -d << END
./run.sh
END
exit 0
I have tried running the script from terminal, and it works from the terminal but it doesn't work when I run the Java program.
The problem is that you are attaching to an interactive tmux session, where you need to have a terminal which supports cursor movement codes etc.
The easy workaround is to not attach; just submit the command you want to run into the session.
ssh -tt -i ~/.ssh/ssh-key.key opc#___._.___.___ tmux send-keys './run' C-m
This obviously requires that whatever is running inside the remote tmux session is in a state where you can submit a shell command, i.e. at a shell prompt or similar. For robustness you might want to take additional measures to make sure this is always the case, or refactor your solution to avoid running inside tmux e.g. by having the command redirect its output to a file where you can examine it from any terminal at any time (though this assumes you don't also need to interact with it subsequently).

Stop AppleScript Running .jar When Java Error Raised

I have an AppleScript which I am using to run a .jar file. The .jar file takes several inputs which were originally entered via the command line but now I enter into a .csv and get read into the .jar automatically. For unknown reasons, sometimes a number in the CSV is not read correctly leading to a NumberFormatException in the Java code. However, instead of breaking, my script continually tries to enter the invalid input in an infinite loop. Is there a way to amend my code so that when an error is raised by the .jar, the script stops?
Here is my current code:
on RunFile(jar_location)
do shell script "cd " & jar_location & " ; cat 'prompt.csv' | sh 'runScript.sh' 'WSO'"
end RunFile
After going over this in the comments, it's clear that the problem is that the .jar file is trying to be interactive — asking for some input at the cursor — and AppleScript's do shell script is not designed for that. AppleScript can get errors and outputs form the shell, but it cannot feed a response back to the shell, or tell if a shell script is waiting for input.
if the .jar file cannot be operated in a non-interactive mode, then the only for AppleScript to make sure the process ends is to grab its process id number, wait a reasonable amount of time, and then send it a kill signal. That script would look like this:
on RunFile(jar_location)
set pid to do shell script "cd " & jar_location & " ; cat 'prompt.csv' | sh 'runScript.sh' 'WSO' &> /dev/null & echo $!"
-- wait 5 seconds, or whatever seems appropriate for the task to complete
delay 5
try
do shell script "kill " & pid
end try
end RunFile
The appended &> /dev/null & echo $! phrase detaches the shell script, allowing the AppleScript to move forward, and returns the process id of the process for later use. I've put the kill signal in a try block so that the script does not throw an error if the process has already exited normally.

Logging in into shell as sudo user and executing shell through java

i have a shell file that needs to be executed through java program. But, the problem is the shell file is n't getting executed because usually we run shell files through command line after executing the sudo command which in turn asks for password.Is there any possibility that i can execute the shell using Runtime.exec("ksh path-to-shell-file") by-passing the sudo command.
You can write commands to the outStream of the process from Runtime.exec();
Get the outStream, write a command to it, and then write "\r\n" to it to execute.
So basically treat the process as a console, and write commands to it like to normal console. Obviously you can also get the in and err streams

Starting and killing java app with shell script (Debian)

I'm new to UNIX. I want to start my java app with a script like so:
#!/bin/sh
java -jar /usr/ScriptCheck.jar &
echo $! > /var/run/ScriptCheck.pid
This is supposedly working. It does run the app and it does write the pid file. But when I try to stop the process with a different script which contains this:
#!/bin/sh
kill -9 /var/run/ScriptCheck.pid
the console gives me this error:
bash: kill: /var/run/ScriptCheck.pid: arguments must be process or job IDs
My best guess is that I'm not writing the right code in the stop script, maybe not giving the right command to open the .pid file.
Any help will be very appreciated.
You're passing a file name as an argument to kill when it expects a (proces id) number, so just read the process id from that file and pass it to kill:
#!/bin/sh
PID=$(cat /var/run/ScriptCheck.pid)
kill -9 $PID
A quick and dirty method would be :
kill -9 $(cat /var/run/ScriptCheck.pid)
Your syntax is wrong, kill takes a process id, not a file. You also should not be using kill -9 unless you absolutely know what you are doing.
kill $(cat /var/run/ScriptCheck.pid)
or
xargs kill </var/run/ScriptCheck.pid
I think you need to read in the contents of the ScriptCheck.pid file (which I'm assuming has only one entry with the PID of the process in the first row).
#!/bin/sh
procID=0;
while read line
do
procID="$line";
done </var/run/ScriptCheck.pid
kill -9 procID
I've never had to create my own pid; your question was interesting.
Here is a bash code snippet I found:
#!/bin/bash
PROGRAM=/path/to/myprog
$PROGRAM &
PID=$!
echo $PID > /path/to/pid/file.pid
You would have to have root privileges to put your file.pid into /var/run --referenced by a lot of articles -- which is why daemons have root privileges.
In this case, you need to put your pid some agreed upon place, known to your start and stop scripts. You can use the fact a pid file exists, for example, not to allow a second identical process to run.
The $PROGRAM & puts the script into background "batch" mode.
If you want the program to hang around after your script exits, I suggest launching it with nohup, which means the program won't die, when your script logs out.
I just checked. The PID is returned with a nohup.

How to execute unix commands through Windows/cygwin using Java

I am trying to accomplish two things:
I am running cygwin on Windows7 to execute my unix shell commands and I need to automate the process by writing a Java app. I already know how to use the windows shell through Java using the 'Process class' and Runtime.getRuntime().exec("cmd /c dir"). I need to be able to do the same with unix commands: i.e.: ls -la and so forth. What should I look into?
Is there a way to remember a shell's state?
explanation: when I use: Runtime.getRuntime().exec("cmd /c dir"), I always get a listing of my home directory. If I do Runtime.getRuntime().exec("cmd /c cd <some-folder>") and then do Runtime.getRuntime().exec("cmd /c dir") again, I will still get the listing of my home folder. Is there a way to tell the process to remember its state, like a regular shell would?
It seems that the bash command line proposed by Paŭlo does not work:
C:\cygwin\bin>bash -c ls -la
-la: ls: command not found
I am having trouble figuring out the technicalities.
This is my code:
p = Runtime.getRuntime().exec("C:\\cygwin\\bin\\bash.exe -c ls -la");
reader2 = new BufferedReader(new InputStreamReader(p.getInputStream()));
line = reader2.readLine();
line ends up having a null value.
I added this to my .bash_profile:
#BASH
export BASH_HOME=/cygdrive/c/cygwin
export PATH=$BASH_HOME/bin:$PATH
I added the following as well:
System Properties -> advanced -> Environment variables -> user variebales -> variable: BASH, value: c:\cygwin\bin
Still nothing...
However, if I execute this instead, it works!
p = Runtime.getRuntime().exec("c:\\cygwin\\bin\\ls -la ~/\"Eclipse_Workspace/RenameScript/files copy\"");
1. Calling unix commands:
You simply need to call your unix shell (e.g. the bash delivered with cygwin) instead of cmd.
bash -c "ls -la"
should do. Of course, if your command is an external program, you could simply call it directly:
ls -la
When starting this from Java, it is best to use the variant which takes a string array, as then
you don't have Java let it parse to see where the arguments start and stop:
Process p =
Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe",
"-c", "ls -la"},
new String[]{"PATH=/cygdrive/c/cygwin/bin"});
The error message in your example (ls: command not found) seems to show that your bash can't find the ls command. Maybe you need to put it into the PATH variable (see above for a way to do this from Java).
Maybe instead of /cygdrive/c/cygwin/bin, the right directory name would be /usr/bin.
(Everything is a bit complicated here by having to bridge between Unix and Windows
conventions everywhere.)
The simple ls command can be called like this:
Process p = Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\ls.exe", "-la"});
2. Invoking multiple commands:
There are basically two ways of invoking multiple commands in one shell:
passing them all at once to the shell; or
passing them interactively to the shell.
For the first way, simply give multiple commands as argument to the -c option, separated by ; or \n (a newline), like this:
bash -c "cd /bin/ ; ls -la"
or from Java (adapting the example above):
Process p =
Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe",
"-c", "cd /bin/; ls -la"},
new String[]{"PATH=/cygdrive/c/cygwin/bin"});
Here the shell will parse the command line as, and execute it as a script. If it contains multiple commands, they will all be executed, if the shell does not somehow exit before for some reason (like an exit command). (I'm not sure if the Windows cmd does work in a similar way. Please test and report.)
Instead of passing the bash (or cmd or whatever shell you are using) the commands on the
command line, you can pass them via the Process' input stream.
A shell started in "input mode" (e.g. one which got neither the -c option nor a shell script file argument) will read input from the stream, and interpret the first line as a command (or several ones).
Then it will execute this command. The command itself might read more input from the stream, if it wants.
Then the shell will read the next line, interpret it as a command, and execute.
(In some cases the shell has to read more than one line, for example for long strings or composed commands like if or loops.)
This will go on until either the end of the stream (e.g. stream.close() at your side) or executing an explicit exit command (or some other reasons to exit).
Here would be an example for this:
Process p = Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe", "-s"});
InputStream outStream = p.getInputStream(); // normal output of the shell
InputStream errStream = p.getInputStream(); // error output of the shell
// TODO: start separate threads to read these streams
PrintStream ps = new PrintStream(p.getOutputStream());
ps.println("cd /bin/");
ps.println("ls -la");
ps.println("exit");
ps.close();
You do not need cygwin here. There are several pure Java libraries implementing SSH protocol. Use them. BTW they will solve your second problem. You will open session and execute command withing the same session, so the shell state will be preserved automatically.
One example would be JSch.

Categories