shell plugins give out user friendly type error
diff --git a/changelogs/fragments/shell_env_typeerror.yml b/changelogs/fragments/shell_env_typeerror.yml
new file mode 100644
index 00000000000000..105eefeb72b950
--- /dev/null
+++ b/changelogs/fragments/shell_env_typeerror.yml
@@ -0,0 +1,2 @@
+bugfixes:
+  - shell plugins now give a more user friendly error when fed the wrong type of data.
diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py
index 513ddb6fe8d179..d5db261f680dca 100644
--- a/lib/ansible/plugins/shell/__init__.py
+++ b/lib/ansible/plugins/shell/__init__.py
@@ -25,8 +25,9 @@
 import time
 
 from ansible.errors import AnsibleError
-from ansible.module_utils.six import text_type
 from ansible.module_utils._text import to_native
+from ansible.module_utils.six import text_type, string_types
+from ansible.module_utils.common._collections_compat import Mapping, Sequence
 from ansible.plugins import AnsiblePlugin
 
 _USER_HOME_PATH_RE = re.compile(r'^~[_.A-Za-z0-9][-_.A-Za-z0-9]*$')
@@ -60,12 +61,16 @@ def set_options(self, task_keys=None, var_options=None, direct=None):
         super(ShellBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
 
         # set env if needed, deal with environment's 'dual nature' list of dicts or dict
+        # TODO: config system should already resolve this so we should be able to just iterate over dicts
         env = self.get_option('environment')
-        if isinstance(env, list):
-            for env_dict in env:
-                self.env.update(env_dict)
-        else:
-            self.env.update(env)
+        if isinstance(env, string_types):
+            raise AnsibleError('The "envirionment" keyword takes a list of dictionaries or a dictionary, not a string')
+        if not isinstance(env, Sequence):
+            env = [env]
+        for env_dict in env:
+            if not isinstance(env_dict, Mapping):
+                raise AnsibleError('The "envirionment" keyword takes a list of dictionaries (or single dictionary), but got a "%s" instead' % type(env_dict))
+            self.env.update(env_dict)
 
         # We can remove the try: except in the future when we make ShellBase a proper subset of
         # *all* shells.  Right now powershell and third party shells which do not use the

currently they get 
```
 "msg": "Unexpected failure during module execution.",
    "exception": "Traceback (most recent call last):\n  File \"/usr/lib/python3.6/site-packages/ansible/executor/task_executor.py\", line 147, in run\n    res = self._execute()\n  File \"/usr/lib/python3.6/site-packages/ansible/executor/task_executor.py\", line 626, in _execute\n    plugin_vars = self._set_connection_options(cvars, templar)\n  File \"/usr/lib/python3.6/site-packages/ansible/executor/task_executor.py\", line 1026, in _set_connection_options\n    varnames.extend(self._set_plugin_options('shell', variables, templar, task_keys))\n  File \"/usr/lib/python3.6/site-packages/ansible/executor/task_executor.py\", line 990, in _set_plugin_options\n    plugin.set_options(task_keys=task_keys, var_options=options)\n  File \"/usr/lib/python3.6/site-packages/ansible/plugins/shell/__init__.py\", line 66, in set_options\n    self.env.update(env_dict)\nValueError: dictionary update sequence element #0 has length 1; 2 is required\n",
```

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Bugfix Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
plugins/shell
