diff --git commands/core/core.drush.inc commands/core/core.drush.inc
index 93f0c30..2c3c1bf 100644
--- commands/core/core.drush.inc
+++ commands/core/core.drush.inc
@@ -29,7 +29,6 @@ function core_drush_command() {
     'examples' => array(
       'drush dl cck zen' => 'Download CCK module and Zen theme.',
       'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.',
-      'drush help --pipe' => 'A space delimited list of commands',
     ),
   );
   $items['core-cron'] = array(
@@ -204,7 +203,9 @@ function core_drush_command() {
     ),
     'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
   );
-
+  $items['complete'] = array(
+    'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap.
+  );
   return $items;
 }
 
@@ -269,6 +270,8 @@ function drush_core_help() {
               if (!array_key_exists($key, $printed_rows)) {
                 $name = $command['aliases'] ? $key . ' (' . implode(', ', $command['aliases']) . ')': $key;
                 $rows[$key] = array($name, $command['description']);
+                //FIXME_ALIAS
+                //$rows[$key] = array($key, $commands[$key]['description']);
                 $pipe[] = "\"$key\"";
               }
             }
@@ -282,8 +285,6 @@ function drush_core_help() {
       }
     }
 
-    // Space delimited list for use by other scripts. Set the --pipe option.
-    drush_print_pipe(implode(' ', $pipe));
     return;
   }
   else {
@@ -790,3 +791,123 @@ EOD;
 
   return $bashrc_data;
 }
+
+function drush_core_complete() {
+  // Set up arguments as if we were running the command, and attempt to parse.
+  $args = drush_get_context('argv');
+  // TODO: This needs work.
+  // For some reason we get duplicate drush/drush.php arguments in some cases,
+  // we also need to remove the "complete" command (running now).
+  $args = preg_grep("/(drush|drush.php|complete)/", $args, PREG_GREP_INVERT);
+  // We also get an empty argument on the end when we autocomplete on just 'drush '.
+  $last_word = end($args);
+  if (empty($last_word)) {
+    array_pop($args);
+  }
+  // Add the drush command back in, since we removed it above.
+  array_unshift($args, 'drush');
+  // Reindex the array.
+  $args = array_values($args);
+  drush_set_context('argv', $args);
+  drush_set_command(NULL);
+  drush_parse_args();
+  drush_parse_command();
+
+  $complete = array();
+  $command = drush_get_command();
+  $command_name = $command['command'];
+  $cache_filename = sys_get_temp_dir() . '/drush_complete.' . substr(md5(DRUPAL_ROOT . drush_get_context('DRUSH_DRUPAL_SITE_ROOT')), 0, 5);
+  if (file_exists($cache_filename)) {
+    $cache = file_get_contents($cache_filename);
+    $complete = unserialize($cache);
+  }
+  if (empty($complete)) {
+    // Bootstrap to the highest level possible (up to the database level).
+    drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
+    $complete = drush_command_invoke_all('drush_complete');
+    foreach (array_keys(drush_get_commands()) as $command_key) {
+      if (!isset($complete['commands'][$command_key])) {
+        $complete['commands'][$command_key] = NULL;
+      }
+    }
+  }
+//  print_r($complete);
+  $completions = array();
+
+  // Argument debugging code
+//  foreach ($args as $n => $arg) {
+//    $completions[] = $n.'-'.$arg;
+//  }
+  
+  // Add in any command specific options.
+  if ($command && $command_name !== 'help') {
+    $complete['options'] = array_merge($complete['options'], array_keys($command['options']));
+  }
+  // Remove any options that have been already specified.
+  foreach (array_keys(drush_get_merged_options()) as $option) {
+    unset($complete['options'][array_search('--' . $option, $complete['options'])]);
+  }
+
+  // Complete options (starting with a hypnen) first.
+  $last_word = end($args);
+  if ($last_word[0] == '-') {
+    $completions = preg_grep("/^$last_word/", $complete['options']);
+  }
+  else if ($command && $command_name !== 'help') {
+    // We do have a valid command already, so we try matching command arguments.
+    $complete_arguments = array();
+    if (isset($complete['commands'][$command_name])) {
+      if (is_array($complete['commands'][$command_name])) {
+        // Arguments can be added as an array on the command.
+        $complete_arguments = $complete['commands'][$command_name];
+      }
+      if (is_string($complete['commands'][$command_name]) && isset($complete[$complete['commands'][$command_name]]) && is_array($complete[$complete['commands'][$command_name]])) {
+        // Alternatively a key to retrieve argument sets shared between multiple commands.
+        $complete_arguments = $complete[$complete['commands'][$command_name]];
+      }
+    }
+    while (!empty($args) && empty($completions)) {
+      $candidate = implode(' ', $args);
+      $completions = preg_grep("/^$candidate/", $complete_arguments);
+      array_shift($args);
+    }
+    if (empty($completions)) {
+      $completions = array_merge($complete_arguments, $complete['options']);
+    }
+  }
+  else {
+    // We don't have a valid command, so we complete available commands trying
+    // the longest match first.
+    while (!empty($args) && empty($completions)) {
+      $candidate = implode(' ', $args);
+      $completions = preg_grep("/^$candidate/", array_keys($complete['commands']));
+      array_shift($args);
+    }
+  }
+  // The default, if we haven't yet found any context sensitive completions is
+  // to return commands and options.
+  if (empty($completions)) {
+    $completions = array_merge(array_keys($complete['commands']), $complete['options']);
+  }
+  drush_print(implode("\n", $completions));
+  if (!file_exists($cache_filename)) {
+    file_put_contents($cache_filename, serialize($complete));
+  }
+}
+
+function core_drush_complete() {
+  // TODO: Store this in a better structured format upstream
+  $complete = array();
+  preg_match_all('/--[a-z-]*/', implode(' ', array_keys(drush_get_option_help())), $matches);
+  $complete['options'] = $matches[0];
+
+  if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_DATABASE) {
+    $result = db_query('SELECT DISTINCT(type) FROM {watchdog} ORDER BY type');
+    while ($object = db_fetch_object($result)) {
+      $complete['watchdog'][] = $object->type;
+    }
+    $complete['commands']['watchdog show'] = 'watchdog';
+    $complete['commands']['watchdog delete'] = 'watchdog';
+  }
+  return $complete;
+}
diff --git commands/pm/pm.drush.inc commands/pm/pm.drush.inc
index b758686..1cf543f 100644
--- commands/pm/pm.drush.inc
+++ commands/pm/pm.drush.inc
@@ -1440,3 +1440,28 @@ function pm_drush_pm_adjust_download_destination(&$project, $release) {
     }
   }
 }
+
+function pm_drush_complete() {
+  $complete = array();
+  if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_DATABASE) {
+   $result = db_query('SELECT name, status FROM {system} WHERE type = "module"');
+    while ($module = db_fetch_object($result)) {
+      if ($module->status == TRUE) {
+        $complete['commands']['disable'][] = $module->name;
+      }
+      else {
+        $complete['commands']['enable'][] = $module->name;
+      }
+    }
+  }
+  if ($xml = drush_pm_dl_xml()) {
+    // TODO: Possibly introduce a separate caching step for this.
+    if ($projects = $xml->xpath("/projects/project[project_status='published'][api_versions/api_version='" . drush_get_context('DRUSH_DRUPAL_MAJOR_VERSION', 6) . ".x']/short_name")) {
+      foreach ($projects as $project) {
+        $complete['modules'][] = (string)$project;
+      }
+      $complete['commands']['dl'] = $complete['commands']['update'] = $complete['commands']['updatecode'] = $complete['commands']['info'] = 'modules';
+    }
+  }
+  return $complete;
+}
\ No newline at end of file
diff --git drush.completion drush.completion
new file mode 100644
index 0000000..543784f
--- /dev/null
+++ drush.completion
@@ -0,0 +1,9 @@
+#!/bin/bash
+_drush_completion() {
+  oldIFS="$IFS";
+  IFS=$'\n';
+  COMPREPLY=( $(drush complete "${COMP_WORDS[@]}" < /dev/null) )
+  IFS="$oldIFS";
+}
+
+complete -F _drush_completion d dr drush drush.php
diff --git includes/drush.inc includes/drush.inc
index 6a16e8e..25a9c7f 100644
--- includes/drush.inc
+++ includes/drush.inc
@@ -795,16 +795,6 @@ function drush_print($message = '', $indent = 0) {
 }
 
 /**
- * Stores a message which is printed during drush_shutdown() if in compact mode.
- * @param $message
- *   The message to print.
- */
-function drush_print_pipe($message = '') {
-  $buffer = &drush_get_context('DRUSH_PIPE_BUFFER' , '');
-  $buffer .= $message;
-}
-
-/**
  * Prints an array or string.
  * @param $array
  *   The array to print.
