You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by el...@apache.org on 2014/04/08 05:03:30 UTC

[34/53] [abbrv] ACCUMULO-1879 Move shell into new package and module

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/TableOperation.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/TableOperation.java b/shell/src/main/java/org/apache/accumulo/shell/commands/TableOperation.java
new file mode 100644
index 0000000..032579b
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/TableOperation.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.impl.Namespaces;
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Token;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+
+public abstract class TableOperation extends Command {
+
+  protected Option optTablePattern, optTableName, optNamespace;
+  private boolean force = true;
+  private boolean useCommandLine = true;
+
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws Exception {
+    // populate the tableSet set with the tables you want to operate on
+    final SortedSet<String> tableSet = new TreeSet<String>();
+    if (cl.hasOption(optTablePattern.getOpt())) {
+      for (String table : shellState.getConnector().tableOperations().list())
+        if (table.matches(cl.getOptionValue(optTablePattern.getOpt()))) {
+          tableSet.add(table);
+        }
+    } else if (cl.hasOption(optTableName.getOpt())) {
+      tableSet.add(cl.getOptionValue(optTableName.getOpt()));
+    } else if (cl.hasOption(optNamespace.getOpt())) {
+      Instance instance = shellState.getInstance();
+      String namespaceId = Namespaces.getNamespaceId(instance, cl.getOptionValue(optNamespace.getOpt()));
+      for (String tableId : Namespaces.getTableIds(instance, namespaceId)) {
+        tableSet.add(Tables.getTableName(instance, tableId));
+      }
+    } else if (useCommandLine && cl.getArgs().length > 0) {
+      for (String tableName : cl.getArgs()) {
+        tableSet.add(tableName);
+      }
+    } else {
+      shellState.checkTableState();
+      tableSet.add(shellState.getTableName());
+    }
+
+    if (tableSet.isEmpty())
+      Shell.log.warn("No tables found that match your criteria");
+
+    boolean more = true;
+    // flush the tables
+    for (String tableName : tableSet) {
+      if (!more) {
+        break;
+      }
+      if (!shellState.getConnector().tableOperations().exists(tableName)) {
+        throw new TableNotFoundException(null, tableName, null);
+      }
+      boolean operate = true;
+      if (!force) {
+        shellState.getReader().flush();
+        String line = shellState.getReader().readLine(getName() + " { " + tableName + " } (yes|no)? ");
+        more = line != null;
+        operate = line != null && (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"));
+      }
+      if (operate) {
+        doTableOp(shellState, tableName);
+      }
+    }
+
+    return 0;
+  }
+
+  protected abstract void doTableOp(Shell shellState, String tableName) throws Exception;
+
+  @Override
+  public String description() {
+    return "makes a best effort to flush tables from memory to disk";
+  }
+
+  @Override
+  public Options getOptions() {
+    final Options o = new Options();
+
+    optTablePattern = new Option("p", "pattern", true, "regex pattern of table names to operate on");
+    optTablePattern.setArgName("pattern");
+
+    optTableName = new Option(Shell.tableOption, "table", true, "name of a table to operate on");
+    optTableName.setArgName("tableName");
+
+    optNamespace = new Option(Shell.namespaceOption, "namespace", true, "name of a namespace to operate on");
+    optNamespace.setArgName("namespace");
+
+    final OptionGroup opg = new OptionGroup();
+
+    opg.addOption(optTablePattern);
+    opg.addOption(optTableName);
+    opg.addOption(optNamespace);
+
+    o.addOptionGroup(opg);
+
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return useCommandLine ? Shell.NO_FIXED_ARG_LENGTH_CHECK : 0;
+  }
+
+  protected void force() {
+    force = true;
+  }
+
+  protected void noForce() {
+    force = false;
+  }
+
+  protected void disableUnflaggedTableOptions() {
+    useCommandLine = false;
+  }
+
+  @Override
+  public String usage() {
+    return getName() + " [<table>{ <table>}]";
+  }
+
+  @Override
+  public void registerCompletion(final Token root, final Map<Command.CompletionSet,Set<String>> special) {
+    if (useCommandLine)
+      registerCompletionForTables(root, special);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/TablePermissionsCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/TablePermissionsCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/TablePermissionsCommand.java
new file mode 100644
index 0000000..75e3fbd
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/TablePermissionsCommand.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.security.TablePermission;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class TablePermissionsCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    for (String p : TablePermission.printableValues()) {
+      shellState.getReader().println(p);
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "displays a list of valid table permissions";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/TablesCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/TablesCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/TablesCommand.java
new file mode 100644
index 0000000..5bded8d
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/TablesCommand.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.NamespaceNotFoundException;
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
+
+public class TablesCommand extends Command {
+  static final String NAME_AND_ID_FORMAT = "%-20s => %9s%n";
+
+  private Option tableIdOption;
+  private Option sortByTableIdOption;
+  private Option disablePaginationOpt;
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException,
+      NamespaceNotFoundException {
+
+    final String namespace = cl.hasOption(OptUtil.namespaceOpt().getOpt()) ? OptUtil.getNamespaceOpt(cl, shellState) : null;
+    Map<String,String> tables = shellState.getConnector().tableOperations().tableIdMap();
+
+    // filter only specified namespace
+    tables = Maps.filterKeys(tables, new Predicate<String>() {
+      @Override
+      public boolean apply(String tableName) {
+        return namespace == null || Tables.qualify(tableName).getFirst().equals(namespace);
+      }
+    });
+
+    final boolean sortByTableId = cl.hasOption(sortByTableIdOption.getOpt());
+    tables = new TreeMap<String,String>((sortByTableId ? MapUtils.invertMap(tables) : tables));
+
+    Iterator<String> it = Iterators.transform(tables.entrySet().iterator(), new Function<Entry<String,String>,String>() {
+      @Override
+      public String apply(Map.Entry<String,String> entry) {
+        String tableName = String.valueOf(sortByTableId ? entry.getValue() : entry.getKey());
+        String tableId = String.valueOf(sortByTableId ? entry.getKey() : entry.getValue());
+        if (namespace != null)
+          tableName = Tables.qualify(tableName).getSecond();
+        if (cl.hasOption(tableIdOption.getOpt()))
+          return String.format(NAME_AND_ID_FORMAT, tableName, tableId);
+        else
+          return tableName;
+      };
+    });
+
+    shellState.printLines(it, !cl.hasOption(disablePaginationOpt.getOpt()));
+    return 0;
+  }
+
+  @Override
+  public String description() {
+    return "displays a list of all existing tables";
+  }
+
+  @Override
+  public Options getOptions() {
+    final Options o = new Options();
+    tableIdOption = new Option("l", "list-ids", false, "display internal table ids along with the table name");
+    o.addOption(tableIdOption);
+    sortByTableIdOption = new Option("s", "sort-ids", false, "with -l: sort output by table ids");
+    o.addOption(sortByTableIdOption);
+    disablePaginationOpt = new Option("np", "no-pagination", false, "disable pagination of output");
+    o.addOption(disablePaginationOpt);
+    o.addOption(OptUtil.namespaceOpt("name of namespace to list only its tables"));
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/TraceCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/TraceCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/TraceCommand.java
new file mode 100644
index 0000000..7f63570
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/TraceCommand.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.trace.instrument.Trace;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.trace.TraceDump;
+import org.apache.accumulo.core.trace.TraceDump.Printer;
+import org.apache.accumulo.core.util.BadArgumentException;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.commons.cli.CommandLine;
+import org.apache.hadoop.io.Text;
+
+public class TraceCommand extends DebugCommand {
+  
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    if (cl.getArgs().length == 1) {
+      if (cl.getArgs()[0].equalsIgnoreCase("on")) {
+        Trace.on("shell:" + shellState.getPrincipal());
+      } else if (cl.getArgs()[0].equalsIgnoreCase("off")) {
+        if (Trace.isTracing()) {
+          final long trace = Trace.currentTrace().traceId();
+          Trace.off();
+          StringBuffer sb = new StringBuffer();
+          int traceCount = 0;
+          for (int i = 0; i < 30; i++) {
+            sb = new StringBuffer();
+            try {
+              final Map<String,String> properties = shellState.getConnector().instanceOperations().getSystemConfiguration();
+              final String table = properties.get(Property.TRACE_TABLE.getKey());
+              final String user = shellState.getConnector().whoami();
+              final Authorizations auths = shellState.getConnector().securityOperations().getUserAuthorizations(user);
+              final Scanner scanner = shellState.getConnector().createScanner(table, auths);
+              scanner.setRange(new Range(new Text(Long.toHexString(trace))));
+              final StringBuffer finalSB = sb;
+              traceCount = TraceDump.printTrace(scanner, new Printer() {
+                @Override
+                public void print(final String line) {
+                  try {
+                    finalSB.append(line + "\n");
+                  } catch (Exception ex) {
+                    throw new RuntimeException(ex);
+                  }
+                }
+              });
+              if (traceCount > 0) {
+                shellState.getReader().print(sb.toString());
+                break;
+              }
+            } catch (Exception ex) {
+              shellState.printException(ex);
+            }
+            shellState.getReader().println("Waiting for trace information");
+            shellState.getReader().flush();
+            UtilWaitThread.sleep(500);
+          }
+          if (traceCount < 0) {
+            // display the trace even though there are unrooted spans
+            shellState.getReader().print(sb.toString());
+          }
+        } else {
+          shellState.getReader().println("Not tracing");
+        }
+      } else
+        throw new BadArgumentException("Argument must be 'on' or 'off'", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
+    } else if (cl.getArgs().length == 0) {
+      shellState.getReader().println(Trace.isTracing() ? "on" : "off");
+    } else {
+      shellState.printException(new IllegalArgumentException("Expected 0 or 1 argument. There were " + cl.getArgs().length + "."));
+      printHelp(shellState);
+      return 1;
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "turns trace logging on or off";
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/UserCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/UserCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/UserCommand.java
new file mode 100644
index 0000000..491f144
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/UserCommand.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Token;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class UserCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
+    // save old credentials and connection in case of failure
+    String user = cl.getArgs()[0];
+    byte[] pass;
+    
+    // We can't let the wrapping try around the execute method deal
+    // with the exceptions because we have to do something if one
+    // of these methods fails
+    final String p = shellState.readMaskedLine("Enter password for user " + user + ": ", '*');
+    if (p == null) {
+      shellState.getReader().println();
+      return 0;
+    } // user canceled
+    pass = p.getBytes(StandardCharsets.UTF_8);
+    shellState.updateUser(user, new PasswordToken(pass));
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "switches to the specified user";
+  }
+  
+  @Override
+  public void registerCompletion(final Token root, final Map<Command.CompletionSet,Set<String>> special) {
+    registerCompletionForUsers(root, special);
+  }
+  
+  @Override
+  public String usage() {
+    return getName() + " <username>";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 1;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/UserPermissionsCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/UserPermissionsCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/UserPermissionsCommand.java
new file mode 100644
index 0000000..ad6a20d
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/UserPermissionsCommand.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.security.NamespacePermission;
+import org.apache.accumulo.core.security.SystemPermission;
+import org.apache.accumulo.core.security.TablePermission;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+public class UserPermissionsCommand extends Command {
+  private Option userOpt;
+  
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
+    final String user = cl.getOptionValue(userOpt.getOpt(), shellState.getConnector().whoami());
+
+    String delim = "";
+    shellState.getReader().print("System permissions: ");
+    for (SystemPermission p : SystemPermission.values()) {
+      if (p != null && shellState.getConnector().securityOperations().hasSystemPermission(user, p)) {
+        shellState.getReader().print(delim + "System." + p.name());
+        delim = ", ";
+      }
+    }
+    shellState.getReader().println();
+
+    boolean runOnce = true;
+    for (String n : shellState.getConnector().namespaceOperations().list()) {
+      delim = "";
+      for (NamespacePermission p : NamespacePermission.values()) {
+        if (p != null && shellState.getConnector().securityOperations().hasNamespacePermission(user, n, p)) {
+          if (runOnce) {
+            shellState.getReader().print("\nNamespace permissions (" + n + "): ");
+            runOnce = false;
+          }
+          shellState.getReader().print(delim + "Namespace." + p.name());
+          delim = ", ";
+        }
+      }
+      runOnce = true;
+    }
+    shellState.getReader().println();
+
+    
+    runOnce = true;
+    for (String t : shellState.getConnector().tableOperations().list()) {
+      delim = "";
+      for (TablePermission p : TablePermission.values()) {
+        if (shellState.getConnector().securityOperations().hasTablePermission(user, t, p) && p != null) {
+          if (runOnce) {
+            shellState.getReader().print("\nTable permissions (" + t + "): ");
+            runOnce = false;
+          }
+          shellState.getReader().print(delim + "Table." + p.name());
+          delim = ", ";
+        }
+
+      }
+      runOnce = true;
+    }
+    shellState.getReader().println();
+
+    return 0;
+  }
+
+  @Override
+  public String description() {
+    return "displays a user's system, table, and namespace permissions";
+  }
+
+  @Override
+  public Options getOptions() {
+    Options o = new Options();
+    userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
+    userOpt.setArgName("user");
+    o.addOption(userOpt);
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/UsersCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/UsersCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/UsersCommand.java
new file mode 100644
index 0000000..5ea61bf
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/UsersCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class UsersCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
+    for (String user : shellState.getConnector().securityOperations().listLocalUsers()) {
+      shellState.getReader().println(user);
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "displays a list of existing users";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/commands/WhoAmICommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/WhoAmICommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/WhoAmICommand.java
new file mode 100644
index 0000000..f80a621
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/WhoAmICommand.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class WhoAmICommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    shellState.getReader().println(shellState.getConnector().whoami());
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "reports the current user name";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/format/DeleterFormatter.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/format/DeleterFormatter.java b/shell/src/main/java/org/apache/accumulo/shell/format/DeleterFormatter.java
new file mode 100644
index 0000000..b90b55e
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/format/DeleterFormatter.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.format;
+
+import java.io.IOException;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.MutationsRejectedException;
+import org.apache.accumulo.core.data.ConstraintViolationSummary;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.security.ColumnVisibility;
+import org.apache.accumulo.core.util.format.DefaultFormatter;
+import org.apache.accumulo.shell.Shell;
+import org.apache.log4j.Logger;
+
+public class DeleterFormatter extends DefaultFormatter {
+  
+  private static final Logger log = Logger.getLogger(DeleterFormatter.class);
+  private BatchWriter writer;
+  private Shell shellState;
+  private boolean printTimestamps;
+  private boolean force;
+  private boolean more;
+  
+  public DeleterFormatter(BatchWriter writer, Iterable<Entry<Key,Value>> scanner, boolean printTimestamps, Shell shellState, boolean force) {
+    super.initialize(scanner, printTimestamps);
+    this.writer = writer;
+    this.shellState = shellState;
+    this.printTimestamps = printTimestamps;
+    this.force = force;
+    this.more = true;
+  }
+  
+  @Override
+  public boolean hasNext() {
+    if (!getScannerIterator().hasNext() || !more) {
+      try {
+        writer.close();
+      } catch (MutationsRejectedException e) {
+        log.error(e.toString());
+        if (Shell.isDebuggingEnabled())
+          for (ConstraintViolationSummary cvs : e.getConstraintViolationSummaries())
+            log.trace(cvs.toString());
+      }
+      return false;
+    }
+    return true;
+  }
+  
+  /**
+   * @return null, because the iteration will provide prompts and handle deletes internally.
+   */
+  @Override
+  public String next() {
+    Entry<Key,Value> next = getScannerIterator().next();
+    Key key = next.getKey();
+    Mutation m = new Mutation(key.getRow());
+    String entryStr = formatEntry(next, printTimestamps);
+    boolean delete = force;
+    try {
+      if (!force) {
+        shellState.getReader().flush();
+        String line = shellState.getReader().readLine("Delete { " + entryStr + " } ? ");
+        more = line != null;
+        delete = line != null && (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"));
+      }
+      if (delete) {
+        m.putDelete(key.getColumnFamily(), key.getColumnQualifier(), new ColumnVisibility(key.getColumnVisibility()), key.getTimestamp());
+        try {
+          writer.addMutation(m);
+        } catch (MutationsRejectedException e) {
+          log.error(e.toString());
+          if (Shell.isDebuggingEnabled())
+            for (ConstraintViolationSummary cvs : e.getConstraintViolationSummaries())
+              log.trace(cvs.toString());
+        }
+      }
+      shellState.getReader().print(String.format("[%s] %s%n", delete ? "DELETED" : "SKIPPED", entryStr));
+    } catch (IOException e) {
+      log.error("Cannot write to console", e);
+      throw new RuntimeException(e);
+    }
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/java/org/apache/accumulo/shell/mock/MockShell.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/mock/MockShell.java b/shell/src/main/java/org/apache/accumulo/shell/mock/MockShell.java
new file mode 100644
index 0000000..151648a
--- /dev/null
+++ b/shell/src/main/java/org/apache/accumulo/shell/mock/MockShell.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.client.mock.MockInstance;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.ShellOptionsJC;
+
+/**
+ * An Accumulo Shell implementation that allows a developer to attach an InputStream and Writer to the Shell for testing purposes.
+ */
+public class MockShell extends Shell {
+  private static final String NEWLINE = "\n";
+  
+  protected InputStream in;
+  protected OutputStream out;
+  
+  public MockShell(InputStream in, OutputStream out) throws IOException {
+    super();
+    this.in = in;
+    this.out = out;
+  }
+  
+  public boolean config(String... args) {
+    configError = super.config(args);
+    
+    // Update the ConsoleReader with the input and output "redirected"
+    try {
+      this.reader = new ConsoleReader(in, out);
+    } catch (Exception e) {
+      printException(e);
+      configError = true;
+    }
+    
+    // Don't need this for testing purposes
+    this.reader.setHistoryEnabled(false);
+    this.reader.setPaginationEnabled(false);
+    
+    // Make the parsing from the client easier;
+    this.verbose = false;
+    return configError;
+  }
+  
+  @Override
+  protected void setInstance(ShellOptionsJC options) {
+    // We always want a MockInstance for this test
+    instance = new MockInstance();
+  }
+  
+  public int start() throws IOException {
+    if (configError)
+      return 1;
+    
+    String input;
+    if (isVerbose())
+      printInfo();
+    
+    if (execFile != null) {
+      java.util.Scanner scanner = new java.util.Scanner(execFile, StandardCharsets.UTF_8.name());
+      try {
+        while (scanner.hasNextLine() && !hasExited()) {
+          execCommand(scanner.nextLine(), true, isVerbose());
+        }
+      } finally {
+        scanner.close();
+      }
+    } else if (execCommand != null) {
+      for (String command : execCommand.split("\n")) {
+        execCommand(command, true, isVerbose());
+      }
+      return exitCode;
+    }
+    
+    while (true) {
+      if (hasExited())
+        return exitCode;
+      
+      reader.setPrompt(getDefaultPrompt());
+      input = reader.readLine();
+      if (input == null) {
+        reader.println();
+        return exitCode;
+      } // user canceled
+      
+      execCommand(input, false, false);
+    }
+  }
+  
+  /**
+   * @param in
+   *          the in to set
+   */
+  public void setConsoleInputStream(InputStream in) {
+    this.in = in;
+  }
+  
+  /**
+   * @param out
+   *          the output stream to set
+   */
+  public void setConsoleWriter(OutputStream out) {
+    this.out = out;
+  }
+  
+  /**
+   * Convenience method to create the byte-array to hand to the console
+   * 
+   * @param commands
+   *          An array of commands to run
+   * @return A byte[] input stream which can be handed to the console.
+   */
+  public static ByteArrayInputStream makeCommands(String... commands) {
+    StringBuilder sb = new StringBuilder(commands.length * 8);
+    
+    for (String command : commands) {
+      sb.append(command).append(NEWLINE);
+    }
+    
+    return new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8));
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/main/resources/.gitignore
----------------------------------------------------------------------
diff --git a/shell/src/main/resources/.gitignore b/shell/src/main/resources/.gitignore
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/PasswordConverterTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/PasswordConverterTest.java b/shell/src/test/java/org/apache/accumulo/shell/PasswordConverterTest.java
new file mode 100644
index 0000000..8e03830
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/PasswordConverterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Scanner;
+
+import org.apache.accumulo.shell.ShellOptionsJC.PasswordConverter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+
+public class PasswordConverterTest {
+  
+  private class Password {
+    @Parameter(names = "--password", converter = PasswordConverter.class)
+    String password;
+  }
+  
+  private String[] argv;
+  private Password password;
+  private static InputStream realIn;
+  
+  @BeforeClass
+  public static void saveIn() {
+    realIn = System.in;
+  }
+  
+  @Before
+  public void setup() throws IOException {
+    argv = new String[] {"--password", ""};
+    password = new Password();
+    
+    PipedInputStream in = new PipedInputStream();
+    PipedOutputStream out = new PipedOutputStream(in);
+    OutputStreamWriter osw = new OutputStreamWriter(out);
+    osw.write("secret");
+    osw.close();
+    
+    System.setIn(in);
+  }
+  
+  @After
+  public void teardown() {
+    System.setIn(realIn);
+  }
+  
+  @Test
+  public void testPass() {
+    String expected = String.valueOf(Math.random());
+    argv[1] = "pass:" + expected;
+    new JCommander(password, argv);
+    assertEquals(expected, password.password);
+  }
+  
+  @Test
+  public void testEnv() {
+    String name = System.getenv().keySet().iterator().next();
+    argv[1] = "env:" + name;
+    new JCommander(password, argv);
+    assertEquals(System.getenv(name), password.password);
+  }
+  
+  @Test
+  public void testFile() throws FileNotFoundException {
+    argv[1] = "file:pom.xml";
+    Scanner scan = new Scanner(new File("pom.xml"));
+    String expected = scan.nextLine();
+    scan.close();
+    new JCommander(password, argv);
+    assertEquals(expected, password.password);
+  }
+  
+  @Test(expected=ParameterException.class)
+  public void testNoFile() throws FileNotFoundException {
+    argv[1] = "file:doesnotexist";
+    new JCommander(password, argv);
+  }
+
+  @Test
+  public void testStdin() {
+    argv[1] = "stdin";
+    new JCommander(password, argv);
+    assertEquals("stdin", password.password);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java b/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java
new file mode 100644
index 0000000..27f3247
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.ShellTest.TestOutputStream;
+import org.apache.log4j.Level;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.beust.jcommander.ParameterException;
+
+public class ShellConfigTest {
+  TestOutputStream output;
+  Shell shell;
+  PrintStream out;
+  
+  @Before
+  public void setUp() throws Exception {
+    Shell.log.setLevel(Level.ERROR);
+    
+    out = System.out;
+    output = new TestOutputStream();
+    System.setOut(new PrintStream(output));
+
+    shell = new Shell(new ConsoleReader(new FileInputStream(FileDescriptor.in), output), new PrintWriter(output));
+    shell.setLogErrorsToConsole();
+  }
+  
+  @After
+  public void teardown() throws Exception {
+    shell.shutdown();
+    output.clear();
+    System.setOut(out);
+  }
+  
+  @Test
+  public void testHelp() {
+    assertTrue(shell.config("--help"));
+    assertTrue("Did not print usage", output.get().startsWith("Usage"));
+  }
+  
+  @Test
+  public void testBadArg() {
+    assertTrue(shell.config("--bogus"));
+    assertTrue("Did not print usage", output.get().startsWith("Usage"));
+  }
+  
+  @Test
+  public void testToken() {
+    assertTrue(shell.config("--fake", "-tc", PasswordToken.class.getCanonicalName()));
+    assertTrue(output.get().contains(ParameterException.class.getCanonicalName()));
+  }
+  
+  @Test
+  public void testTokenAndOption() {
+    assertFalse(shell.config("--fake", "-tc", PasswordToken.class.getCanonicalName(), "-u", "foo", "-l", "password=foo"));
+  }
+  
+  @Test
+  public void testTokenAndOptionAndPassword() {
+    assertTrue(shell.config("--fake", "-tc", PasswordToken.class.getCanonicalName(), "-l", "password=foo", "-p", "bar"));
+    assertTrue(output.get().contains(ParameterException.class.getCanonicalName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/ShellSetInstanceTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/ShellSetInstanceTest.java b/shell/src/test/java/org/apache/accumulo/shell/ShellSetInstanceTest.java
new file mode 100644
index 0000000..463f97d
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/ShellSetInstanceTest.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expect;
+import static org.powermock.api.easymock.PowerMock.createMock;
+import static org.powermock.api.easymock.PowerMock.expectLastCall;
+import static org.powermock.api.easymock.PowerMock.expectNew;
+import static org.powermock.api.easymock.PowerMock.mockStatic;
+import static org.powermock.api.easymock.PowerMock.replay;
+import static org.powermock.api.easymock.PowerMock.verify;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.client.ClientConfiguration;
+import org.apache.accumulo.core.client.ClientConfiguration.ClientProperty;
+import org.apache.accumulo.core.client.ZooKeeperInstance;
+import org.apache.accumulo.core.client.mock.MockInstance;
+import org.apache.accumulo.core.conf.AccumuloConfiguration;
+import org.apache.accumulo.core.conf.ConfigSanityCheck;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.conf.SiteConfiguration;
+import org.apache.accumulo.core.zookeeper.ZooUtil;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.ShellOptionsJC;
+import org.apache.hadoop.fs.Path;
+import org.apache.log4j.Level;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({Shell.class, ZooUtil.class, ConfigSanityCheck.class})
+public class ShellSetInstanceTest {
+  public static class TestOutputStream extends OutputStream {
+    StringBuilder sb = new StringBuilder();
+
+    @Override
+    public void write(int b) throws IOException {
+      sb.append((char) (0xff & b));
+    }
+
+    public String get() {
+      return sb.toString();
+    }
+
+    public void clear() {
+      sb.setLength(0);
+    }
+  }
+
+  @BeforeClass
+  public static void setupClass() {
+    // This is necessary because PowerMock messes with Hadoop's ability to
+    // determine the current user (see security.UserGroupInformation).
+    System.setProperty("HADOOP_USER_NAME", "test");
+  }
+  @AfterClass
+  public static void teardownClass() {
+    System.clearProperty("HADOOP_USER_NAME");
+  }
+
+  private TestOutputStream output;
+  private Shell shell;
+
+  @Before
+  public void setup() throws IOException {
+    Shell.log.setLevel(Level.OFF);
+    output = new TestOutputStream();
+    shell = new Shell(new ConsoleReader(new FileInputStream(FileDescriptor.in), output), new PrintWriter(output));
+    shell.setLogErrorsToConsole();
+  }
+  @After
+  public void tearDown() {
+    shell.shutdown();
+    SiteConfiguration.clearInstance();
+  }
+
+  @Test
+  public void testSetInstance_Fake() throws Exception {
+    ShellOptionsJC opts = createMock(ShellOptionsJC.class);
+    expect(opts.isFake()).andReturn(true);
+    replay(opts);
+    MockInstance theInstance = createMock(MockInstance.class);
+    expectNew(MockInstance.class, "fake").andReturn(theInstance);
+    replay(theInstance, MockInstance.class);
+
+    shell.setInstance(opts);
+    verify(theInstance, MockInstance.class);
+  }
+  @Test
+  public void testSetInstance_HdfsZooInstance_Explicit() throws Exception {
+    testSetInstance_HdfsZooInstance(true, false, false);
+  }
+  @Test
+  public void testSetInstance_HdfsZooInstance_InstanceGiven() throws Exception {
+    testSetInstance_HdfsZooInstance(false, true, false);
+  }
+  @Test
+  public void testSetInstance_HdfsZooInstance_HostsGiven() throws Exception {
+    testSetInstance_HdfsZooInstance(false, false, true);
+  }
+  @Test
+  public void testSetInstance_HdfsZooInstance_Implicit() throws Exception {
+    testSetInstance_HdfsZooInstance(false, false, false);
+  }
+  
+  @SuppressWarnings("deprecation")
+  private void testSetInstance_HdfsZooInstance(boolean explicitHdfs, boolean onlyInstance, boolean onlyHosts)
+    throws Exception {
+    ClientConfiguration clientConf = createMock(ClientConfiguration.class);
+    ShellOptionsJC opts = createMock(ShellOptionsJC.class);
+    expect(opts.isFake()).andReturn(false);
+    expect(opts.getClientConfiguration()).andReturn(clientConf);
+    expect(opts.isHdfsZooInstance()).andReturn(explicitHdfs);
+    if (!explicitHdfs) {
+      expect(opts.getZooKeeperInstance())
+        .andReturn(Collections.<String>emptyList());
+      if (onlyInstance) {
+        expect(opts.getZooKeeperInstanceName()).andReturn("instance");
+        expect(clientConf.withInstance("instance")).andReturn(clientConf);
+      } else {
+        expect(opts.getZooKeeperInstanceName()).andReturn(null);
+      }
+      if (onlyHosts) {
+        expect(opts.getZooKeeperHosts()).andReturn("host3,host4");
+        expect(clientConf.withZkHosts("host3,host4")).andReturn(clientConf);
+      } else {
+        expect(opts.getZooKeeperHosts()).andReturn(null);
+      }
+    }
+    replay(opts);
+
+    if (!onlyInstance) {
+      expect(clientConf.get(ClientProperty.INSTANCE_NAME)).andReturn(null);
+    }
+
+    mockStatic(ConfigSanityCheck.class);
+    ConfigSanityCheck.validate(EasyMock.<AccumuloConfiguration>anyObject());
+    expectLastCall().atLeastOnce();
+    replay(ConfigSanityCheck.class);
+
+    if (!onlyHosts) {
+      expect(clientConf.containsKey(Property.INSTANCE_ZK_HOST.getKey())).andReturn(true).atLeastOnce();
+      expect(clientConf.getString(Property.INSTANCE_ZK_HOST.getKey())).andReturn("host1,host2").atLeastOnce();
+      expect(clientConf.withZkHosts("host1,host2")).andReturn(clientConf);
+    }
+    if (!onlyInstance) {
+      expect(clientConf.containsKey(Property.INSTANCE_VOLUMES.getKey())).andReturn(false).atLeastOnce();
+      expect(clientConf.containsKey(Property.INSTANCE_DFS_DIR.getKey())).andReturn(true).atLeastOnce();
+      expect(clientConf.containsKey(Property.INSTANCE_DFS_URI.getKey())).andReturn(true).atLeastOnce();
+      expect(clientConf.getString(Property.INSTANCE_DFS_URI.getKey())).andReturn("hdfs://nn1").atLeastOnce();
+      expect(clientConf.getString(Property.INSTANCE_DFS_DIR.getKey())).andReturn("/dfs").atLeastOnce();
+    }
+
+    UUID randomUUID = null;
+    if (!onlyInstance) {
+      mockStatic(ZooUtil.class);
+      randomUUID = UUID.randomUUID();
+      expect(ZooUtil.getInstanceIDFromHdfs(anyObject(Path.class), anyObject(AccumuloConfiguration.class)))
+        .andReturn(randomUUID.toString());
+      replay(ZooUtil.class);
+      expect(clientConf.withInstance(randomUUID)).andReturn(clientConf);
+    }
+    replay(clientConf);
+
+    ZooKeeperInstance theInstance = createMock(ZooKeeperInstance.class);
+    
+    expectNew(ZooKeeperInstance.class, clientConf).andReturn(theInstance);
+    replay(theInstance, ZooKeeperInstance.class);
+
+    shell.setInstance(opts);
+    verify(theInstance, ZooKeeperInstance.class);
+  }
+  @Test
+  public void testSetInstance_ZKInstance_DashZ() throws Exception {
+    testSetInstance_ZKInstance(true);
+  }
+  @Test
+  public void testSetInstance_ZKInstance_DashZIandZH() throws Exception {
+    testSetInstance_ZKInstance(false);
+  }
+  private void testSetInstance_ZKInstance(boolean dashZ) throws Exception {
+    ClientConfiguration clientConf = createMock(ClientConfiguration.class);
+    ShellOptionsJC opts = createMock(ShellOptionsJC.class);
+    expect(opts.isFake()).andReturn(false);
+    expect(opts.getClientConfiguration()).andReturn(clientConf);
+    expect(opts.isHdfsZooInstance()).andReturn(false);
+    if (dashZ) {
+      expect(clientConf.withInstance("foo")).andReturn(clientConf);
+      expect(clientConf.withZkHosts("host1,host2")).andReturn(clientConf);
+      List<String> zl = new java.util.ArrayList<String>();
+      zl.add("foo");
+      zl.add("host1,host2");
+      expect(opts.getZooKeeperInstance()).andReturn(zl);
+      expectLastCall().anyTimes();
+    } else {
+      expect(clientConf.withInstance("bar")).andReturn(clientConf);
+      expect(clientConf.withZkHosts("host3,host4")).andReturn(clientConf);
+      expect(opts.getZooKeeperInstance()).andReturn(Collections.<String>emptyList());
+      expect(opts.getZooKeeperInstanceName()).andReturn("bar");
+      expect(opts.getZooKeeperHosts()).andReturn("host3,host4");
+    }
+    replay(clientConf);
+    replay(opts);
+
+    ZooKeeperInstance theInstance = createMock(ZooKeeperInstance.class);
+    expectNew(ZooKeeperInstance.class, clientConf).andReturn(theInstance);
+    replay(theInstance, ZooKeeperInstance.class);
+
+    shell.setInstance(opts);
+    verify(theInstance, ZooKeeperInstance.class);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/ShellTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/ShellTest.java b/shell/src/test/java/org/apache/accumulo/shell/ShellTest.java
new file mode 100644
index 0000000..ef12baa
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/ShellTest.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.util.format.DateStringFormatter;
+import org.apache.accumulo.shell.Shell;
+import org.apache.log4j.Level;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ShellTest {
+  public static class TestOutputStream extends OutputStream {
+    StringBuilder sb = new StringBuilder();
+
+    @Override
+    public void write(int b) throws IOException {
+      sb.append((char) (0xff & b));
+    }
+
+    public String get() {
+      return sb.toString();
+    }
+
+    public void clear() {
+      sb.setLength(0);
+    }
+  }
+
+  public static class StringInputStream extends InputStream {
+    private String source = "";
+    private int offset = 0;
+
+    @Override
+    public int read() throws IOException {
+      if (offset == source.length())
+        return '\n';
+      else
+        return source.charAt(offset++);
+    }
+
+    public void set(String other) {
+      source = other;
+      offset = 0;
+    }
+  }
+
+  private StringInputStream input;
+  private TestOutputStream output;
+  private Shell shell;
+
+  void exec(String cmd) throws IOException {
+    output.clear();
+    shell.execCommand(cmd, true, true);
+  }
+
+  void exec(String cmd, boolean expectGoodExit) throws IOException {
+    exec(cmd);
+    if (expectGoodExit)
+      assertGoodExit("", true);
+    else
+      assertBadExit("", true);
+  }
+
+  void exec(String cmd, boolean expectGoodExit, String expectString) throws IOException {
+    exec(cmd, expectGoodExit, expectString, true);
+  }
+
+  void exec(String cmd, boolean expectGoodExit, String expectString, boolean stringPresent) throws IOException {
+    exec(cmd);
+    if (expectGoodExit)
+      assertGoodExit(expectString, stringPresent);
+    else
+      assertBadExit(expectString, stringPresent);
+  }
+
+  @Before
+  public void setup() throws IOException {
+    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+    Shell.log.setLevel(Level.OFF);
+    output = new TestOutputStream();
+    input = new StringInputStream();
+    PrintWriter pw = new PrintWriter(new OutputStreamWriter(output));
+    shell = new Shell(new ConsoleReader(input, output), pw);
+    shell.setLogErrorsToConsole();
+    shell.config("--fake", "-u", "test", "-p", "secret");
+  }
+
+  @After
+  public void teardown() {
+    shell.shutdown();
+  }
+
+  void assertGoodExit(String s, boolean stringPresent) {
+    Shell.log.debug(output.get());
+    assertEquals(shell.getExitCode(), 0);
+    if (s.length() > 0)
+      assertEquals(s + " present in " + output.get() + " was not " + stringPresent, stringPresent, output.get().contains(s));
+  }
+
+  void assertBadExit(String s, boolean stringPresent) {
+    Shell.log.debug(output.get());
+    assertTrue(shell.getExitCode() > 0);
+    if (s.length() > 0)
+      assertEquals(s + " present in " + output.get() + " was not " + stringPresent, stringPresent, output.get().contains(s));
+    shell.resetExitCode();
+  }
+
+  @Test
+  public void aboutTest() throws IOException {
+    Shell.log.debug("Starting about test -----------------------------------");
+    exec("about", true, "Shell - Apache Accumulo Interactive Shell");
+    exec("about -v", true, "Current user:");
+    exec("about arg", false, "java.lang.IllegalArgumentException: Expected 0 arguments");
+  }
+
+  @Test
+  public void addGetSplitsTest() throws IOException {
+    Shell.log.debug("Starting addGetSplits test ----------------------------");
+    exec("addsplits arg", false, "java.lang.IllegalStateException: Not in a table context");
+    exec("createtable test", true);
+    exec("addsplits 1 \\x80", true);
+    exec("getsplits", true, "1\n\\x80");
+    exec("deletetable test -f", true, "Table: [test] has been deleted");
+  }
+
+  @Test
+  public void insertDeleteScanTest() throws IOException {
+    Shell.log.debug("Starting insertDeleteScan test ------------------------");
+    exec("insert r f q v", false, "java.lang.IllegalStateException: Not in a table context");
+    exec("delete r f q", false, "java.lang.IllegalStateException: Not in a table context");
+    exec("createtable test", true);
+    exec("insert r f q v", true);
+    exec("scan", true, "r f:q []    v");
+    exec("delete r f q", true);
+    exec("scan", true, "r f:q []    v", false);
+    exec("insert \\x90 \\xa0 \\xb0 \\xc0\\xd0\\xe0\\xf0", true);
+    exec("scan", true, "\\x90 \\xA0:\\xB0 []    \\xC0\\xD0");
+    exec("scan -f 2", true, "\\x90 \\xA0:\\xB0 []    \\xC0\\xD0");
+    exec("scan -f 2", true, "\\x90 \\xA0:\\xB0 []    \\xC0\\xD0\\xE0", false);
+    exec("scan -b \\x90 -e \\x90 -c \\xA0", true, "\\x90 \\xA0:\\xB0 []    \\xC0");
+    exec("scan -b \\x90 -e \\x90 -c \\xA0:\\xB0", true, "\\x90 \\xA0:\\xB0 []    \\xC0");
+    exec("scan -b \\x90 -be", true, "\\x90 \\xA0:\\xB0 []    \\xC0", false);
+    exec("scan -e \\x90 -ee", true, "\\x90 \\xA0:\\xB0 []    \\xC0", false);
+    exec("scan -b \\x90\\x00", true, "\\x90 \\xA0:\\xB0 []    \\xC0", false);
+    exec("scan -e \\x8f", true, "\\x90 \\xA0:\\xB0 []    \\xC0", false);
+    exec("delete \\x90 \\xa0 \\xb0", true);
+    exec("scan", true, "\\x90 \\xA0:\\xB0 []    \\xC0", false);
+    exec("deletetable test -f", true, "Table: [test] has been deleted");
+  }
+
+  @Test
+  public void authsTest() throws Exception {
+    Shell.log.debug("Starting auths test --------------------------");
+    exec("setauths x,y,z", false, "Missing required option");
+    exec("setauths -s x,y,z -u notauser", false, "user does not exist");
+    exec("setauths -s y,z,x", true);
+    exec("getauths -u notauser", false, "user does not exist");
+    exec("getauths", true, "x,y,z");
+    exec("addauths -u notauser", false, "Missing required option");
+    exec("addauths -u notauser -s foo", false, "user does not exist");
+    exec("addauths -s a", true);
+    exec("getauths", true, "a,x,y,z");
+    exec("setauths -c", true);
+  }
+
+  @Test
+  public void userTest() throws Exception {
+    Shell.log.debug("Starting user test --------------------------");
+    // Test cannot be done via junit because createuser only prompts for password
+    // exec("createuser root", false, "user exists");
+  }
+
+  @Test
+  public void duContextTest() throws Exception {
+    Shell.log.debug("Starting du context test --------------------------");
+    exec("createtable t", true);
+    exec("du", true, "0 [t]");
+    exec("deletetable t -f", true, "Table: [t] has been deleted");
+  }
+
+  @Test
+  public void duTest() throws IOException {
+    Shell.log.debug("Starting DU test --------------------------");
+    exec("createtable t", true);
+    exec("du t", true, "0 [t]");
+    exec("deletetable t -f", true, "Table: [t] has been deleted");
+  }
+
+  @Test
+  public void duPatternTest() throws IOException {
+    Shell.log.debug("Starting DU with pattern test --------------------------");
+    exec("createtable t", true);
+    exec("createtable tt", true);
+    exec("du -p t.*", true, "0 [t, tt]");
+    exec("deletetable t -f", true, "Table: [t] has been deleted");
+    exec("deletetable tt -f", true, "Table: [tt] has been deleted");
+  }
+
+  @Test
+  public void scanDateStringFormatterTest() throws IOException {
+    Shell.log.debug("Starting scan dateStringFormatter test --------------------------");
+    exec("createtable t", true);
+    exec("insert r f q v -ts 0", true);
+    DateFormat dateFormat = new SimpleDateFormat(DateStringFormatter.DATE_FORMAT);
+    String expected = String.format("r f:q [] %s    v", dateFormat.format(new Date(0)));
+    exec("scan -fm org.apache.accumulo.core.util.format.DateStringFormatter -st", true, expected);
+    exec("deletetable t -f", true, "Table: [t] has been deleted");
+  }
+
+  @Test
+  public void commentTest() throws IOException {
+    Shell.log.debug("Starting comment test --------------------------");
+    exec("#", true, "Unknown command", false);
+    exec("# foo", true, "Unknown command", false);
+    exec("- foo", true, "Unknown command", true);
+  }
+
+  @Test
+  public void execFileTest() throws IOException {
+    Shell.log.debug("Starting exec file test --------------------------");
+    shell.config("--fake", "-u", "test", "-p", "secret", "-f", "src/test/resources/shelltest.txt");
+    assertEquals(0, shell.start());
+    assertGoodExit("Unknown command", false);
+  }
+
+  @Test
+  public void setIterTest() throws IOException {
+    Shell.log.debug("Starting setiter test --------------------------");
+    exec("createtable t", true);
+
+    String cmdJustClass = "setiter -class VersioningIterator -p 1";
+    exec(cmdJustClass, false, "java.lang.IllegalArgumentException", false);
+    exec(cmdJustClass, false, "fully qualified package name", true);
+
+    String cmdFullPackage = "setiter -class o.a.a.foo -p 1";
+    exec(cmdFullPackage, false, "java.lang.IllegalArgumentException", false);
+    exec(cmdFullPackage, false, "class not found", true);
+
+    String cmdNoOption = "setiter -class java.lang.String -p 1";
+    exec(cmdNoOption, false, "loaded successfully but does not implement SortedKeyValueIterator", true);
+
+    input.set("\n\n");
+    exec("setiter -scan -class org.apache.accumulo.core.iterators.ColumnFamilyCounter -p 30 -name foo", true);
+    
+    input.set("bar\nname value\n");
+    exec("setiter -scan -class org.apache.accumulo.core.iterators.ColumnFamilyCounter -p 31", true);
+    
+    //TODO can't verify this as config -t fails, functionality verified in ShellServerIT
+    
+    exec("deletetable t -f", true, "Table: [t] has been deleted");
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/ShellUtilTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/ShellUtilTest.java b/shell/src/test/java/org/apache/accumulo/shell/ShellUtilTest.java
new file mode 100644
index 0000000..4e99336
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/ShellUtilTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.apache.accumulo.shell.ShellUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.io.Text;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import com.google.common.collect.ImmutableList;
+
+public class ShellUtilTest {
+
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder(new File(System.getProperty("user.dir") + "/target"));
+
+  // String with 3 lines, with one empty line
+  private static final String FILEDATA = "line1\n\nline2";
+
+  @Test
+  public void testWithoutDecode() throws IOException {
+    File testFile = new File(folder.getRoot(), "testFileNoDecode.txt");
+    FileUtils.writeStringToFile(testFile, FILEDATA);
+    List<Text> output = ShellUtil.scanFile(testFile.getAbsolutePath(), false);
+    assertEquals(ImmutableList.of(new Text("line1"), new Text("line2")), output);
+  }
+
+  @Test
+  public void testWithDecode() throws IOException {
+    File testFile = new File(folder.getRoot(), "testFileWithDecode.txt");
+    FileUtils.writeStringToFile(testFile, FILEDATA);
+    List<Text> output = ShellUtil.scanFile(testFile.getAbsolutePath(), true);
+    assertEquals(
+        ImmutableList.of(new Text(Base64.decodeBase64("line1".getBytes(StandardCharsets.UTF_8))), new Text(Base64.decodeBase64("line2".getBytes(StandardCharsets.UTF_8)))),
+        output);
+  }
+
+  @Test(expected = FileNotFoundException.class)
+  public void testWithMissingFile() throws FileNotFoundException {
+    ShellUtil.scanFile("missingFile.txt", false);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/command/FormatterCommandTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/command/FormatterCommandTest.java b/shell/src/test/java/org/apache/accumulo/shell/command/FormatterCommandTest.java
new file mode 100644
index 0000000..203f08f
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/command/FormatterCommandTest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.command;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.TableExistsException;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.util.format.Formatter;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.mock.MockShell;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Uses the MockShell to test the shell output with Formatters
+ */
+public class FormatterCommandTest {
+  ByteArrayOutputStream out = null;
+  InputStream in = null;
+  
+  @Test
+  public void test() throws IOException, AccumuloException, AccumuloSecurityException, TableExistsException, ClassNotFoundException {
+    // Keep the Shell AUDIT log off the test output
+    Logger.getLogger(Shell.class).setLevel(Level.WARN);
+    
+    final String[] args = new String[] {"--fake", "-u", "root", "-p", ""};
+    
+    final String[] commands = createCommands();
+    
+    in = MockShell.makeCommands(commands);
+    out = new ByteArrayOutputStream();
+    
+    final MockShell shell = new MockShell(in, out);
+    shell.config(args);
+    
+    // Can't call createtable in the shell with MockAccumulo
+    shell.getConnector().tableOperations().create("test");
+    
+    try {
+      shell.start();
+    } catch (Exception e) {
+      Assert.fail("Exception while running commands: " + e.getMessage());
+    }
+    
+    shell.getReader().flush();
+    
+    final String[] output = new String(out.toByteArray()).split("\n\r");
+    
+    boolean formatterOn = false;
+    
+    final String[] expectedDefault = new String[] {"row cf:cq []    1234abcd", "row cf1:cq1 []    9876fedc", "row2 cf:cq []    13579bdf",
+        "row2 cf1:cq []    2468ace"};
+    
+    final String[] expectedFormatted = new String[] {"row cf:cq []    0x31 0x32 0x33 0x34 0x61 0x62 0x63 0x64",
+        "row cf1:cq1 []    0x39 0x38 0x37 0x36 0x66 0x65 0x64 0x63", "row2 cf:cq []    0x31 0x33 0x35 0x37 0x39 0x62 0x64 0x66",
+        "row2 cf1:cq []    0x32 0x34 0x36 0x38 0x61 0x63 0x65"};
+    
+    int outputIndex = 0;
+    while (outputIndex < output.length) {
+      final String line = output[outputIndex];
+      
+      if (line.startsWith("root@mock-instance")) {
+        if (line.contains("formatter")) {
+          formatterOn = true;
+        }
+        
+        outputIndex++;
+      } else if (line.startsWith("row")) {
+        int expectedIndex = 0;
+        String[] comparisonData;
+        
+        // Pick the type of data we expect (formatted or default)
+        if (formatterOn) {
+          comparisonData = expectedFormatted;
+        } else {
+          comparisonData = expectedDefault;
+        }
+        
+        // Ensure each output is what we expected
+        while (expectedIndex + outputIndex < output.length && expectedIndex < expectedFormatted.length) {
+          Assert.assertEquals(comparisonData[expectedIndex].trim(), output[expectedIndex + outputIndex].trim());
+          expectedIndex++;
+        }
+        
+        outputIndex += expectedIndex;
+      }
+    }
+  }
+  
+  private String[] createCommands() {
+    return new String[] {"table test", "insert row cf cq 1234abcd", "insert row cf1 cq1 9876fedc", "insert row2 cf cq 13579bdf", "insert row2 cf1 cq 2468ace",
+        "scan", "formatter -t test -f org.apache.accumulo.core.util.shell.command.FormatterCommandTest$HexFormatter", "scan"};
+  }
+  
+  /**
+   * <p>
+   * Simple <code>Formatter</code> that will convert each character in the Value from decimal to hexadecimal. Will automatically skip over characters in the
+   * value which do not fall within the [0-9,a-f] range.
+   * </p>
+   * 
+   * <p>
+   * Example: <code>'0'</code> will be displayed as <code>'0x30'</code>
+   * </p>
+   */
+  public static class HexFormatter implements Formatter {
+    private Iterator<Entry<Key,Value>> iter = null;
+    private boolean printTs = false;
+    
+    private final static String tab = "\t";
+    private final static String newline = "\n";
+    
+    public HexFormatter() {}
+    
+    @Override
+    public boolean hasNext() {
+      return this.iter.hasNext();
+    }
+    
+    @Override
+    public String next() {
+      final Entry<Key,Value> entry = iter.next();
+      
+      String key;
+      
+      // Observe the timestamps
+      if (printTs) {
+        key = entry.getKey().toString();
+      } else {
+        key = entry.getKey().toStringNoTime();
+      }
+      
+      final Value v = entry.getValue();
+      
+      // Approximate how much space we'll need
+      final StringBuilder sb = new StringBuilder(key.length() + v.getSize() * 5);
+      
+      sb.append(key).append(tab);
+      
+      for (byte b : v.get()) {
+        if ((b >= 48 && b <= 57) || (b >= 97 || b <= 102)) {
+          sb.append(String.format("0x%x ", Integer.valueOf(b)));
+        }
+      }
+      
+      sb.append(newline);
+      
+      return sb.toString();
+    }
+    
+    @Override
+    public void remove() {}
+    
+    @Override
+    public void initialize(final Iterable<Entry<Key,Value>> scanner, final boolean printTimestamps) {
+      this.iter = scanner.iterator();
+      this.printTs = printTimestamps;
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/java/org/apache/accumulo/shell/format/DeleterFormatterTest.java
----------------------------------------------------------------------
diff --git a/shell/src/test/java/org/apache/accumulo/shell/format/DeleterFormatterTest.java b/shell/src/test/java/org/apache/accumulo/shell/format/DeleterFormatterTest.java
new file mode 100644
index 0000000..e5b7394
--- /dev/null
+++ b/shell/src/test/java/org/apache/accumulo/shell/format/DeleterFormatterTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.shell.format;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import jline.UnsupportedTerminal;
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.MutationsRejectedException;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.format.DeleterFormatter;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DeleterFormatterTest {
+  DeleterFormatter formatter;
+  Map<Key,Value> data;
+  BatchWriter writer;
+  BatchWriter exceptionWriter;
+  Shell shellState;
+
+  ByteArrayOutputStream baos;
+  ConsoleReader reader;
+
+  SettableInputStream input;
+
+  class SettableInputStream extends InputStream {
+    ByteArrayInputStream bais;
+
+    @Override
+    public int read() throws IOException {
+      return bais.read();
+    }
+
+    public void set(String in) {
+      bais = new ByteArrayInputStream(in.getBytes(StandardCharsets.UTF_8));
+    }
+  };
+
+  @Before
+  public void setUp() throws IOException, MutationsRejectedException {
+    input = new SettableInputStream();
+    baos = new ByteArrayOutputStream();
+
+    MutationsRejectedException mre = createMock(MutationsRejectedException.class);
+
+    writer = createNiceMock(BatchWriter.class);
+    exceptionWriter = createNiceMock(BatchWriter.class);
+    exceptionWriter.close();
+    expectLastCall().andThrow(mre);
+    exceptionWriter.addMutation(anyObject(Mutation.class));
+    expectLastCall().andThrow(mre);
+
+    shellState = createNiceMock(Shell.class);
+
+    reader = new ConsoleReader(input, baos, new UnsupportedTerminal());
+    expect(shellState.getReader()).andReturn(reader).anyTimes();
+
+    replay(writer, exceptionWriter, shellState);
+
+    data = new TreeMap<Key,Value>();
+    data.put(new Key("r", "cf", "cq"), new Value("value".getBytes(StandardCharsets.UTF_8)));
+  }
+
+  @Test
+  public void testEmpty() {
+    formatter = new DeleterFormatter(writer, Collections.<Key,Value> emptyMap().entrySet(), true, shellState, true);
+    assertFalse(formatter.hasNext());
+  }
+
+  @Test
+  public void testSingle() throws IOException {
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, true);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+
+    verify("[DELETED]", " r ", "cf", "cq", "value");
+  }
+
+  @Test
+  public void testNo() throws IOException {
+    input.set("no\n");
+    data.put(new Key("z"), new Value("v2".getBytes(StandardCharsets.UTF_8)));
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, false);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+
+    verify("[SKIPPED]", " r ", "cf", "cq", "value");
+
+    assertTrue(formatter.hasNext());
+  }
+
+  @Test
+  public void testNoConfirmation() throws IOException {
+    input.set("");
+    data.put(new Key("z"), new Value("v2".getBytes(StandardCharsets.UTF_8)));
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, false);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+
+    verify("[SKIPPED]", " r ", "cf", "cq", "value");
+
+    assertFalse(formatter.hasNext());
+  }
+
+  @Test
+  public void testYes() throws IOException {
+    input.set("y\nyes\n");
+    data.put(new Key("z"), new Value("v2".getBytes(StandardCharsets.UTF_8)));
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, false);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+    verify("[DELETED]", " r ", "cf", "cq", "value");
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+    verify("[DELETED]", " z ", "v2");
+  }
+
+  @Test
+  public void testMutationException() {
+    formatter = new DeleterFormatter(exceptionWriter, data.entrySet(), true, shellState, true);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+    assertFalse(formatter.hasNext());
+  }
+
+  private void verify(String... chunks) throws IOException {
+    reader.flush();
+
+    String output = baos.toString();
+    for (String chunk : chunks) {
+      assertTrue(output.contains(chunk));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/shell/src/test/resources/log4j.properties b/shell/src/test/resources/log4j.properties
new file mode 100644
index 0000000..9f968f8
--- /dev/null
+++ b/shell/src/test/resources/log4j.properties
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+log4j.rootLogger=INFO, CA
+log4j.appender.CA=org.apache.log4j.ConsoleAppender
+log4j.appender.CA.layout=org.apache.log4j.PatternLayout
+log4j.appender.CA.layout.ConversionPattern=[%t] %-5p %c %x - %m%n
+
+log4j.logger.org.apache.accumulo.core.iterators.system.VisibilityFilter=FATAL
+log4j.logger.org.apache.accumulo.core.iterators.user.TransformingIteratorTest$IllegalVisCompactionKeyTransformingIterator=FATAL
+log4j.logger.org.apache.accumulo.core.iterators.user.TransformingIteratorTest$IllegalVisKeyTransformingIterator=FATAL
+log4j.logger.org.apache.commons.vfs2.impl.DefaultFileSystemManager=WARN
+log4j.logger.org.apache.hadoop.mapred=ERROR
+log4j.logger.org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter=ERROR
+log4j.logger.org.apache.hadoop.util.ProcessTree=ERROR
+log4j.logger.org.apache.accumulo.core.util.format=FATAL

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/shell/src/test/resources/shelltest.txt
----------------------------------------------------------------------
diff --git a/shell/src/test/resources/shelltest.txt b/shell/src/test/resources/shelltest.txt
new file mode 100644
index 0000000..19b6f61
--- /dev/null
+++ b/shell/src/test/resources/shelltest.txt
@@ -0,0 +1,16 @@
+# Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+exit
+foo

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/start/src/main/java/org/apache/accumulo/start/Main.java
----------------------------------------------------------------------
diff --git a/start/src/main/java/org/apache/accumulo/start/Main.java b/start/src/main/java/org/apache/accumulo/start/Main.java
index 3c7da95..f34cd30 100644
--- a/start/src/main/java/org/apache/accumulo/start/Main.java
+++ b/start/src/main/java/org/apache/accumulo/start/Main.java
@@ -54,7 +54,7 @@ public class Main {
       } else if (args[0].equals("tserver")) {
         runTMP = cl.loadClass("org.apache.accumulo.tserver.TabletServer");
       } else if (args[0].equals("shell")) {
-        runTMP = cl.loadClass("org.apache.accumulo.core.util.shell.Shell");
+        runTMP = cl.loadClass("org.apache.accumulo.shell.Shell");
       } else if (args[0].equals("init")) {
         runTMP = cl.loadClass("org.apache.accumulo.server.init.Initialize");
       } else if (args[0].equals("admin")) {

http://git-wip-us.apache.org/repos/asf/accumulo/blob/066043d4/test/src/test/java/org/apache/accumulo/test/ShellServerIT.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/test/ShellServerIT.java b/test/src/test/java/org/apache/accumulo/test/ShellServerIT.java
index b3d44e3..bff8c3a 100644
--- a/test/src/test/java/org/apache/accumulo/test/ShellServerIT.java
+++ b/test/src/test/java/org/apache/accumulo/test/ShellServerIT.java
@@ -56,7 +56,7 @@ import org.apache.accumulo.core.metadata.MetadataTable;
 import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.util.UtilWaitThread;
-import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.shell.Shell;
 import org.apache.accumulo.test.functional.FunctionalTestUtils;
 import org.apache.accumulo.test.functional.SimpleMacIT;
 import org.apache.accumulo.test.functional.SlowIterator;