How to pipe a string argument to an executable lau

2019-01-24 06:36发布

问题:

I need to pipe a text argument to the stdin of a command launched with Apache Commons Exec (for the curious, the command is gpg and the argument is the passphrase to the keystore; gpg does not have an argument to provide the passphrase explicitly, only to accept it from stdin).

In addition, I need this to support both Linux and Windows.

In a shell script I'd do

cat mypassphrase|gpg --passphrase-fd

or

type mypassphrase|gpg --passphrase-fd

but type doesn't work on Windows as it's not an executable but a command built into the command interpreted (cmd.exe).

The code not working (for the above reason) is below. To spawn an entire shell for this is too ugly, I was looking for a more elegant solution. Unfortunately, there are some incompatibility problems between the BouncyCastle library and PGP so I cannot use a fully programmatic solution in the (very short) time I have.

Thanks in advance.

CommandLine cmdLine = new CommandLine("type");
cmdLine.addArgument(passphrase);
cmdLine.addArgument("|");
cmdLine.addArgument("gpg");
cmdLine.addArgument("--passphrase-fd");
cmdLine.addArgument("0");
cmdLine.addArgument("--no-default-keyring");
cmdLine.addArgument("--keyring");
cmdLine.addArgument("${publicRingPath}");
cmdLine.addArgument("--secret-keyring");
cmdLine.addArgument("${secretRingPath}");
cmdLine.addArgument("--sign");
cmdLine.addArgument("--encrypt");
cmdLine.addArgument("-r");
cmdLine.addArgument("recipientName");
cmdLine.setSubstitutionMap(map);
DefaultExecutor executor = new DefaultExecutor();
int exitValue = executor.execute(cmdLine);

回答1:

You cannot add a pipe argument (|) because the gpg command won't accept that. It's the shell (e.g. bash) that interprets the pipe and does special processing when you type that commandline into the shell.

You can use ByteArrayInputStream to manually send data to the standard input of a command (much like bash does when it sees the |).

    Executor exec = new DefaultExecutor();

    CommandLine cl = new CommandLine("sed");
            cl.addArgument("s/hello/goodbye/");

    String text = "hello";
    ByteArrayInputStream input =
        new ByteArrayInputStream(text.getBytes("ISO-8859-1"));
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    exec.setStreamHandler(new PumpStreamHandler(output, null, input));
    exec.execute(cl);

    System.out.println("result: " + output.toString("ISO-8859-1"));

This should be the equivalent of typing echo "hello" | sed s/hello/goodbye/ into a (bash) shell (though UTF-8 may be a more appropriate encoding).



回答2:

Hi to do this i will use a little helper class like this: https://github.com/Macilias/Utils/blob/master/ShellUtils.java

basically you can than simulate the pipe usage like shown here before without calling the bash beforehand:

public static String runCommand(String command, Optional<File> dir) throws IOException {
    String[] commands = command.split("\\|");
    ByteArrayOutputStream output = null;
    for (String cmd : commands) {
        output = runSubCommand(output != null ? new ByteArrayInputStream(output.toByteArray()) : null, cmd.trim(), dir);
    }
    return output != null ? output.toString() : null;
}

private static ByteArrayOutputStream runSubCommand(ByteArrayInputStream input, String command, Optional<File> dir) throws IOException {
    final ByteArrayOutputStream output = new ByteArrayOutputStream();
    CommandLine cmd = CommandLine.parse(command);
    DefaultExecutor exec = new DefaultExecutor();
    if (dir.isPresent()) {
        exec.setWorkingDirectory(dir.get());
    }
    PumpStreamHandler streamHandler = new PumpStreamHandler(output, output, input);
    exec.setStreamHandler(streamHandler);
    exec.execute(cmd);
    return output;
}