Java tutorial
/* * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.Map; import sun.security.action.GetPropertyAction; /** * This class is used to create operating system processes. * * <p>Each {@code ProcessBuilder} instance manages a collection * of process attributes. The {@link #start()} method creates a new * {@link Process} instance with those attributes. The {@link * #start()} method can be invoked repeatedly from the same instance * to create new subprocesses with identical or related attributes. * <p> * The {@link #startPipeline startPipeline} method can be invoked to create * a pipeline of new processes that send the output of each process * directly to the next process. Each process has the attributes of * its respective ProcessBuilder. * * <p>Each process builder manages these process attributes: * * <ul> * * <li>a <i>command</i>, a list of strings which signifies the * external program file to be invoked and its arguments, if any. * Which string lists represent a valid operating system command is * system-dependent. For example, it is common for each conceptual * argument to be an element in this list, but there are operating * systems where programs are expected to tokenize command line * strings themselves - on such a system a Java implementation might * require commands to contain exactly two elements. * * <li>an <i>environment</i>, which is a system-dependent mapping from * <i>variables</i> to <i>values</i>. The initial value is a copy of * the environment of the current process (see {@link System#getenv()}). * * <li>a <i>working directory</i>. The default value is the current * working directory of the current process, usually the directory * named by the system property {@code user.dir}. * * <li><a id="redirect-input">a source of <i>standard input</i></a>. * By default, the subprocess reads input from a pipe. Java code * can access this pipe via the output stream returned by * {@link Process#getOutputStream()}. However, standard input may * be redirected to another source using * {@link #redirectInput(Redirect) redirectInput}. * In this case, {@link Process#getOutputStream()} will return a * <i>null output stream</i>, for which: * * <ul> * <li>the {@link OutputStream#write(int) write} methods always * throw {@code IOException} * <li>the {@link OutputStream#close() close} method does nothing * </ul> * * <li><a id="redirect-output">a destination for <i>standard output</i> * and <i>standard error</i></a>. By default, the subprocess writes standard * output and standard error to pipes. Java code can access these pipes * via the input streams returned by {@link Process#getInputStream()} and * {@link Process#getErrorStream()}. However, standard output and * standard error may be redirected to other destinations using * {@link #redirectOutput(Redirect) redirectOutput} and * {@link #redirectError(Redirect) redirectError}. * In this case, {@link Process#getInputStream()} and/or * {@link Process#getErrorStream()} will return a <i>null input * stream</i>, for which: * * <ul> * <li>the {@link InputStream#read() read} methods always return * {@code -1} * <li>the {@link InputStream#available() available} method always returns * {@code 0} * <li>the {@link InputStream#close() close} method does nothing * </ul> * * <li>a <i>redirectErrorStream</i> property. Initially, this property * is {@code false}, meaning that the standard output and error * output of a subprocess are sent to two separate streams, which can * be accessed using the {@link Process#getInputStream()} and {@link * Process#getErrorStream()} methods. * * <p>If the value is set to {@code true}, then: * * <ul> * <li>standard error is merged with the standard output and always sent * to the same destination (this makes it easier to correlate error * messages with the corresponding output) * <li>the common destination of standard error and standard output can be * redirected using * {@link #redirectOutput(Redirect) redirectOutput} * <li>any redirection set by the * {@link #redirectError(Redirect) redirectError} * method is ignored when creating a subprocess * <li>the stream returned from {@link Process#getErrorStream()} will * always be a <a href="#redirect-output">null input stream</a> * </ul> * * </ul> * * <p>Modifying a process builder's attributes will affect processes * subsequently started by that object's {@link #start()} method, but * will never affect previously started processes or the Java process * itself. * * <p>Most error checking is performed by the {@link #start()} method. * It is possible to modify the state of an object so that {@link * #start()} will fail. For example, setting the command attribute to * an empty list will not throw an exception unless {@link #start()} * is invoked. * * <p><strong>Note that this class is not synchronized.</strong> * If multiple threads access a {@code ProcessBuilder} instance * concurrently, and at least one of the threads modifies one of the * attributes structurally, it <i>must</i> be synchronized externally. * * <p>Starting a new process which uses the default working directory * and environment is easy: * * <pre> {@code * Process p = new ProcessBuilder("myCommand", "myArg").start(); * }</pre> * * <p>Here is an example that starts a process with a modified working * directory and environment, and redirects standard output and error * to be appended to a log file: * * <pre> {@code * ProcessBuilder pb = * new ProcessBuilder("myCommand", "myArg1", "myArg2"); * Map<String, String> env = pb.environment(); * env.put("VAR1", "myValue"); * env.remove("OTHERVAR"); * env.put("VAR2", env.get("VAR1") + "suffix"); * pb.directory(new File("myDir")); * File log = new File("log"); * pb.redirectErrorStream(true); * pb.redirectOutput(Redirect.appendTo(log)); * Process p = pb.start(); * assert pb.redirectInput() == Redirect.PIPE; * assert pb.redirectOutput().file() == log; * assert p.getInputStream().read() == -1; * }</pre> * * <p>To start a process with an explicit set of environment * variables, first call {@link java.util.Map#clear() Map.clear()} * before adding environment variables. * * <p> * Unless otherwise noted, passing a {@code null} argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * * @author Martin Buchholz * @since 1.5 */ public final class ProcessBuilder { private List<String> command; private File directory; private Map<String, String> environment; private boolean redirectErrorStream; private Redirect[] redirects; /** * Constructs a process builder with the specified operating * system program and arguments. This constructor does <i>not</i> * make a copy of the {@code command} list. Subsequent * updates to the list will be reflected in the state of the * process builder. It is not checked whether * {@code command} corresponds to a valid operating system * command. * * @param command the list containing the program and its arguments */ public ProcessBuilder(List<String> command) { if (command == null) throw new NullPointerException(); this.command = command; } /** * Constructs a process builder with the specified operating * system program and arguments. This is a convenience * constructor that sets the process builder's command to a string * list containing the same strings as the {@code command} * array, in the same order. It is not checked whether * {@code command} corresponds to a valid operating system * command. * * @param command a string array containing the program and its arguments */ public ProcessBuilder(String... command) { this.command = new ArrayList<>(command.length); for (String arg : command) this.command.add(arg); } /** * Sets this process builder's operating system program and * arguments. This method does <i>not</i> make a copy of the * {@code command} list. Subsequent updates to the list will * be reflected in the state of the process builder. It is not * checked whether {@code command} corresponds to a valid * operating system command. * * @param command the list containing the program and its arguments * @return this process builder */ public ProcessBuilder command(List<String> command) { if (command == null) throw new NullPointerException(); this.command = command; return this; } /** * Sets this process builder's operating system program and * arguments. This is a convenience method that sets the command * to a string list containing the same strings as the * {@code command} array, in the same order. It is not * checked whether {@code command} corresponds to a valid * operating system command. * * @param command a string array containing the program and its arguments * @return this process builder */ public ProcessBuilder command(String... command) { this.command = new ArrayList<>(command.length); for (String arg : command) this.command.add(arg); return this; } /** * Returns this process builder's operating system program and * arguments. The returned list is <i>not</i> a copy. Subsequent * updates to the list will be reflected in the state of this * process builder. * * @return this process builder's program and its arguments */ public List<String> command() { return command; } /** * Returns a string map view of this process builder's environment. * * Whenever a process builder is created, the environment is * initialized to a copy of the current process environment (see * {@link System#getenv()}). Subprocesses subsequently started by * this object's {@link #start()} method will use this map as * their environment. * * <p>The returned object may be modified using ordinary {@link * java.util.Map Map} operations. These modifications will be * visible to subprocesses started via the {@link #start()} * method. Two {@code ProcessBuilder} instances always * contain independent process environments, so changes to the * returned map will never be reflected in any other * {@code ProcessBuilder} instance or the values returned by * {@link System#getenv System.getenv}. * * <p>If the system does not support environment variables, an * empty map is returned. * * <p>The returned map does not permit null keys or values. * Attempting to insert or query the presence of a null key or * value will throw a {@link NullPointerException}. * Attempting to query the presence of a key or value which is not * of type {@link String} will throw a {@link ClassCastException}. * * <p>The behavior of the returned map is system-dependent. A * system may not allow modifications to environment variables or * may forbid certain variable names or values. For this reason, * attempts to modify the map may fail with * {@link UnsupportedOperationException} or * {@link IllegalArgumentException} * if the modification is not permitted by the operating system. * * <p>Since the external format of environment variable names and * values is system-dependent, there may not be a one-to-one * mapping between them and Java's Unicode strings. Nevertheless, * the map is implemented in such a way that environment variables * which are not modified by Java code will have an unmodified * native representation in the subprocess. * * <p>The returned map and its collection views may not obey the * general contract of the {@link Object#equals} and * {@link Object#hashCode} methods. * * <p>The returned map is typically case-sensitive on all platforms. * * <p>If a security manager exists, its * {@link SecurityManager#checkPermission checkPermission} method * is called with a * {@link RuntimePermission}{@code ("getenv.*")} permission. * This may result in a {@link SecurityException} being thrown. * * <p>When passing information to a Java subprocess, * <a href=System.html#EnvironmentVSSystemProperties>system properties</a> * are generally preferred over environment variables. * * @return this process builder's environment * * @throws SecurityException * if a security manager exists and its * {@link SecurityManager#checkPermission checkPermission} * method doesn't allow access to the process environment * * @see Runtime#exec(String[],String[],java.io.File) * @see System#getenv() */ public Map<String, String> environment() { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(new RuntimePermission("getenv.*")); if (environment == null) environment = ProcessEnvironment.environment(); assert environment != null; return environment; } // Only for use by Runtime.exec(...envp...) ProcessBuilder environment(String[] envp) { assert environment == null; if (envp != null) { environment = ProcessEnvironment.emptyEnvironment(envp.length); assert environment != null; for (String envstring : envp) { // Before 1.5, we blindly passed invalid envstrings // to the child process. // We would like to throw an exception, but do not, // for compatibility with old broken code. // Silently discard any trailing junk. if (envstring.indexOf((int) '\u0000') != -1) envstring = envstring.replaceFirst("\u0000.*", ""); int eqlsign = envstring.indexOf('=', ProcessEnvironment.MIN_NAME_LENGTH); // Silently ignore envstrings lacking the required `='. if (eqlsign != -1) environment.put(envstring.substring(0, eqlsign), envstring.substring(eqlsign + 1)); } } return this; } /** * Returns this process builder's working directory. * * Subprocesses subsequently started by this object's {@link * #start()} method will use this as their working directory. * The returned value may be {@code null} -- this means to use * the working directory of the current Java process, usually the * directory named by the system property {@code user.dir}, * as the working directory of the child process. * * @return this process builder's working directory */ public File directory() { return directory; } /** * Sets this process builder's working directory. * * Subprocesses subsequently started by this object's {@link * #start()} method will use this as their working directory. * The argument may be {@code null} -- this means to use the * working directory of the current Java process, usually the * directory named by the system property {@code user.dir}, * as the working directory of the child process. * * @param directory the new working directory * @return this process builder */ public ProcessBuilder directory(File directory) { this.directory = directory; return this; } // ---------------- I/O Redirection ---------------- /** * Implements a <a href="#redirect-output">null input stream</a>. */ static class NullInputStream extends InputStream { static final NullInputStream INSTANCE = new NullInputStream(); private NullInputStream() { } public int read() { return -1; } public int available() { return 0; } } /** * Implements a <a href="#redirect-input">null output stream</a>. */ static class NullOutputStream extends OutputStream { static final NullOutputStream INSTANCE = new NullOutputStream(); private NullOutputStream() { } public void write(int b) throws IOException { throw new IOException("Stream closed"); } } /** * Represents a source of subprocess input or a destination of * subprocess output. * * Each {@code Redirect} instance is one of the following: * * <ul> * <li>the special value {@link #PIPE Redirect.PIPE} * <li>the special value {@link #INHERIT Redirect.INHERIT} * <li>the special value {@link #DISCARD Redirect.DISCARD} * <li>a redirection to read from a file, created by an invocation of * {@link Redirect#from Redirect.from(File)} * <li>a redirection to write to a file, created by an invocation of * {@link Redirect#to Redirect.to(File)} * <li>a redirection to append to a file, created by an invocation of * {@link Redirect#appendTo Redirect.appendTo(File)} * </ul> * * <p>Each of the above categories has an associated unique * {@link Type Type}. * * @since 1.7 */ public abstract static class Redirect { private static final File NULL_FILE = new File( (GetPropertyAction.privilegedGetProperty("os.name").startsWith("Windows") ? "NUL" : "/dev/null")); /** * The type of a {@link Redirect}. */ public enum Type { /** * The type of {@link Redirect#PIPE Redirect.PIPE}. */ PIPE, /** * The type of {@link Redirect#INHERIT Redirect.INHERIT}. */ INHERIT, /** * The type of redirects returned from * {@link Redirect#from Redirect.from(File)}. */ READ, /** * The type of redirects returned from * {@link Redirect#to Redirect.to(File)}. */ WRITE, /** * The type of redirects returned from * {@link Redirect#appendTo Redirect.appendTo(File)}. */ APPEND }; /** * Returns the type of this {@code Redirect}. * @return the type of this {@code Redirect} */ public abstract Type type(); /** * Indicates that subprocess I/O will be connected to the * current Java process over a pipe. * * This is the default handling of subprocess standard I/O. * * <p>It will always be true that * <pre> {@code * Redirect.PIPE.file() == null && * Redirect.PIPE.type() == Redirect.Type.PIPE * }</pre> */ public static final Redirect PIPE = new Redirect() { public Type type() { return Type.PIPE; } public String toString() { return type().toString(); } }; /** * Indicates that subprocess I/O source or destination will be the * same as those of the current process. This is the normal * behavior of most operating system command interpreters (shells). * * <p>It will always be true that * <pre> {@code * Redirect.INHERIT.file() == null && * Redirect.INHERIT.type() == Redirect.Type.INHERIT * }</pre> */ public static final Redirect INHERIT = new Redirect() { public Type type() { return Type.INHERIT; } public String toString() { return type().toString(); } }; /** * Indicates that subprocess output will be discarded. * A typical implementation discards the output by writing to * an operating system specific "null file". * * <p>It will always be true that * <pre> {@code * Redirect.DISCARD.file() is the filename appropriate for the operating system * and may be null && * Redirect.DISCARD.type() == Redirect.Type.WRITE * }</pre> * @since 9 */ public static final Redirect DISCARD = new Redirect() { public Type type() { return Type.WRITE; } public String toString() { return type().toString(); } public File file() { return NULL_FILE; } boolean append() { return false; } }; /** * Returns the {@link File} source or destination associated * with this redirect, or {@code null} if there is no such file. * * @return the file associated with this redirect, * or {@code null} if there is no such file */ public File file() { return null; } /** * When redirected to a destination file, indicates if the output * is to be written to the end of the file. */ boolean append() { throw new UnsupportedOperationException(); } /** * Returns a redirect to read from the specified file. * * <p>It will always be true that * <pre> {@code * Redirect.from(file).file() == file && * Redirect.from(file).type() == Redirect.Type.READ * }</pre> * * @param file The {@code File} for the {@code Redirect}. * @return a redirect to read from the specified file */ public static Redirect from(final File file) { if (file == null) throw new NullPointerException(); return new Redirect() { public Type type() { return Type.READ; } public File file() { return file; } public String toString() { return "redirect to read from file \"" + file + "\""; } }; } /** * Returns a redirect to write to the specified file. * If the specified file exists when the subprocess is started, * its previous contents will be discarded. * * <p>It will always be true that * <pre> {@code * Redirect.to(file).file() == file && * Redirect.to(file).type() == Redirect.Type.WRITE * }</pre> * * @param file The {@code File} for the {@code Redirect}. * @return a redirect to write to the specified file */ public static Redirect to(final File file) { if (file == null) throw new NullPointerException(); return new Redirect() { public Type type() { return Type.WRITE; } public File file() { return file; } public String toString() { return "redirect to write to file \"" + file + "\""; } boolean append() { return false; } }; } /** * Returns a redirect to append to the specified file. * Each write operation first advances the position to the * end of the file and then writes the requested data. * Whether the advancement of the position and the writing * of the data are done in a single atomic operation is * system-dependent and therefore unspecified. * * <p>It will always be true that * <pre> {@code * Redirect.appendTo(file).file() == file && * Redirect.appendTo(file).type() == Redirect.Type.APPEND * }</pre> * * @param file The {@code File} for the {@code Redirect}. * @return a redirect to append to the specified file */ public static Redirect appendTo(final File file) { if (file == null) throw new NullPointerException(); return new Redirect() { public Type type() { return Type.APPEND; } public File file() { return file; } public String toString() { return "redirect to append to file \"" + file + "\""; } boolean append() { return true; } }; } /** * Compares the specified object with this {@code Redirect} for * equality. Returns {@code true} if and only if the two * objects are identical or both objects are {@code Redirect} * instances of the same type associated with non-null equal * {@code File} instances. */ public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof Redirect)) return false; Redirect r = (Redirect) obj; if (r.type() != this.type()) return false; assert this.file() != null; return this.file().equals(r.file()); } /** * Returns a hash code value for this {@code Redirect}. * @return a hash code value for this {@code Redirect} */ public int hashCode() { File file = file(); if (file == null) return super.hashCode(); else return file.hashCode(); } /** * No public constructors. Clients must use predefined * static {@code Redirect} instances or factory methods. */ private Redirect() { } } /** * Private implementation subclass of Redirect that holds a FileDescriptor for the * output of a previously started Process. * The FileDescriptor is used as the standard input of the next Process * to be started. */ static class RedirectPipeImpl extends Redirect { final FileDescriptor fd; RedirectPipeImpl() { this.fd = new FileDescriptor(); } @Override public Type type() { return Type.PIPE; } @Override public String toString() { return type().toString(); } FileDescriptor getFd() { return fd; } } /** * Return the array of redirects, creating the default as needed. * @return the array of redirects */ private Redirect[] redirects() { if (redirects == null) { redirects = new Redirect[] { Redirect.PIPE, Redirect.PIPE, Redirect.PIPE }; } return redirects; } /** * Sets this process builder's standard input source. * * Subprocesses subsequently started by this object's {@link #start()} * method obtain their standard input from this source. * * <p>If the source is {@link Redirect#PIPE Redirect.PIPE} * (the initial value), then the standard input of a * subprocess can be written to using the output stream * returned by {@link Process#getOutputStream()}. * If the source is set to any other value, then * {@link Process#getOutputStream()} will return a * <a href="#redirect-input">null output stream</a>. * * @param source the new standard input source * @return this process builder * @throws IllegalArgumentException * if the redirect does not correspond to a valid source * of data, that is, has type * {@link Redirect.Type#WRITE WRITE} or * {@link Redirect.Type#APPEND APPEND} * @since 1.7 */ public ProcessBuilder redirectInput(Redirect source) { if (source.type() == Redirect.Type.WRITE || source.type() == Redirect.Type.APPEND) throw new IllegalArgumentException("Redirect invalid for reading: " + source); redirects()[0] = source; return this; } /** * Sets this process builder's standard output destination. * * Subprocesses subsequently started by this object's {@link #start()} * method send their standard output to this destination. * * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE} * (the initial value), then the standard output of a subprocess * can be read using the input stream returned by {@link * Process#getInputStream()}. * If the destination is set to any other value, then * {@link Process#getInputStream()} will return a * <a href="#redirect-output">null input stream</a>. * * @param destination the new standard output destination * @return this process builder * @throws IllegalArgumentException * if the redirect does not correspond to a valid * destination of data, that is, has type * {@link Redirect.Type#READ READ} * @since 1.7 */ public ProcessBuilder redirectOutput(Redirect destination) { if (destination.type() == Redirect.Type.READ) throw new IllegalArgumentException("Redirect invalid for writing: " + destination); redirects()[1] = destination; return this; } /** * Sets this process builder's standard error destination. * * Subprocesses subsequently started by this object's {@link #start()} * method send their standard error to this destination. * * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE} * (the initial value), then the error output of a subprocess * can be read using the input stream returned by {@link * Process#getErrorStream()}. * If the destination is set to any other value, then * {@link Process#getErrorStream()} will return a * <a href="#redirect-output">null input stream</a>. * * <p>If the {@link #redirectErrorStream() redirectErrorStream} * attribute has been set {@code true}, then the redirection set * by this method has no effect. * * @param destination the new standard error destination * @return this process builder * @throws IllegalArgumentException * if the redirect does not correspond to a valid * destination of data, that is, has type * {@link Redirect.Type#READ READ} * @since 1.7 */ public ProcessBuilder redirectError(Redirect destination) { if (destination.type() == Redirect.Type.READ) throw new IllegalArgumentException("Redirect invalid for writing: " + destination); redirects()[2] = destination; return this; } /** * Sets this process builder's standard input source to a file. * * <p>This is a convenience method. An invocation of the form * {@code redirectInput(file)} * behaves in exactly the same way as the invocation * {@link #redirectInput(Redirect) redirectInput} * {@code (Redirect.from(file))}. * * @param file the new standard input source * @return this process builder * @since 1.7 */ public ProcessBuilder redirectInput(File file) { return redirectInput(Redirect.from(file)); } /** * Sets this process builder's standard output destination to a file. * * <p>This is a convenience method. An invocation of the form * {@code redirectOutput(file)} * behaves in exactly the same way as the invocation * {@link #redirectOutput(Redirect) redirectOutput} * {@code (Redirect.to(file))}. * * @param file the new standard output destination * @return this process builder * @since 1.7 */ public ProcessBuilder redirectOutput(File file) { return redirectOutput(Redirect.to(file)); } /** * Sets this process builder's standard error destination to a file. * * <p>This is a convenience method. An invocation of the form * {@code redirectError(file)} * behaves in exactly the same way as the invocation * {@link #redirectError(Redirect) redirectError} * {@code (Redirect.to(file))}. * * @param file the new standard error destination * @return this process builder * @since 1.7 */ public ProcessBuilder redirectError(File file) { return redirectError(Redirect.to(file)); } /** * Returns this process builder's standard input source. * * Subprocesses subsequently started by this object's {@link #start()} * method obtain their standard input from this source. * The initial value is {@link Redirect#PIPE Redirect.PIPE}. * * @return this process builder's standard input source * @since 1.7 */ public Redirect redirectInput() { return (redirects == null) ? Redirect.PIPE : redirects[0]; } /** * Returns this process builder's standard output destination. * * Subprocesses subsequently started by this object's {@link #start()} * method redirect their standard output to this destination. * The initial value is {@link Redirect#PIPE Redirect.PIPE}. * * @return this process builder's standard output destination * @since 1.7 */ public Redirect redirectOutput() { return (redirects == null) ? Redirect.PIPE : redirects[1]; } /** * Returns this process builder's standard error destination. * * Subprocesses subsequently started by this object's {@link #start()} * method redirect their standard error to this destination. * The initial value is {@link Redirect#PIPE Redirect.PIPE}. * * @return this process builder's standard error destination * @since 1.7 */ public Redirect redirectError() { return (redirects == null) ? Redirect.PIPE : redirects[2]; } /** * Sets the source and destination for subprocess standard I/O * to be the same as those of the current Java process. * * <p>This is a convenience method. An invocation of the form * <pre> {@code * pb.inheritIO() * }</pre> * behaves in exactly the same way as the invocation * <pre> {@code * pb.redirectInput(Redirect.INHERIT) * .redirectOutput(Redirect.INHERIT) * .redirectError(Redirect.INHERIT) * }</pre> * * This gives behavior equivalent to most operating system * command interpreters, or the standard C library function * {@code system()}. * * @return this process builder * @since 1.7 */ public ProcessBuilder inheritIO() { Arrays.fill(redirects(), Redirect.INHERIT); return this; } /** * Tells whether this process builder merges standard error and * standard output. * * <p>If this property is {@code true}, then any error output * generated by subprocesses subsequently started by this object's * {@link #start()} method will be merged with the standard * output, so that both can be read using the * {@link Process#getInputStream()} method. This makes it easier * to correlate error messages with the corresponding output. * The initial value is {@code false}. * * @return this process builder's {@code redirectErrorStream} property */ public boolean redirectErrorStream() { return redirectErrorStream; } /** * Sets this process builder's {@code redirectErrorStream} property. * * <p>If this property is {@code true}, then any error output * generated by subprocesses subsequently started by this object's * {@link #start()} method will be merged with the standard * output, so that both can be read using the * {@link Process#getInputStream()} method. This makes it easier * to correlate error messages with the corresponding output. * The initial value is {@code false}. * * @param redirectErrorStream the new property value * @return this process builder */ public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) { this.redirectErrorStream = redirectErrorStream; return this; } /** * Starts a new process using the attributes of this process builder. * * <p>The new process will * invoke the command and arguments given by {@link #command()}, * in a working directory as given by {@link #directory()}, * with a process environment as given by {@link #environment()}. * * <p>This method checks that the command is a valid operating * system command. Which commands are valid is system-dependent, * but at the very least the command must be a non-empty list of * non-null strings. * * <p>A minimal set of system dependent environment variables may * be required to start a process on some operating systems. * As a result, the subprocess may inherit additional environment variable * settings beyond those in the process builder's {@link #environment()}. * * <p>If there is a security manager, its * {@link SecurityManager#checkExec checkExec} * method is called with the first component of this object's * {@code command} array as its argument. This may result in * a {@link SecurityException} being thrown. * * <p>Starting an operating system process is highly system-dependent. * Among the many things that can go wrong are: * <ul> * <li>The operating system program file was not found. * <li>Access to the program file was denied. * <li>The working directory does not exist. * <li>Invalid character in command argument, such as NUL. * </ul> * * <p>In such cases an exception will be thrown. The exact nature * of the exception is system-dependent, but it will always be a * subclass of {@link IOException}. * * <p>If the operating system does not support the creation of * processes, an {@link UnsupportedOperationException} will be thrown. * * <p>Subsequent modifications to this process builder will not * affect the returned {@link Process}. * * @return a new {@link Process} object for managing the subprocess * * @throws NullPointerException * if an element of the command list is null * * @throws IndexOutOfBoundsException * if the command is an empty list (has size {@code 0}) * * @throws SecurityException * if a security manager exists and * <ul> * * <li>its * {@link SecurityManager#checkExec checkExec} * method doesn't allow creation of the subprocess, or * * <li>the standard input to the subprocess was * {@linkplain #redirectInput redirected from a file} * and the security manager's * {@link SecurityManager#checkRead(String) checkRead} method * denies read access to the file, or * * <li>the standard output or standard error of the * subprocess was * {@linkplain #redirectOutput redirected to a file} * and the security manager's * {@link SecurityManager#checkWrite(String) checkWrite} method * denies write access to the file * * </ul> * * @throws UnsupportedOperationException * If the operating system does not support the creation of processes. * * @throws IOException if an I/O error occurs * * @see Runtime#exec(String[], String[], java.io.File) */ public Process start() throws IOException { return start(redirects); } /** * Start a new Process using an explicit array of redirects. * See {@link #start} for details of starting each Process. * * @param redirect array of redirects for stdin, stdout, stderr * @return the new Process * @throws IOException if an I/O error occurs */ private Process start(Redirect[] redirects) throws IOException { // Must convert to array first -- a malicious user-supplied // list might try to circumvent the security check. String[] cmdarray = command.toArray(new String[command.size()]); cmdarray = cmdarray.clone(); for (String arg : cmdarray) if (arg == null) throw new NullPointerException(); // Throws IndexOutOfBoundsException if command is empty String prog = cmdarray[0]; SecurityManager security = System.getSecurityManager(); if (security != null) security.checkExec(prog); String dir = directory == null ? null : directory.toString(); for (int i = 1; i < cmdarray.length; i++) { if (cmdarray[i].indexOf('\u0000') >= 0) { throw new IOException("invalid null character in command"); } } try { return ProcessImpl.start(cmdarray, environment, dir, redirects, redirectErrorStream); } catch (IOException | IllegalArgumentException e) { String exceptionInfo = ": " + e.getMessage(); Throwable cause = e; if ((e instanceof IOException) && security != null) { // Can not disclose the fail reason for read-protected files. try { security.checkRead(prog); } catch (SecurityException se) { exceptionInfo = ""; cause = se; } } // It's much easier for us to create a high-quality error // message than the low-level C code which found the problem. throw new IOException("Cannot run program \"" + prog + "\"" + (dir == null ? "" : " (in directory \"" + dir + "\")") + exceptionInfo, cause); } } /** * Starts a Process for each ProcessBuilder, creating a pipeline of * processes linked by their standard output and standard input streams. * The attributes of each ProcessBuilder are used to start the respective * process except that as each process is started, its standard output * is directed to the standard input of the next. The redirects for standard * input of the first process and standard output of the last process are * initialized using the redirect settings of the respective ProcessBuilder. * All other {@code ProcessBuilder} redirects should be * {@link Redirect#PIPE Redirect.PIPE}. * <p> * All input and output streams between the intermediate processes are * not accessible. * The {@link Process#getOutputStream standard input} of all processes * except the first process are <i>null output streams</i> * The {@link Process#getInputStream standard output} of all processes * except the last process are <i>null input streams</i>. * <p> * The {@link #redirectErrorStream()} of each ProcessBuilder applies to the * respective process. If set to {@code true}, the error stream is written * to the same stream as standard output. * <p> * If starting any of the processes throws an Exception, all processes * are forcibly destroyed. * <p> * The {@code startPipeline} method performs the same checks on * each ProcessBuilder as does the {@link #start} method. Each new process * invokes the command and arguments given by the respective process builder's * {@link #command()}, in a working directory as given by its {@link #directory()}, * with a process environment as given by its {@link #environment()}. * <p> * Each process builder's command is checked to be a valid operating * system command. Which commands are valid is system-dependent, * but at the very least the command must be a non-empty list of * non-null strings. * <p> * A minimal set of system dependent environment variables may * be required to start a process on some operating systems. * As a result, the subprocess may inherit additional environment variable * settings beyond those in the process builder's {@link #environment()}. * <p> * If there is a security manager, its * {@link SecurityManager#checkExec checkExec} * method is called with the first component of each process builder's * {@code command} array as its argument. This may result in * a {@link SecurityException} being thrown. * <p> * Starting an operating system process is highly system-dependent. * Among the many things that can go wrong are: * <ul> * <li>The operating system program file was not found. * <li>Access to the program file was denied. * <li>The working directory does not exist. * <li>Invalid character in command argument, such as NUL. * </ul> * <p> * In such cases an exception will be thrown. The exact nature * of the exception is system-dependent, but it will always be a * subclass of {@link IOException}. * <p> * If the operating system does not support the creation of * processes, an {@link UnsupportedOperationException} will be thrown. * <p> * Subsequent modifications to any of the specified builders * will not affect the returned {@link Process}. * @apiNote * For example to count the unique imports for all the files in a file hierarchy * on a Unix compatible platform: * <pre>{@code * String directory = "/home/duke/src"; * ProcessBuilder[] builders = { * new ProcessBuilder("find", directory, "-type", "f"), new ProcessBuilder("xargs", "grep", "-h", "^import "), new ProcessBuilder("awk", "{print $2;}"), new ProcessBuilder("sort", "-u")}; * List<Process> processes = ProcessBuilder.startPipeline( * Arrays.asList(builders)); * Process last = processes.get(processes.size()-1); * try (InputStream is = last.getInputStream(); * Reader isr = new InputStreamReader(is); * BufferedReader r = new BufferedReader(isr)) { * long count = r.lines().count(); * } * }</pre> * * @param builders a List of ProcessBuilders * @return a {@code List<Process>}es started from the corresponding * ProcessBuilder * @throws IllegalArgumentException any of the redirects except the * standard input of the first builder and the standard output of * the last builder are not {@link Redirect#PIPE}. * @throws NullPointerException * if an element of the command list is null or * if an element of the ProcessBuilder list is null or * the builders argument is null * @throws IndexOutOfBoundsException * if the command is an empty list (has size {@code 0}) * @throws SecurityException * if a security manager exists and * <ul> * <li>its * {@link SecurityManager#checkExec checkExec} * method doesn't allow creation of the subprocess, or * <li>the standard input to the subprocess was * {@linkplain #redirectInput redirected from a file} * and the security manager's * {@link SecurityManager#checkRead(String) checkRead} method * denies read access to the file, or * <li>the standard output or standard error of the * subprocess was * {@linkplain #redirectOutput redirected to a file} * and the security manager's * {@link SecurityManager#checkWrite(String) checkWrite} method * denies write access to the file * </ul> * * @throws UnsupportedOperationException * If the operating system does not support the creation of processes * * @throws IOException if an I/O error occurs * @since 9 */ public static List<Process> startPipeline(List<ProcessBuilder> builders) throws IOException { // Accumulate and check the builders final int numBuilders = builders.size(); List<Process> processes = new ArrayList<>(numBuilders); try { Redirect prevOutput = null; for (int index = 0; index < builders.size(); index++) { ProcessBuilder builder = builders.get(index); Redirect[] redirects = builder.redirects(); if (index > 0) { // check the current Builder to see if it can take input from the previous if (builder.redirectInput() != Redirect.PIPE) { throw new IllegalArgumentException("builder redirectInput()" + " must be PIPE except for the first builder: " + builder.redirectInput()); } redirects[0] = prevOutput; } if (index < numBuilders - 1) { // check all but the last stage has output = PIPE if (builder.redirectOutput() != Redirect.PIPE) { throw new IllegalArgumentException("builder redirectOutput()" + " must be PIPE except for the last builder: " + builder.redirectOutput()); } redirects[1] = new RedirectPipeImpl(); // placeholder for new output } processes.add(builder.start(redirects)); prevOutput = redirects[1]; } } catch (Exception ex) { // Cleanup processes already started processes.forEach(Process::destroyForcibly); processes.forEach(p -> { try { p.waitFor(); // Wait for it to exit } catch (InterruptedException ie) { // If interrupted; continue with next Process Thread.currentThread().interrupt(); } }); throw ex; } return processes; } }