diff --git a/drush.complete.sh b/drush.complete.sh
new file mode 100644
index 0000000..94bf722
--- /dev/null
+++ b/drush.complete.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# BASH completion script for Drush.
+#
+# Place this in your /etc/bash_completion.d/ directory or source it from your
+# ~/.bash_completion file.
+
+# Completion function, uses the "drush complete" command to retrieve completions
+# for a specific command line COMP_WORDS.
+_drush_completion() {
+  # The '< /dev/null' is a work around for a bug in php libedit stdin handling.
+  # Note that libedit in place of libreadline in some distributions. See:
+  # https://bugs.launchpad.net/ubuntu/+source/php5/+bug/322214
+  COMPREPLY=( $(drush complete "${COMP_WORDS[@]}" < /dev/null) )
+}
+
+# Register our completion function. We include common short aliases for Drush.
+complete -F _drush_completion d dr drush drush.php
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 07abe16..9c5c814 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -918,8 +918,17 @@ function drush_bootstrap_prepare() {
 
   drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE);
 
-  // We need some global options processed at this early stage. Namely --debug.
-  drush_parse_args();
+  // We need some global options/arguments processed at this early stage.
+  $arguments = drush_parse_args();
+
+  // Because the complete argument is added by an autocomplete shell script,
+  // it should always be first.
+  if ($arguments[0] == 'complete') {
+    require_once DRUSH_BASE_PATH . '/includes/complete.inc';
+    drush_complete();
+  }
+  
+  // Process early global options such as --debug. 
   _drush_bootstrap_global_options();
 }
 
diff --git a/includes/cache.inc b/includes/cache.inc
index 794978d..de17f71 100644
--- a/includes/cache.inc
+++ b/includes/cache.inc
@@ -180,7 +180,8 @@ function drush_get_cid($prefix, $contexts = array(), $params = array()) {
   $cid = array();
 
   foreach ($contexts as $context) {
-    if ($c = drush_get_context($context) && !empty($c)) {
+    $c = drush_get_context($context);
+    if (!empty($c)) {
       $cid[] = is_scalar($c) ? $c : serialize($c);
     }
   }
diff --git a/includes/command.inc b/includes/command.inc
index 98381be..470c2fc 100644
--- a/includes/command.inc
+++ b/includes/command.inc
@@ -342,9 +342,8 @@ function drush_parse_args() {
         $opt = substr($opt, 1);
         // Check if the current opt is in $arg_opts (= has to be followed by an argument).
         if ((in_array($opt, $arg_opts))) {
-          if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
-            drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument.");
-          }
+          // Raising errors for missing option values should be handled by the
+          // bootstrap or specific command, so we no longer do this here.
           $options[$opt] = $args[$i + 1];
           $i++;
         }
@@ -374,6 +373,7 @@ function drush_parse_args() {
 
   drush_set_arguments($arguments);
   drush_set_context('cli', $options);
+  return $arguments;
 }
 
 /**
diff --git a/includes/complete.inc b/includes/complete.inc
new file mode 100644
index 0000000..96cff74
--- /dev/null
+++ b/includes/complete.inc
@@ -0,0 +1,368 @@
+<?php
+
+/**
+ * @file
+ * 
+ * Provide completion output for shells.
+ * 
+ * This is not called directly, but by shell completion scripts specific to
+ * each shell (bash, csh etc). These run whenever the user triggers completion,
+ * typically when pressing <tab>. The shell completion scripts should call
+ * "drush complete <text>", where <text> is the full command line, which we take
+ * as input and use to produce a list of possible completions for the
+ * current/next word, separated by newlines. Typically, when multiple
+ * completions are returned the shell will display them to the user in a concise
+ * format - but when a single completion is returned it will autocomplete. 
+ * 
+ * We provide completion for site aliases, commands, shell aliases, options,
+ * engines and arguments. Displaying all of these when the last word has no
+ * characters yet is not useful, as there are too many items. Instead we filter
+ * the possible completions based on context, in a similar way to git.
+ * For example:
+ * - We only display site aliases and commands if one is not already present.
+ * - We only display options if the user has already entered a hyphen.
+ * - We only display global options before a command is entered, and we only
+ *   display command specific options after the command (Drush itself does not
+ *   care about option placement, but this approach keeps things more concise).
+ * 
+ * Below is typical output of complete in different situations. Tokens in square
+ * brackets are optional, and [word] will filter available options that start
+ * with the same characters, or display all listed options if empty. 
+ * drush --[word] : Output global options
+ * drush [word] : Output aliases, local sites and commands
+ * drush [@alias] [word] : Output commands
+ * drush [@alias] command [word] : Output command specific arguments
+ * drush [@alias] command --[word] : Output command specific options
+ * 
+ * Because the purpose of autocompletion is to make command line work efficient,
+ * it is very important that the list of completions is returned quickly.
+ * To do this, we call drush_complete() early in the Drush bootstrap, and
+ * implement a simple caching system. 
+ * 
+ * To generate the list of completions, we set up the Drush environment as if
+ * the command was called on it's own, parse the command using the standard
+ * Drush functions, bootstrap the site (if any) and collect available
+ * completions from various sources. Because this can be somewhat slow, we cache
+ * the results. The cache strategy aims to balance accuracy and responsiveness:
+ * - We cache global and command specific options globally, since these are
+ *   almost always identical between sites - the cache can be used even if drush
+ *   complete has never run on a site before and does not require any bootstrap.
+ * - We cache command names and aliases per site, since they can vary per-site.
+ * - We generate (and cache) everything except arguments at the same time, so
+ *   subsequent completions on the site don't need any bootstrap.
+ * - We generate and cache arguments on-demand, since these can often be
+ *   expensive to generate. Arguments are also cached per-site.
+ */
+
+/**
+ * Produce autocomplete output.
+ * 
+ * Determine context (is there a site-alias or command set, and are we trying
+ * to complete an option). Then produce a list of completions for the last word
+ * and output them separated by newlines. 
+ */
+function drush_complete() {
+  // We use a distinct --complete-debug option to avoid unwanted debug messages
+  // being printed when users use this option for other purposes in the command
+  // they are trying to complete.
+  if (drush_get_option('complete-debug', FALSE)) {
+    drush_set_context('DRUSH_DEBUG', TRUE);
+  }
+  // Set up as if we were running the command, and attempt to parse.
+  $argv = drush_complete_process_argv();
+  $set_sitealias = drush_sitealias_get_record('@self');
+  $set_sitealias_name = NULL;
+  if (!empty($set_sitealias['name'])) {
+    $set_sitealias_name = $set_sitealias['name'];
+  }
+  // Arguments has now had site-aliases and options removed, so we take the
+  // first item as our command. We need to know if the command is valid, so that
+  // we know if we are supposed to complete an in-progress command name, or
+  // arguments for a command. We do this by checking against our per-site cache
+  // of command names (which will only bootstrap if the cache needs to be
+  // regenerated), rather than drush_parse_command() which always requires a
+  // site bootstrap.
+  $arguments = drush_get_arguments();
+  if (isset($arguments[0]) && count(drush_complete_match($arguments[0], 'command-names')) == 1) {
+    $set_command_name = $arguments[0];
+  }
+  // We unset the command if it is "help" but that is not explicitly found in
+  // args, since Drush sets the command to "help" if no command is specified,
+  // which prevents completion of global options.
+  if ($set_command_name == 'help' && !array_search('help', $argv)) {
+    unset($set_command_name);
+  }
+
+  // Determine the word we are trying to complete, and if it is an option.
+  $last_word = end($argv);
+  if ($last_word[0] == '-') {
+    $word_is_option = TRUE;
+    $last_word = ltrim($last_word, '-');
+  }
+
+  $completions = array();
+
+  if (!$set_command_name) {
+    // We have no command yet.
+    if ($word_is_option) {
+      // Include global option completions.
+      $completions += drush_hyphenate_options(drush_complete_match($last_word, 'options'));
+    }
+    else {
+      if (!$set_sitealias_name) {
+        // Include site alias completions.
+        $completions += drush_complete_match($last_word, 'site-aliases');
+      }
+      // Include command completions.      
+      $completions += drush_complete_match($last_word, 'command-names');
+    }
+  }
+  else {
+    if ($word_is_option) {
+      // Include command option completions.
+      $completions += drush_hyphenate_options(drush_complete_match($last_word, 'options', $set_command_name));
+    }
+    else {
+      // Include command argument completions.
+      $completions += drush_complete_match($last_word, 'arguments', $set_command_name);
+    }
+  }
+
+  // Print the final output.
+  drush_print(implode("\n", $completions));
+
+  // We complete execution here, since our work is done, and continuing would
+  // trigger valid commands occuring later in the bootstrap.
+  drush_bootstrap_finish();
+  exit(1);
+}
+
+/**
+ * This function resets the raw arguments so that Drush can parse the command as
+ * if it was run directly.
+ * 
+ * @return $args
+ *   Array of arguments (argv), excluding the "complete" command.
+ */
+function drush_complete_process_argv() {
+  $argv = drush_get_context('argv');
+  // Remove the "complete" command (running now), as well as arguments added
+  // the the "drush" shell script.
+  $argv = preg_grep("/(drush(\.php)?$|^complete$|^--php)/", $argv, PREG_GREP_INVERT);
+  array_unshift($argv, 'drush');
+  // Reindex the array and perform a partial reparsing.
+  $argv = array_values($argv);
+  drush_set_context('argv', $argv);
+  drush_set_command(NULL);
+  // Reparse arguments, site alias, and command.
+  drush_parse_args();
+  drush_sitealias_check_arg();
+
+  return $argv;
+}
+
+/**
+ * Retrieves the appropriate list of candidate completions, then filters this
+ * list using the last word that we are trying to complete.
+ * 
+ * @param string $last_word
+ *   The last word in the argument list (i.e. the subject of completion).
+ * @param string $type
+ *   The type of completion to be checked (command_name, sitealias etc).
+ * @param string $command
+ *   An optional command to check, if testing for command specific completions.
+ * 
+ * @return array
+ *   Array of candidate completions that start with the same characters as the
+ *   last word. If the last word is empty, return all candidates.
+ */
+function drush_complete_match($last_word, $type, $command = NULL) {
+  // Using preg_grep appears to be faster that strpos with array_filter/loop.
+  return preg_grep('/^' . preg_quote($last_word, '/') . '/', drush_complete_get($type, $command));
+}
+
+/**
+ * Simple helper function to ensure options are properly hyphenated before we
+ * return them to the user (we match against the non-hyphenated versions
+ * internally).
+ * 
+ * @param array $options
+ *   Array of unhyphenated option names.
+ * 
+ * @return array
+ *   Array of hyphenated option names.
+ */
+function drush_hyphenate_options($options) {
+  foreach ($options as $key => $option) {
+    $options[$key] = '--' . ltrim($option, '--');
+  }
+  return $options;
+}
+
+/**
+ * Retrieves from cache, or generates a listing of completion candidates of a
+ * specific type (and optionally, command).
+ * 
+ * @param string $type
+ *   String indicating type of completions to return.
+ *   See drush_complete_rebuild() for possible keys. 
+ * @param string $command
+ *   An optional command name if command specific completion is needed.
+ *   
+ * @return array
+ *   List of candidate completions. 
+ */
+function drush_complete_get($type, $command = NULL) {
+  if (empty($command)) {
+    // Retrieve global items from a non-command specific cache, or rebuild cache
+    // if needed.
+    $cache = drush_cache_get(drush_complete_cache_cid($type), 'complete');
+    if (isset($cache->data)) {
+      return $cache->data;
+    }
+    $complete = drush_complete_rebuild();
+    return $complete[$type];
+  }
+  // Retrieve items from a command specific cache.
+  $cache = drush_cache_get(drush_complete_cache_cid($type, $command), 'complete');
+  if (isset($cache->data)) {
+    return $cache->data;
+  }
+  // Build argument cache - built only on demand.
+  if ($type == 'arguments') {
+    return drush_complete_rebuild_arguments($command);
+  }
+  // Rebuild cache of general command specific items.
+  $complete = drush_complete_rebuild();
+  if (!empty($complete['commands'][$command][$type])) {
+    return $complete['commands'][$command][$type];
+  }
+  return array();
+}
+
+/**
+ * Rebuild and cache completions for everything except command arguments.
+ * 
+ * @return array
+ *   Structured array of completion types, commands and candidate completions.
+ */
+function drush_complete_rebuild() {
+  $complete = array();
+  // Generate completion list, skip if cache available.
+  // Bootstrap to the site level (if possible) - commands may need to check
+  // the bootstrap level, and perhaps bootstrap higher in extrodinary cases.
+  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
+  $commands = drush_get_commands();
+  foreach ($commands as $command_name => $command) {
+    // Add command options - we don't currently complete on option values (with
+    // the exception of engine names).
+    $options = array_keys($command['options']);
+    // Add engine types and sub-options from engines, if any.
+    // This could potentially be improved to only show options associated with
+    // an active engine.
+    foreach ($command['engines'] as $type => $description) {
+      $all_engines = drush_get_engines($type);
+      foreach ($all_engines as $name => $engine) {
+        $options[] = $name;
+        if (!empty($engine['sub-options'])) {
+          foreach ($engine['sub-options'] as $sub_option => $sub_option_values) {
+            $options = array_merge($options, array_keys($sub_option_values));
+          }
+        }
+      }
+    }
+    $complete['commands'][$command_name]['options'] = $options;
+  }
+  // We treat shell aliases as commands for the purposes of completion.
+  $complete['command-names'] = array_merge(array_keys($commands), array_keys(drush_get_option('shell-aliases', array())));
+  $site_aliases = _drush_sitealias_all_list();
+  unset($site_aliases['@0']);
+  $complete['site-aliases'] = array_keys($site_aliases);
+  $complete['options'] = array_keys(drush_get_global_options());
+
+  drush_complete_cache_set($complete);
+  return $complete;
+}
+
+/**
+ * Rebuild and cache completions for command arguments.
+ * 
+ */
+
+/**
+ * Rebuild and cache completions for command arguments.
+ * 
+ * @param string $command
+ *   A specific command to retrieve and cache arguments for.
+ *   
+ * @return array
+ *   Structured array of candidate completion arguments, keyed by the command.
+ */
+function drush_complete_rebuild_arguments($command) {
+  $complete = array();
+  // Generate completion list, skip if cache available.
+  // Bootstrap to the site level (if possible) - commands may need to check
+  // the bootstrap level, and perhaps bootstrap higher in extrodinary cases.
+  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
+  $commands = drush_get_commands();
+  $hook = str_replace("-", "_", $commands[$command]['command-hook']);
+  // Commandfiles can implement drush_COMMAND_complete() returning
+  // an array of completions for that command.
+  $complete['commands'][$command]['arguments'] = drush_command_invoke_all($hook . '_complete');
+  drush_complete_cache_set($complete);
+  return $complete['commands'][$command]['arguments'];
+}
+
+/**
+ * Stores caches for completions.
+ * 
+ * @param $complete
+ *   A structured array of completions, keyed by type, including a 'commands'
+ *   type that contains all commands with command specific completions keyed by
+ *   type. The array does not need to include all types - used by
+ *   drush_complete_rebuild_arguments().
+ */
+function drush_complete_cache_set($complete) {
+  foreach ($complete as $type => $values) {
+    if ($type == 'commands') {
+      foreach ($values as $command_name => $command) {
+        foreach ($command as $command_type => $command_values) {
+          drush_cache_set(drush_complete_cache_cid($command_type, $command_name), $command_values, 'complete', DRUSH_CACHE_TEMPORARY);
+        }
+      }
+    }
+    else {
+      drush_cache_set(drush_complete_cache_cid($type), $values, 'complete', DRUSH_CACHE_TEMPORARY);
+    }
+  }
+}
+
+/**
+ * Generate a cache id.
+ * 
+ * @param $type
+ *   The completion type.
+ * @param $command
+ *   The command name (optional), if completions are command specific.
+ * 
+ * @return string
+ *   Cache id.
+ */
+function drush_complete_cache_cid($type, $command = NULL) {
+  // Everything is cached per-site, except options
+  $context = array();
+  $root = NULL;
+  $site = NULL;
+  if ($type !== 'options') {
+    // For per-site caches (everything except options), we include the site root
+    // and uri/path in the cache id hash. These are quick to determine, and
+    // prevents a bootstrap to site just to get a validated root and URI.
+    // Because these are not validated, there is the possibility of cache misses
+    // but they should be rare, since sites are normally referred to the same
+    // way (e.g. a site alias, or using the current way), at least within a
+    // single command completion session.
+    $root = drush_get_option(array('r', 'root'), drush_locate_root());
+    $site = drush_get_option(array('l', 'uri'), drush_site_path());
+  }
+  return drush_get_cid('complete', array(), array($type, $command, $root, $site));
+}
diff --git a/tests/completeTest.php b/tests/completeTest.php
new file mode 100644
index 0000000..8c4d6ea
--- /dev/null
+++ b/tests/completeTest.php
@@ -0,0 +1,82 @@
+<?php
+
+class completeCase extends Drush_CommandTestCase {
+  public function testComplete() {
+    $env = 'test-complete';
+    $this->setUpDrupal($env);
+    $root = $this->sites[$env]['root'];
+    $docroot = 'web';
+    // Create a couple of aliases for D7 site to test.
+    $aliases['complete-dev'] = array(
+      'root' => UNISH_SANDBOX . '/' . $docroot,
+      'uri' => $env,
+    );
+    // We use the same site, since we don't need a separate one to test aliases.
+    $aliases['complete-dev-alternate'] = array(
+      'root' => UNISH_SANDBOX . '/' . $docroot,
+      'uri' => $env,
+    );
+    $contents = $this->file_aliases($aliases);
+    $alias_path =  UNISH_SANDBOX . '/home/.drush/aliases.drushrc.php';
+    file_put_contents($alias_path, $contents);
+    // We copy our test command into our dev site, so we have a difference we
+    // can detect for cache correctness. 
+    mkdir("$root/sites/$env/modules");
+    copy(dirname(__FILE__) . '/unit.drush.inc', "$root/sites/$env/modules/unit.drush.inc");
+
+    // Test completion generation differentiates site contexts, both on initial
+    // generation and when cached (checking it is retrieving the correct cache).
+    $this->verifyComplete('@complete-dev uni', 'uninstall', 'unit-invoke', FALSE);
+    $this->verifyComplete('uni', '', '', FALSE);
+    $this->verifyComplete('@complete-dev uni', 'uninstall', 'unit-invoke');
+    $this->verifyComplete('uni', '', '');
+
+    // Test overall context sensitivity - all of these should be cache hits.
+    // Site alias alone.
+    $this->verifyComplete('@', '@complete-dev', '@complete-dev-alternate');
+    // Command alone.
+    $this->verifyComplete('d', 'drupal-directory', 'download');
+    // Global option alone.
+    $this->verifyComplete('--n', '--no', '--nocolor');
+    // Site alias + command.
+    $this->verifyComplete('@complete-dev d', 'drupal-directory', 'download');
+    // Command + command option.
+    $this->verifyComplete('dl --', '--destination', '--gitsubmoduleaddparams');
+    // Site alias + command + command option.
+    $this->verifyComplete('@complete-dev dl --', '--destination', '--gitsubmoduleaddparams');
+
+    // TODO: Tests for arguments (which should generate a cache miss on first
+    // request, since they are lazy loaded).
+  }
+
+  /**
+   * Helper function to call completion and make common checks.
+   *
+   * @param $command
+   *   The command line to attempt to complete.
+   * @param $first
+   *   String indicating the expected first completion suggestion.
+   * @param $last
+   *   String indicating the expected last completion suggestion.
+   * @param bool $cache_hit
+   *   Optional parameter, if TRUE or ommitted the debug log is checked to
+   *   ensure a cache hit, if FALSE then a cache miss is checked for.
+   */
+  function verifyComplete($command, $first, $last, $cache_hit = TRUE) {
+    // We capture debug output to a separate file, so we can check for cache
+    // hits/misses.
+    $debug_file = tempnam('/tmp', 'complete-debug');
+    // We expect a return code of 1 so just call execute() directly.
+    $exec = sprintf('%s complete --complete-debug %s 2> %s', UNISH_DRUSH, $command, $debug_file);
+    $this->execute($exec, self::EXIT_ERROR);
+    $result = $this->getOutputAsList();
+    $this->assertEquals(reset($result), $first);
+    $this->assertEquals(end($result), $last);
+    $cache = 'HIT';
+    if (!$cache_hit) {
+      $cache = 'MISS';
+    }
+    $this->assertTrue(strpos(file_get_contents($debug_file), 'Cache ' . $cache . ' cid') !== FALSE);
+    unlink($debug_file);
+  }
+}
\ No newline at end of file
