diff --git a/src/ahriman/models/shell_template.py b/src/ahriman/models/shell_template.py
new file mode 100644
index 00000000..28547ee0
--- /dev/null
+++ b/src/ahriman/models/shell_template.py
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2021-2024 ahriman team.
+#
+# This file is part of ahriman
+# (see https://github.com/arcan1s/ahriman).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import re
+
+from collections.abc import Mapping
+from string import Template
+
+
+class ShellTemplate(Template):
+ """
+ extension to the default :class:`Template` class, which also adds additional tokens to braced regex and enables
+ bash expansion
+
+ Attributes:
+ braceidpattern(str): regular expression to match every character except for closing bracket
+ """
+
+ braceidpattern = r"(?a:[_a-z0-9][^}]*)"
+
+ _REMOVAL_FRONT = re.compile(r"^\w+#(?P.+)$")
+ _REMOVAL_BACK = re.compile(r"^\w+%(?P.+)$")
+ _REPLACE = re.compile(r"^\w+/(?P.+)/(?P.+)$")
+
+ def _remove_front(self, raw: str, remove: str) -> str:
+ if remove.startswith("#"):
+
+ def shell_substitute(self, mapping: Mapping[str, str]) -> str:
+ """
+ this method behaves the same as :func:`safe_substitute`, however also expands bash string operations
+
+ Args:
+ mapping(Mapping[str, str]): key-value dictionary of variables
+
+ Returns:
+ str: string with replaced values
+ """
+ return self.safe_substitute(mapping)