4

Перенаправление ввода/вывода в Java

Воть и сам муж появился... :))) Пока попробую опубликовать те статейки, котрые писал на vingrad.ru
21.01.08
9.28
Да-а-а-а... "совсем ерунда - перенаправить вывод программ куда-нибудь...". Все оказалось не так просто, как я думал. В тех книжках, где я имел счастье видеть примеры запуска внешних программ либо не было сказано, как это делать вообще, либо описано это очень неточно и неполно. К такому выводу я пришел, когда у меня наконец-то все заработало.

Итак, сперва я решил просто запустить внешнюю программу, но не notepad, как в большинстве примеров в книгах по java (на мой взгляд, более бесполезного примера трудно придумать), а внешнюю java-программу.Строка запуска должна быть вот такой:
java -cp . -ea HelloWorld
где программка HelloWorld выводит всем известную со времен Кернигана и Ричи фразу :). Сам текст внешней программки:
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
System.out.println("Hello, world!");
System.exit(2);
}
}
Дело видете ли в том, что мне помимо запуска внешней программы надо получить код, возвращаемый программой, и если он отличается от 0 (т.е. произошла ошибка), вывести сообщение, которое вывела внешняя программа. В данном случае возвращаю 2, что бы было понятно что получившееся значение - результат работы именно этой программы, а не что-либо еще (например, код ошибки, возвращаемый java-интерпретатором). Вот мое первое творение (весьма далекое от того, что должно быть, поскольку в кач-ве образца использовался простейший пример с notepad-ом из книги "Полный справочник по Java" Г.Шилдта):
import java.lang.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello", "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}

Запускаем RunHello и пока все отлично - программа работает, только сообщение "Hello, world!" не выводится.
Теперь немножко изменим программку HelloWorld:
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
for (int i = 1; i <= 40; i++)
System.out.println("Hello, world! " + i);
System.exit(2);
}
}

Теперь сообщение должно вывестись 40 раз подряд, вместе с порядковым номером вывода. Запускаем RunHello и.. ничего не происходит - программа благополучно повисает. Дабы этого не происходило, нужно читать вывод программы. То есть нужно читать из потоков, возвращаемых методами getInputStream() и getErrorStream(), определенных в классе Process.
Модифицируем нашу программку RunHello:
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream()));
int m;
do{
m = stdinput.read();
System.out.print((char)m);
}while(m >= 0);
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Вывод дочернего процесса естественно, не обязательно печатать. Все замечательно работает, и нет проблем. Но пока рано радоваться :) Теперь немножко модифицируем программу HelloWorld. Помимо стандартного устройства вывода будем печатать еще и на стандартное устройство ошибок.
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
for (int i = 1; i <= 40; i++)
{
System.out.println("Hello, world! " + i);
if(i < 30)
System.err.println("It's prints to stderr" + i);
}
System.exit(2);
}
}
Теперь наша программа выведет некоторое число сообщений "Hello, world! " и встанет, поскольку мы в програме RunHello не читаем из стандартного устройства ошибок. Что ж, надо читать :)
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream())),
stderror = new BufferedReader(new InputStreamReader(p.getErrorStream()));
int m1, m2;
do{
m1 = stdinput.read();
if(m1 > 0)
System.out.print((char)m1);
m2 = stderror.read();
}while((m1 >= 0) || (m2 >= 0));
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Пока у меня на Win98 все отлично работает. Попробуем в программе HelloWorld количество циклов увеличить с 40 до 80:
for (int i = 1; i <= 80; i++)
{
System.out.println("Hello, world! " + i);
if(i < 30)
System.err.println("It's prints to stderr" + i);
}
Как видим, приключения еще не кончились! :) Опять программа подвисает, у меня - после вывода 40-го сообщения. Если честно, пока я проходил все эти стадии прозрения, мой стол немного пострадал, поскольку я лупил по нему кулаком от злости :) Так же немного пострадал мой кулак :).
Но все же есть выход из положения, позволяющий запускать любые внешние программы, что угодно печатающие на консоль и в стандартное устройство ошибок. Прежде чем читать что-то из потоков stderror или stdinput, необходимо проверить, можно ли производить чтение и не приведет ли это к блокировке процессов. Это можно сделать, вызывая для потоков метод ready(). Он возвращает true(истину), если читать из потока можно. Итак, окончательная версия программки RunHello у меня получилась такой:
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Projects\\Java\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream())),
stderror = new BufferedReader(new InputStreamReader(p.getErrorStream()));
int res=0;
int m1;
do
{
if( stderror.ready() || stdinput.ready())
{
if(stdinput.ready())
{
m1 = stdinput.read();
if(m1 >= 0)
System.out.print( (char)m1 );
}
if(stderror.ready())
stderror.read();
}
else
try{
res = p.exitValue();
break;
}catch(java.lang.IllegalThreadStateException ex){}
}while(true);
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}

В цикле сначала проверяем, готов ли какой-либо поток к чтению из него. Если оба потока не готовы - есть вероятность, что программа закончила свою работу (но это еще не факт!! Как корректно проверить, завершил процесс работу или нет, я так и не нашел в документации на Java API. Если кто знает - напишите, буду весьма признателен! :) ) После пробуем получить значение, возвращаемое внешней программой. Если внешняя программа завершила работу - завершаем цикл и печатаем возвращаемый код.

4 коммент.:

Unknown комментирует...

Я же говорила, что писатель у нас - муж! Короче, много букофф ты, любимый, настрочил. Вот только все слова мне непонятные:)))

Zuborg комментирует...

Так чем плох способ определения завершения программы, приведенный в тексте? try{
m1=0;p.exitValue();
// break; }catch(java.lang.IllegalThreadStateException ex){m1=1;}
Исключение генерится как раз если процесс "р" не завершен. Во "while" вместо "true" можно поставить условие "m1>0" оно неплохо земенит "break" выше по тексту.

Lotrex комментирует...

Да, действительно, exitValue() генерит это исключение только при условии, если процесс не завершен. Так что такой способ проверки завершения процесса вполне подходит :)

Анонимный комментирует...

Вот тут слегка другое предложение
http://inotroot.by.ru/programming/java/14chapt.htm

public abstract int waitFor() throws InterruptedException

Сколь угодно долго ожидает завершения процесса-потомка и возвращает значение, переданное им методу System.exit или его аналогу (ноль означает успешное завершение, все остальное — неудачу). Если процесс уже завершился, то просто возвращается значение.

Отправить комментарий