diff --git a/docs/source/docker.rst b/docs/source/docker.rst
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -105,11 +105,11 @@
         - docker
 
     actions:
-      - bash:
-          command: bash test.sh ${item} ${workdir}/test_output
+      - shell:
+          - bash test.sh ${item} ${workdir}/test_output
 
-      - bash:
-          command: cat ${workdir}/test_output
+      - shell:
+          - cat ${workdir}/test_output
         export: test_results
 
       - resultsdb:
@@ -160,14 +160,10 @@
         - python2-pytest
 
     actions:
-      - bash:
-          command: systemctl start docker
-
-      - bash:
-          command: docker pull ${item}
-
-      - bash:
-          command: py.test dockertest.py --junitxml=${artifactsdir}/output.xml
+      - shell:
+          - systemctl start docker
+          - docker pull ${item}
+          - py.test dockertest.py --junitxml=${artifactsdir}/output.xml
 
       - xunit:
           file: ${artifactsdir}/output.xml
diff --git a/docs/source/taskyaml.rst b/docs/source/taskyaml.rst
--- a/docs/source/taskyaml.rst
+++ b/docs/source/taskyaml.rst
@@ -287,6 +287,11 @@
 Allows the execution of a python script as part of task execution.
 Offers several methods of executing python code for a task.
 
+shell
+-----
+
+Allows the execution of a shell command(s) in subprocess call.
+
 resultsdb
 ---------
 
diff --git a/libtaskotron/directives/bash_directive.py b/libtaskotron/directives/bash_directive.py
deleted file mode 100644
--- a/libtaskotron/directives/bash_directive.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2009-2014, Red Hat, Inc.
-# License: GPL-2.0+ <http://spdx.org/licenses/GPL-2.0+>
-# See the LICENSE file for more details on Licensing
-
-from __future__ import absolute_import
-
-import os
-import subprocess
-import pipes
-
-from libtaskotron.directives import BaseDirective
-from libtaskotron.exceptions import TaskotronDirectiveError
-from libtaskotron.logger import log
-
-DOCUMENTATION = """
-module: bash_directive
-short_description: execute a bash command
-description: |
-  Execute a bash command. You can also execute a bash script using ``sh file`` as command.
-  The code is run in subprocess.
-parameters:
-  command:
-    required: true
-    description: bash command to be executed
-    type: str
-returns: |
-  The stdout of executed bash command.
-
-
-  If this command is supposed to be the main check of the task, it is supposed to provide text
-  output in a :ref:`resultyaml-format`. This format is then used in the
-  :ref:`resultsdb directive <resultsdb_directive>`.
-
-raises: |
-  * :class:`.TaskotronDirectiveError`: if ``command`` was not provided or executed command returned
-    non-zero returncode.
-version_added: 0.4.12
-"""
-
-EXAMPLES = """
-
-This executes the shell command in subprocess::
-
-  - name: pull docker image
-    bash:
-        command: docker pull httpd
-
-You can also run bash script::
-
-  - name: execute bash script
-    bash:
-        command: sh test.sh
-    export: test_output
-
-Bash directive can work as file loader::
-
-  - name: load file to formula variable
-    bash:
-        command: cat some_file
-    export: loaded_file
-
-"""
-
-directive_class = 'BashDirective'
-
-
-class BashDirective(BaseDirective):
-    def process(self, params, arg_data):
-        basedir = os.path.dirname(os.path.abspath(arg_data['task']))
-
-        try:
-            cmd = [pipes.quote(c) for c in params['command'].split()]
-        except KeyError as e:
-            raise TaskotronDirectiveError('No command provided.')
-
-        log.info("Executing Bash: %s", cmd)
-
-        try:
-            output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=basedir)
-        except subprocess.CalledProcessError as e:
-            raise TaskotronDirectiveError(e)
-        except OSError as e:
-            raise TaskotronDirectiveError(e)
-
-        log.debug(output)
-
-        return output
diff --git a/libtaskotron/directives/shell_directive.py b/libtaskotron/directives/shell_directive.py
new file mode 100644
--- /dev/null
+++ b/libtaskotron/directives/shell_directive.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2016, Red Hat, Inc.
+# License: GPL-2.0+ <http://spdx.org/licenses/GPL-2.0+>
+# See the LICENSE file for more details on Licensing
+
+from __future__ import absolute_import
+
+import os
+import subprocess
+
+from libtaskotron.directives import BaseDirective
+from libtaskotron.exceptions import TaskotronDirectiveError
+from libtaskotron.logger import log
+
+DOCUMENTATION = """
+module: shell_directive
+short_description: execute one ore more shell commands or direct processes
+
+description: |
+  Execute a command in shell or as a direct process. There are no parameters for this directive,
+  it uses a slightly different format:
+
+  - Provide a list of commands directly under the directive name, each item is one command.
+  - If you use a string, it will be executed in the default shell (including all shell expansions).
+  - If you use a list of strings, it will be executed as a direct process (no shell expansions
+    apply, which also means you don't need to worry about escaping special characters).
+  - If start the string with ``ignorereturn:`` key and create a list sub-section under it, all
+    those commands will ignore their exit code and not raise an error.
+
+  See the example section for a better idea.
+
+  This directive is intended for basic simple scripting or running commands. If you need anything
+  more advanced, it's recommended to create a standalone script (bash, perl, etc) and execute it
+  instead.
+parameters:
+  directly_list_of_cmds:
+    required: true
+    description: shell command(s) to be executed
+    type: list
+returns: |
+  The stdout of executed shell command, in case of multiple commands, output of the last one is
+  returned.
+
+raises: |
+  * :class:`.TaskotronDirectiveError`: when parameter of shell directive is not a list, if command
+    to be executed does not exist or if command returned non-zero returncode (returncode can be
+    ignored, see examples).
+
+version_added: 0.4.15
+"""
+
+EXAMPLES = """
+This executes the shell command in subprocess::
+
+  - name: pull docker image
+    shell:
+        - docker pull httpd
+
+You can also execute multiple commands::
+
+  - name: do some stuff
+    shell:
+        - docker pull httpd
+        - docker run -d httpd
+        - docker ps
+
+Shell directive will raise if command (or one of them if multiple) returns non-zero returncode,
+you can bypass this by using ``ignorereturn``::
+
+  - name: do more stuff
+    shell:
+        - ignorereturn:
+            - docker pull thiswontwork!1!1!!
+        - echo 'No prob, continuing'
+
+You can run a process directly, without shell processing, which means you don't need to worry
+about escaping special characters or e.g. spaces in file names::
+
+  - name: execute command directly, without shell
+    shell:
+        - [echo, "don't worry about code injection"]
+        - [rm, a file with # funny & characters]
+        - [mkdir, ${variable_with_unknown_contents}]
+
+You can also run bash script::
+
+  - name: execute bash script
+    shell:
+        - bash test.sh
+    export: test_output
+
+Shell directive can be used to copy file contents into a variable. With this, you can later use
+``${loaded_file}`` in other directives::
+
+  - name: load file to formula variable
+    shell:
+        - cat some_file
+    export: loaded_file
+"""
+
+directive_class = 'ShellDirective'
+
+
+def check_output(cmd, **kwargs):
+    """Custom check_output that prints output in real-time"""
+
+    proc = subprocess.Popen(cmd, **kwargs)
+    output = []
+    for line in iter(proc.stdout.readline, ''):
+        print line,
+        output.append(line)
+
+    retcode = proc.poll()
+    if retcode:
+        raise subprocess.CalledProcessError(retcode, cmd, ''.join(output))
+
+    return ''.join(output)
+
+
+class ShellDirective(BaseDirective):
+    def process(self, params, arg_data, ignorereturn=False):
+
+        if not isinstance(params, list):
+            log.debug('Shell directive received invalid input: %r', params)
+            raise TaskotronDirectiveError("Shell directive must contain a list of commands")
+
+        basedir = os.path.dirname(os.path.abspath(arg_data['task']))
+        output = ""
+
+        for cmd in params:
+            if isinstance(cmd, dict) and 'ignorereturn' in cmd:
+                output = self.process(cmd['ignorereturn'], arg_data, ignorereturn=True)
+                continue
+
+            try:
+                if isinstance(cmd, list):
+                    log.info("Executing process: %s", cmd)
+                    output = check_output(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                                          bufsize=1, close_fds=True, cwd=basedir)
+                else:
+                    log.info("Executing shell: %s", cmd)
+                    output = check_output(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                                          bufsize=1, close_fds=True, cwd=basedir, shell=True)
+
+            except subprocess.CalledProcessError as e:
+                if not ignorereturn:
+                    raise TaskotronDirectiveError(e)
+            except OSError as e:
+                raise TaskotronDirectiveError(e)
+
+        return output
diff --git a/testing/functest_shell_directive.py b/testing/functest_shell_directive.py
new file mode 100644
--- /dev/null
+++ b/testing/functest_shell_directive.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2016, Red Hat, Inc.
+# License: GPL-2.0+ <http://spdx.org/licenses/GPL-2.0+>
+# See the LICENSE file for more details on Licensing
+
+from libtaskotron.directives import shell_directive
+from libtaskotron.exceptions import TaskotronDirectiveError
+
+import os
+import pytest
+
+OUTPUT = "Piko a Rybi prsty"
+PATH = os.path.realpath(__file__)
+
+
+class TestCheckCommand(object):
+
+    def test_existing_command(self):
+        test_params = ['echo -n %s' % OUTPUT]
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        output = test_directive.process(test_params, test_arg_data)
+
+        assert output == OUTPUT
+
+    def test_shell_false(self):
+        test_params = [['echo', '-n', OUTPUT]]
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        output = test_directive.process(test_params, test_arg_data)
+
+        assert output == OUTPUT
+
+    def test_non_existing_command(self):
+        test_params = ['if_this_exists_i_will_go_to_hell_and_i_will_take_kparal_with_me']
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        with pytest.raises(TaskotronDirectiveError):
+            test_directive.process(test_params, test_arg_data)
+
+    def test_zero_returncode(self):
+        test_params = ['true']
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        output = test_directive.process(test_params, test_arg_data)
+
+        assert output == ""
+
+    def test_nonzero_returncode(self):
+        test_params = ['false']
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        with pytest.raises(TaskotronDirectiveError):
+            test_directive.process(test_params, test_arg_data)
+
+    def test_multi_command(self):
+        test_params = ['true',
+                       'echo -n %s' % OUTPUT[::-1],
+                       'echo -n %s' % OUTPUT]
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        output = test_directive.process(test_params, test_arg_data)
+
+        assert output == OUTPUT
+
+    def test_multi_command_error(self):
+        test_params = ['true',
+                       'echo -n %s' % OUTPUT[::-1],
+                       'false',
+                       'echo -n %s' % OUTPUT]
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        with pytest.raises(TaskotronDirectiveError):
+            test_directive.process(test_params, test_arg_data)
+
+    def test_multi_command_ignore_error(self):
+        test_params = ['true',
+                       'echo -n %s' % OUTPUT[::-1],
+                       {'ignorereturn': ['false']},
+                       'echo -n %s' % OUTPUT]
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        output = test_directive.process(test_params, test_arg_data)
+
+        assert output == OUTPUT
diff --git a/testing/test_bash_directive.py b/testing/test_bash_directive.py
deleted file mode 100644
--- a/testing/test_bash_directive.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2009-2014, Red Hat, Inc.
-# License: GPL-2.0+ <http://spdx.org/licenses/GPL-2.0+>
-# See the LICENSE file for more details on Licensing
-
-from libtaskotron.directives import bash_directive
-from libtaskotron.exceptions import TaskotronDirectiveError
-from dingus import Dingus
-import os
-import pytest
-import subprocess
-
-OUTPUT = "Piko a Rybi prsty"
-PATH = os.path.realpath(__file__)
-
-
-class TestCheckCommand(object):
-    def test_command_present(self, monkeypatch):
-        test_params = {'command': 'cmd.exe'}
-        test_arg_data = {'task': PATH}
-        test_directive = bash_directive.BashDirective()
-
-        stub_check_output = Dingus()
-        stub_check_output.return_value = OUTPUT
-
-        monkeypatch.setattr(subprocess, 'check_output', stub_check_output)
-
-        output = test_directive.process(test_params, test_arg_data)
-
-        assert output == OUTPUT
-
-    def test_command_not_present(self, monkeypatch):
-        test_params = {'commond': 'cmd.exe'}
-        test_arg_data = {'task': PATH}
-        test_directive = bash_directive.BashDirective()
-
-        monkeypatch.setattr(subprocess, 'check_output', Dingus())
-
-        with pytest.raises(TaskotronDirectiveError):
-            test_directive.process(test_params, test_arg_data)
-
-    def test_existing_command(self):
-        test_params = {'command': 'echo -n %s' % OUTPUT}
-        test_arg_data = {'task': PATH}
-        test_directive = bash_directive.BashDirective()
-
-        output = test_directive.process(test_params, test_arg_data)
-
-        assert output == OUTPUT
-
-    def test_non_existing_command(self):
-        test_params = {'command': 'ocho -n %s' % OUTPUT}
-        test_arg_data = {'task': PATH}
-        test_directive = bash_directive.BashDirective()
-
-        with pytest.raises(TaskotronDirectiveError):
-            test_directive.process(test_params, test_arg_data)
-
-    def test_zero_returncode(self):
-        test_params = {'command': 'true'}
-        test_arg_data = {'task': PATH}
-        test_directive = bash_directive.BashDirective()
-
-        output = test_directive.process(test_params, test_arg_data)
-
-        assert output == ""
-
-    def test_nonzero_returncode(self):
-        test_params = {'command': 'false'}
-        test_arg_data = {'task': PATH}
-        test_directive = bash_directive.BashDirective()
-
-        with pytest.raises(TaskotronDirectiveError):
-            test_directive.process(test_params, test_arg_data)
diff --git a/testing/test_shell_directive.py b/testing/test_shell_directive.py
new file mode 100644
--- /dev/null
+++ b/testing/test_shell_directive.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright 2009-2016, Red Hat, Inc.
+# License: GPL-2.0+ <http://spdx.org/licenses/GPL-2.0+>
+# See the LICENSE file for more details on Licensing
+
+from libtaskotron.directives import shell_directive
+from libtaskotron.exceptions import TaskotronDirectiveError
+
+import os
+import pytest
+import subprocess
+import mock
+
+OUTPUT = "Piko a Rybi prsty"
+PATH = os.path.realpath(__file__)
+
+
+class TestCheckCommand(object):
+    def test_a_list(self, monkeypatch):
+        test_params = ['cmd.exe']
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        stub_check_output = mock.Mock(return_value=OUTPUT)
+        monkeypatch.setattr(shell_directive, 'check_output', stub_check_output)
+
+        output = test_directive.process(test_params, test_arg_data)
+
+        assert output == OUTPUT
+
+    def test_not_a_list(self, monkeypatch):
+        test_params = {'a dict': 'instead of a list'}
+        test_arg_data = {'task': PATH}
+        test_directive = shell_directive.ShellDirective()
+
+        monkeypatch.setattr(subprocess, 'check_output', mock.Mock())
+
+        with pytest.raises(TaskotronDirectiveError):
+            test_directive.process(test_params, test_arg_data)

