Como fazer tubos funcionarem com Runtime.exec ()?

104

Considere o seguinte código:

String commandf = "ls /etc | grep release";

try {

    // Execute the command and wait for it to complete
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Print the first 16 bytes of its output
    InputStream i = child.getInputStream();
    byte[] b = new byte[16];
    i.read(b, 0, b.length); 
    System.out.println(new String(b));

} catch (IOException e) {
    e.printStackTrace();
    System.exit(-1);
}

O resultado do programa é:

/etc:
adduser.co

Quando executo a partir do shell, é claro, funciona conforme o esperado:

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release

As internets me dizem que, devido ao fato de que o comportamento do pipe não é multiplataforma, as mentes brilhantes que trabalham na fábrica Java produzindo Java não podem garantir que o pipe funcione.

Como posso fazer isso?

Não vou fazer toda a minha análise usando construções Java em vez de grepe sed, porque se eu quiser alterar a linguagem, serei forçado a reescrever meu código de análise nessa linguagem, o que é totalmente impossível.

Como posso fazer o Java fazer piping e redirecionamento ao chamar comandos shell?

poundifdef
fonte
Eu vejo assim: Se você fizer isso com o manuseio de string Java nativo, você terá a portabilidade do aplicativo Java para todas as plataformas de suporte Java. OTOH, se você fizer isso com comandos de shell, é mais fácil mudar o idioma do Java, mas só funcionará quando você estiver em uma plataforma POSIX. Poucas pessoas mudam o idioma do aplicativo em vez da plataforma em que o aplicativo é executado. É por isso que acho seu raciocínio um pouco curioso.
Janus Troelsen
No caso específico de algo simples como command | grep foovocê, é muito melhor apenas executar commande filtrar nativamente em Java. Isso torna seu código um pouco mais complexo, mas também reduz significativamente o consumo geral de recursos e a superfície de ataque.
tripleee

Respostas:

182

Escreva um script e execute-o em vez de comandos separados.

Pipe é uma parte da casca, então você também pode fazer algo assim:

String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};

Process p = Runtime.getRuntime().exec(cmd);
Kaj
fonte
@Kaj E se você quisesse adicionar opções ao lsie ls -lrt?
Kaustav datta
8
@Kaj Vejo que você está tentando usar -c para especificar uma string de comandos para o shell, mas não entendo por que você tem que fazer um array de string em vez de apenas uma única string.
David Doria
2
Se alguém está procurando androidversão aqui, então usar /system/bin/shvez
Nexen
25

Encontrei um problema semelhante no Linux, exceto que era "ps -ef | grep someprocess".
Pelo menos com "ls" você tem uma substituição Java independente da linguagem (embora mais lenta). Por exemplo.:

File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
    if (file.matches(.*some.*)) { System.out.println(file); }
}

Com "ps", é um pouco mais difícil, porque Java não parece ter uma API para isso.

Ouvi dizer que Sigar pode nos ajudar: https://support.hyperic.com/display/SIGAR/Home

A solução mais simples, entretanto, (conforme apontado por Kaj) é executar o comando piped como um array de string. Aqui está o código completo:

try {
    String line;
    String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader in =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = in.readLine()) != null) {
        System.out.println(line); 
    }
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}

Por que o array String funciona com pipe, enquanto uma única string não ... é um dos mistérios do universo (especialmente se você não leu o código-fonte). Suspeito que seja porque quando exec recebe uma única string, ele a analisa primeiro (de uma maneira que não gostamos). Em contraste, quando exec recebe uma matriz de string, ele simplesmente a passa para o sistema operacional sem analisá-la.

Na verdade, se tirarmos um tempo do dia agitado e olharmos o código-fonte (em http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/ Runtime.java # Runtime.exec% 28java.lang.String% 2Cjava.lang.String []% 2Cjava.io.File% 29 ), descobrimos que é exatamente o que está acontecendo:

public Process  [More ...] exec(String command, String[] envp, File dir) 
          throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");
    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}
Tihamer
fonte
Estou vendo o mesmo comportamento com o redirecionamento, ou seja, o caractere '>'. Esta análise extra sobre matrizes de String é esclarecedora
kris
Você não precisa perder um dia de sua agenda lotada - está escrito na frente de seus olhos docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch
6

Crie um Runtime para executar cada um dos processos. Obtenha o OutputStream do primeiro Runtime e copie-o para o InputStream do segundo.

SJuan76
fonte
1
Vale a pena ressaltar que isso é muito menos eficiente do que um pipe os-native, uma vez que você está copiando a memória no espaço de endereço do processo Java e, em seguida, de volta para o processo subsequente.
Tim Boudreau
0

A resposta aceita @Kaj é para linux. Este é o equivalente para Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
gritar
fonte