diff --git a/yt_dlp/options.py b/yt_dlp/options.py
index 55c2b9aa3e..38c99bbcb2 100644
--- a/yt_dlp/options.py
+++ b/yt_dlp/options.py
@@ -1277,17 +1277,31 @@ def parseOpts(overrideArguments=None):
         dest='ffmpeg_location',
         help='Location of the ffmpeg binary; either the path to the binary or its containing directory')
     postproc.add_option(
-        '--exec',
-        metavar='CMD', dest='exec_cmd',
+        '--exec', metavar='CMD',
+        action='callback', dest='exec_cmd', default=[], type='str',
+        callback=_list_from_options_callback, callback_kwargs={'delim': None},
         help=(
             'Execute a command on the file after downloading and post-processing. '
             'Similar syntax to the output template can be used to pass any field as arguments to the command. '
             'An additional field "filepath" that contains the final path of the downloaded file is also available. '
-            'If no fields are passed, %(filepath)q is appended to the end of the command'))
+            'If no fields are passed, %(filepath)q is appended to the end of the command. '
+            'This option can be used multiple times'))
     postproc.add_option(
-        '--exec-before-download',
-        metavar='CMD', dest='exec_before_dl_cmd',
-        help='Execute a command before the actual download. The syntax is the same as --exec but "filepath" is not available')
+        '--no-exec',
+        action='store_const', dest='exec_cmd', const=[],
+        help='Remove any previously defined --exec')
+    postproc.add_option(
+        '--exec-before-download', metavar='CMD',
+        action='callback', dest='exec_before_dl_cmd', default=[], type='str',
+        callback=_list_from_options_callback, callback_kwargs={'delim': None},
+        help=(
+            'Execute a command before the actual download. '
+            'The syntax is the same as --exec but "filepath" is not available. '
+            'This option can be used multiple times'))
+    postproc.add_option(
+        '--no-exec-before-download',
+        action='store_const', dest='exec_before_dl_cmd', const=[],
+        help='Remove any previously defined --exec-before-download')
     postproc.add_option(
         '--convert-subs', '--convert-sub', '--convert-subtitles',
         metavar='FORMAT', dest='convertsubtitles', default=None,
diff --git a/yt_dlp/postprocessor/execafterdownload.py b/yt_dlp/postprocessor/execafterdownload.py
index ce77c6e807..182b900d91 100644
--- a/yt_dlp/postprocessor/execafterdownload.py
+++ b/yt_dlp/postprocessor/execafterdownload.py
@@ -7,6 +7,7 @@ from ..compat import compat_shlex_quote
 from ..utils import (
     encodeArgument,
     PostProcessingError,
+    variadic,
 )
 
 
@@ -14,7 +15,7 @@ class ExecAfterDownloadPP(PostProcessor):
 
     def __init__(self, downloader, exec_cmd):
         super(ExecAfterDownloadPP, self).__init__(downloader)
-        self.exec_cmd = exec_cmd
+        self.exec_cmd = variadic(exec_cmd)
 
     @classmethod
     def pp_key(cls):
@@ -32,9 +33,10 @@ class ExecAfterDownloadPP(PostProcessor):
             info.get('filepath') or info['_filename']))
 
     def run(self, info):
-        cmd = self.parse_cmd(self.exec_cmd, info)
-        self.to_screen('Executing command: %s' % cmd)
-        retCode = subprocess.call(encodeArgument(cmd), shell=True)
-        if retCode != 0:
-            raise PostProcessingError('Command returned error code %d' % retCode)
+        for tmpl in self.exec_cmd:
+            cmd = self.parse_cmd(tmpl, info)
+            self.to_screen('Executing command: %s' % cmd)
+            retCode = subprocess.call(encodeArgument(cmd), shell=True)
+            if retCode != 0:
+                raise PostProcessingError('Command returned error code %d' % retCode)
         return [], info