5 * Functions for executing system commands. (e.g. exec(), system(), ...).
9 use Drush\Log\LogLevel;
10 use \Consolidation\SiteAlias\AliasRecord;
13 * @defgroup commandwrappers Functions to execute commands.
18 * Calls 'system()' function, passing through all arguments unchanged.
20 * This should be used when calling possibly mutative or destructive functions
21 * (e.g. unlink() and other file system functions) so that can be suppressed
22 * if the simulation mode is enabled.
25 * The shell command to execute. Parameters should already be escaped.
27 * The result code from system(): 0 == success.
29 * @see drush_shell_exec()
31 function drush_op_system($exec) {
32 if (Drush::verbose() || Drush::simulate()) {
33 drush_print("Calling system($exec);", 0, STDERR);
35 if (Drush::simulate()) {
39 // Throw away output. Use drush_shell_exec() to capture output.
40 system($exec, $result_code);
46 * Executes a shell command at a new working directory.
47 * The old cwd is restored on exit.
49 * @param $effective_wd
50 * The new working directory to execute the shell command at.
52 * The command to execute. May include placeholders used for sprintf.
54 * Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line.
56 * TRUE on success, FALSE on failure
58 function drush_shell_cd_and_exec($effective_wd, $cmd) {
59 $args = func_get_args();
61 $effective_wd = array_shift($args);
63 drush_op('chdir', $effective_wd);
64 $result = call_user_func_array('drush_shell_exec', $args);
65 drush_op('chdir', $cwd);
70 * Executes a shell command.
71 * Output is only printed if in verbose mode.
72 * Output is stored and can be retrieved using drush_shell_exec_output().
73 * If in simulation mode, no action is taken.
76 * The command to execute. May include placeholders used for sprintf.
78 * Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line.
80 * TRUE on success, FALSE on failure
82 function drush_shell_exec($cmd) {
83 return _drush_shell_exec(func_get_args(), FALSE, Drush::simulate());
87 * A version of drush_shell_exec() that ignores simulate mode
89 function drush_always_exec($cmd) {
90 return _drush_shell_exec(func_get_args(), FALSE, FALSE);
94 * Returns executable code for invoking preferred test editor.
96 * The next line after calling this function is usually
97 * @code drush_shell_exec_interactive($exec, $filepath, $filepath) @endcode
99 * @see drush_config_edit()
101 function drush_get_editor() {
102 $bg = drush_get_option('bg') ? '&' : '';
103 // see http://drupal.org/node/1740294
104 $exec = drush_get_option('editor', '${VISUAL-${EDITOR-vi}}') . " %s $bg";
109 * Executes a command in interactive mode.
111 * @see drush_shell_exec.
113 function drush_shell_exec_interactive($cmd) {
114 return _drush_shell_exec(func_get_args(), TRUE, Drush::simulate());
118 * Internal function: executes a shell command on the
119 * local machine. This function should not be used
120 * in instances where ssh is utilized to execute a
121 * command remotely; otherwise, remote operations would
122 * fail if executed from a Windows machine to a remote
126 * The command and its arguments.
127 * @param $interactive
131 * TRUE on success, FALSE on failure
133 * @see drush_shell_exec.
135 function _drush_shell_exec($args, $interactive = FALSE, $simulate = false) {
136 // Do not change the command itself, just the parameters.
137 for ($x = 1; $x < count($args); $x++) {
138 $args[$x] = drush_escapeshellarg($args[$x]);
140 // Important: we allow $args to take one of two forms here. If
141 // there is only one item in the array, it is the already-escaped
142 // command string, but otherwise sprintf is used. In the case
143 // of pre-escaped strings, sprintf will fail if any of the escaped
144 // parameters contain '%', so we must not call sprintf unless necessary.
145 if (count($args) == 1) {
149 $command = call_user_func_array('sprintf', $args);
152 drush_log('Executing: ' . $command, LogLevel::INFO);
155 $result = drush_shell_proc_open($command);
156 return ($result == 0) ? TRUE : FALSE;
159 exec($command . ' 2>&1', $output, $result);
160 _drush_shell_exec_output_set($output);
162 if (Drush::debug()) {
163 foreach ($output as $line) {
164 drush_print($line, 2);
168 // Exit code 0 means success.
169 return ($result == 0);
178 * Determine whether 'which $command' can find
179 * a command on this system.
181 function drush_which($command) {
182 exec("which $command 2>&1", $output, $result);
183 return ($result == 0);
187 * Build an SSH string including an optional fragment of bash. Commands that use
188 * this should also merge drush_shell_proc_build_options() into their
189 * command options. @see ssh_drush_command().
192 * A site alias record.
193 * @param string $command
194 * An optional bash fragment.
196 * An optional directory to change into before executing the $command. Set to
197 * boolean TRUE to change into $site['root'] if available.
198 * @param boolean $interactive
199 * Force creation of a tty
201 * A string suitable for execution with drush_shell_remote_exec().
204 function drush_shell_proc_build(AliasRecord $site, $command = '', $cd = NULL, $interactive = FALSE) {
205 // Build up the command. TODO: We maybe refactor this soon.
206 $hostname = $site->remoteHostWithUser();
207 $ssh_options = $site->getConfig(Drush::config(), 'ssh.options', "-o PasswordAuthentication=no");
208 $os = drush_os($site);
209 if ($site->get('tty') || $interactive) {
210 $ssh_options .= ' -t';
213 $cmd = "ssh " . $ssh_options . " " . $hostname;
216 if ($site->hasRoot()) {
224 $command = 'cd ' . drush_escapeshellarg($cd, $os) . ' && ' . $command;
227 if (!empty($command)) {
228 $cmd .= " " . drush_escapeshellarg($command, $os);
235 * Execute bash command using proc_open().
238 * Exit code from launched application
241 * 127 command not found
243 function drush_shell_proc_open($cmd) {
244 if (Drush::verbose() || Drush::simulate()) {
245 drush_print("Calling proc_open($cmd);", 0, STDERR);
247 if (!Drush::simulate()) {
248 $process = proc_open($cmd, [0 => STDIN, 1 => STDOUT, 2 => STDERR], $pipes);
249 $proc_status = proc_get_status($process);
250 $exit_code = proc_close($process);
251 return ($proc_status["running"] ? $exit_code : $proc_status["exitcode"] );
257 * Determine the appropriate os value for the
258 * specified site record
261 * NULL for 'same as local machine', 'Windows' or 'Linux'.
263 function drush_os($site_record = NULL) {
264 if (!$site_record instanceof AliasRecord) {
265 return legacy_drush_os($site_record);
267 // n.b. $options['remote-os'] has become 'ssh.os' in drush.yml
268 return $site_record->getConfig(Drush::config(), 'ssh.os', 'Linux');
271 function legacy_drush_os($site_record = NULL) {
272 // Default to $os = NULL, meaning 'same as local machine'
274 // If the site record has an 'os' element, use it
275 if (isset($site_record) && array_key_exists('os', $site_record)) {
276 $os = $site_record['os'];
278 // Otherwise, we will assume that all remote machines are Linux
279 // (or whatever value 'remote-os' is set to in drush.yml).
280 elseif (isset($site_record) && array_key_exists('remote-host', $site_record) && !empty($site_record['remote-host'])) {
281 $os = Drush::config()->get('ssh.os', 'Linux');
288 * Make an attempt to simply wrap the arg with the
289 * kind of quote characters it does not already contain.
290 * If it contains both kinds, then this function reverts to drush_escapeshellarg.
292 function drush_wrap_with_quotes($arg) {
293 $has_double = strpos($arg, '"') !== FALSE;
294 $has_single = strpos($arg, "'") !== FALSE;
295 if ($has_double && $has_single) {
296 return drush_escapeshellarg($arg);
298 elseif ($has_double) {
299 return "'" . $arg . "'";
302 return '"' . $arg . '"';
307 * Platform-dependent version of escapeshellarg().
308 * Given the target platform, return an appropriately-escaped
309 * string. The target platform may be omitted for args that
310 * are /known/ to be for the local machine.
311 * Use raw to get an unquoted version of the escaped arg.
312 * Notice that you can't add quotes later until you know the platform.
316 * Stores output for the most recent shell command.
317 * This should only be run from drush_shell_exec().
319 * @param array|bool $output
320 * The output of the most recent shell command.
321 * If this is not set the stored value will be returned.
323 function _drush_shell_exec_output_set($output = FALSE) {
324 static $stored_output;
325 if ($output === FALSE) return $stored_output;
326 $stored_output = $output;
330 * Returns the output of the most recent shell command as an array of lines.
332 function drush_shell_exec_output() {
333 return _drush_shell_exec_output_set();
337 * Starts a background browser/tab for the current site or a specified URL.
339 * Uses a non-blocking proc_open call, so Drush execution will continue.
342 * Optional URI or site path to open in browser. If omitted, or if a site path
343 * is specified, the current site home page uri will be prepended if the sites
346 * TRUE if browser was opened, FALSE if browser was disabled by the user or a,
347 * default browser could not be found.
349 function drush_start_browser($uri = NULL, $sleep = FALSE, $port = FALSE, $browser = true) {
351 // We can only open a browser if we have a DISPLAY environment variable on
352 // POSIX or are running Windows or OS X.
353 if (!Drush::simulate() && !getenv('DISPLAY') && !drush_is_windows() && !drush_is_osx()) {
354 drush_log(dt('No graphical display appears to be available, not starting browser.'), LogLevel::INFO);
357 $host = parse_url($uri, PHP_URL_HOST);
359 // Build a URI for the current site, if we were passed a path.
360 $site = drush_get_context('DRUSH_URI');
361 $host = parse_url($site, PHP_URL_HOST);
362 $uri = $site . '/' . ltrim($uri, '/');
364 // Validate that the host part of the URL resolves, so we don't attempt to
365 // open the browser for http://default or similar invalid hosts.
366 $hosterror = (gethostbynamel($host) === FALSE);
367 $iperror = (ip2long($host) && gethostbyaddr($host) == $host);
368 if (!Drush::simulate() && ($hosterror || $iperror)) {
369 drush_log(dt('!host does not appear to be a resolvable hostname or IP, not starting browser. You may need to use the --uri option in your command or site alias to indicate the correct URL of this site.', ['!host' => $host]), LogLevel::WARNING);
373 $uri = str_replace($host, "localhost:$port", $uri);
375 if ($browser === TRUE) {
376 // See if we can find an OS helper to open URLs in default browser.
377 if (drush_shell_exec('which xdg-open')) {
378 $browser = 'xdg-open';
380 else if (drush_shell_exec('which open')) {
383 else if (!drush_has_bash()) {
387 // Can't find a valid browser.
393 $prefix = 'sleep ' . $sleep . ' && ';
396 drush_log(dt('Opening browser !browser at !uri', ['!browser' => $browser, '!uri' => $uri]));
397 if (!Drush::simulate()) {
399 proc_close(proc_open($prefix . $browser . ' ' . drush_escapeshellarg($uri) . ' 2> ' . drush_bit_bucket() . ' &', [], $pipes));
408 * @} End of "defgroup commandwrappers".