diff --git a/README.rst b/README.rst
index d60fbe0f7b0957cf4c1c836ef1f1e4ffac7eda2b..40472bb92e943670273462e1a26754c2b3510a77 100644
--- a/README.rst
+++ b/README.rst
@@ -81,6 +81,8 @@ Jinja filters
 
 The following Jinja filters were added:
 
+- :code:`combine`: Merges 2 dictionaries with the 2nd overriding the 1st.
+  Returns the result.
 - :code:`to_yaml`: Convert to yaml (requires the :code:`yaml` package
   specifier).
 - :code:`from_yaml`: Convert from yaml (requires the :code:`yaml` package
@@ -94,8 +96,8 @@ The following Jinja filters were added:
 - :code:`jmespath`: Queries data using the `JMESPath <http://jmespath.org/>`_
   query language (requires the :code:`jmespath` package specifier).
 - :code:`run`: Runs a command and returns the stdout, stderr and returncode
-  using `run
-  <https://docs.python.org/3.6/library/subprocess.html?highlight=popen#subprocess.run>`_.
+  using run_. This filter is replaced with the :code:`run` function and will
+  be removed in the 0.10 release.
 - :code:`ipaddress`: Returns an IPAddress object from the netaddr_ library
   (requires the :code:`netaddr` package specifier).
 - :code:`ipnetwork`: Returns an IPNetwork object from the netaddr_ library
@@ -104,10 +106,21 @@ The following Jinja filters were added:
   (requires the :code:`netaddr` package specifier).
 - :code:`ipglob`: Returns an IPGlob object from the netaddr_ library (requires
   the :code:`netaddr` package specifier).
+- :code:`ipset`: Returns an IPSet object from the netaddr_ library (requires
+  the :code:`netaddr` package specifier).
 
 Example usage can be seen in :code:`tests` and for specific filters in the
 docstrings in :code:`template/filters.py`.
 
+Jinja functions
+---------------
+
+- :code:`run`: Runs a command and returns the stdout, stderr and returncode
+  using run_. This function replaces the :code:`run` filter.
+
+Example usage can be seen in :code:`tests` and for specific filters in the
+docstrings in :code:`template/functions.py`.
+
 Testing
 -------
 
@@ -152,3 +165,4 @@ at: https://git.shore.co.il/nimrod/.
 
 .. _netaddr: https://netaddr.readthedocs.io/
 .. _Pipenv: https://docs.pipenv.org
+.. _run: https://docs.python.org/3.6/library/subprocess.html?highlight=popen#subprocess.run
diff --git a/template/__init__.py b/template/__init__.py
index c218023718a892bdd8138e2a74202c23df32fedc..fee78d16ae1f123e35dda44394e50f2e4c9609a0 100644
--- a/template/__init__.py
+++ b/template/__init__.py
@@ -13,6 +13,7 @@ from os import environ
 import sys
 import argparse
 import template.filters
+import template.functions
 
 # I ignore import errors here and fail on them later in the main function so
 # the module can be imported by the setup.py with jinja missing so the
@@ -31,9 +32,18 @@ def render(template_string):
     env = Environment(autoescape=True)
     # Add all functions in template.filters as Jinja filters.
     # pylint: disable=invalid-name
-    for tf in filter(lambda x: not x.startswith("_"), dir(template.filters)):
+    for tf in filter(
+        lambda x: callable(getattr(template.filters, x))
+        and not x.startswith("_"),
+        dir(template.filters),
+    ):
         env.filters[tf] = getattr(template.filters, tf)
-    t = env.from_string(template_string)
+    functions = {
+        x: getattr(template.functions, x)
+        for x in dir(template.functions)
+        if callable(getattr(template.functions, x)) and not x.startswith("_")
+    }
+    t = env.from_string(template_string, globals=functions)
     return t.render(environ)
 
 
diff --git a/template/filters.py b/template/filters.py
index 085cf6fec5188e44b6074c83593fb56f02116134..4fb8c8b01ec182519af10ce0e290a749c1c1fe0d 100644
--- a/template/filters.py
+++ b/template/filters.py
@@ -10,6 +10,8 @@ from __future__ import (
     unicode_literals,
 )  # pylint: disable=duplicate-code
 
+from template.functions import run  # noqa: F401 pylint: disable=unused-import
+
 
 def to_yaml(value):
     r"""
@@ -145,36 +147,6 @@ def jmespath(value, query):
     return jp.search(query, value)
 
 
-def run(*argv, **kwargs):
-    """
-    Runs a command and returns the stdout, stderr and returncode
-    using `run
-    <https://docs.python.org/3.5/library/subprocess.html?highlight=popen#subprocess.run>`_.
-    >>> run('ls')["returncode"] == 0
-    True
-    >>> 'SHELL' not in run('echo $SHELL', shell=True)['stdout']
-    True
-    >>> run(['ls', 'foo'])['returncode'] > 0
-    True
-    """
-    import sys
-
-    if sys.version_info[0] < 3:  # nosec
-        import subprocess32 as subprocess
-    else:
-        import subprocess  # nosemgrep: rules.bandit.B40
-
-    defaults = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE}
-    defaults.update(kwargs)
-    proc = subprocess.run(  # nosec, pylint: disable=subprocess-run-check
-        *argv, **defaults
-    ).__dict__
-    if "text" not in kwargs or kwargs["text"]:
-        proc["stdout"] = proc["stdout"].decode()
-        proc["stderr"] = proc["stderr"].decode()
-    return proc
-
-
 def ipaddress(addr, version=None, flags=0):
     """
     Returns an IPAddress object from the netaddr library.
diff --git a/template/functions.py b/template/functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..c219b2e1e9258211a6915dbb796963b259d508fe
--- /dev/null
+++ b/template/functions.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+"""Filters for the template CLI."""
+# pylint: disable=import-error, import-outside-toplevel
+
+
+from __future__ import (
+    absolute_import,
+    division,
+    print_function,
+    unicode_literals,
+)  # pylint: disable=duplicate-code
+
+
+def run(*argv, **kwargs):
+    """
+    Runs a command and returns the stdout, stderr and returncode
+    using `run
+    <https://docs.python.org/3.5/library/subprocess.html?highlight=popen#subprocess.run>`_.
+    >>> run('ls')["returncode"] == 0
+    True
+    >>> 'SHELL' not in run('echo $SHELL', shell=True)['stdout']
+    True
+    >>> run(['ls', 'foo'])['returncode'] > 0
+    True
+    """
+    import sys
+
+    if sys.version_info[0] < 3:  # nosec
+        import subprocess32 as subprocess
+    else:
+        import subprocess  # nosemgrep: rules.bandit.B40
+
+    defaults = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE}
+    defaults.update(kwargs)
+    proc = subprocess.run(  # nosec, pylint: disable=subprocess-run-check
+        *argv, **defaults
+    ).__dict__
+    if "text" not in kwargs or kwargs["text"]:
+        proc["stdout"] = proc["stdout"].decode()
+        proc["stderr"] = proc["stderr"].decode()
+    return proc