diff --git a/trac/db/mysql_backend.py b/trac/db/mysql_backend.py
--- a/trac/db/mysql_backend.py
+++ b/trac/db/mysql_backend.py
@@ -15,14 +15,13 @@
 # history and logs, available at http://trac.edgewall.org/log/.
 
 import re, sys, os, time
+from subprocess import call
 
 from trac.core import *
 from trac.config import Option
 from trac.db.api import IDatabaseConnector, _parse_db_str
 from trac.db.util import ConnectionWrapper
-from trac.util import get_pkginfo
-from trac.util.translation import _
-from subprocess import Popen, PIPE
+from trac.util import Null, get_pkginfo
 from trac.util.compat import close_fds
 
 _like_escape_re = re.compile(r'([/_%])')
@@ -59,7 +58,7 @@
 
     implements(IDatabaseConnector)
 
-    dump_bin = Option('trac', 'mysqldump_bin', 'mysqldump',
+    mysqldump_path = Option('trac', 'mysqldump_path', 'mysqldump',
         """Location of mysqldump for MySQL database backups""")
 
     def __init__(self):
@@ -148,40 +147,28 @@
                   '_'.join(index.columns), table.name,
                   self._collist(table, index.columns))
 
-
     def backup(self, dest_file):
-        # msyqldump -n schemaname dbname | gzip > filename.gz
         db_url = self.env.config.get('trac', 'database')
         scheme, db_prop = _parse_db_str(db_url)
         db_name = os.path.basename(db_prop['path'])
-        args = [self.dump_bin, 
+        args = [self.mysqldump_path, 
                 '-u%s' % db_prop['user'],
                 '-h%s' % db_prop['host']]
         if db_prop['port']:
             args.append('-P%s' % str(db_prop['port']))
         args.append(db_name)
         
-        args.extend(['>', dest_file])
-        if sys.platform == 'win':
-            # XXX TODO verify on windows
-            args = ['cmd', '/c', ' '.join(args)]
-        else:
-            args = ['bash', '-c', ' '.join(args)]
-        
         environ = os.environ.copy()
         environ['MYSQL_PWD'] = db_prop['password']
-        #print >> sys.stderr, "backup command %r" % (args,)
-        #print >> sys.stderr, "backup props %r" % (db_prop,)
-        #print >> sys.stderr, "backup to %s" % dest_file
-        p = Popen(args, env=environ, shell=False, bufsize=0, stdin=None,
-                  stdout=PIPE, stderr=PIPE, close_fds=close_fds)
-        err = p.wait()
-        if err:
-            raise TracError("Backup attempt exited with error code %s." % err)
-        p.stdout.close()
-        p.stderr.close()
-        if not os.path.exists(dest_file):
-            raise TracError("Backup attempt failed")
+        out = open(dest_file, "wb")
+        try:
+            ret = call(args, env=environ, shell=False, bufsize=0, stdin=None,
+                       stdout=out, stderr=Null(), close_fds=close_fds)
+        finally:
+            out.close()
+        if ret != 0:
+            raise TracError("Backup attempt failed "
+                            "(mysqldump return code: %d)" % ret)
         return dest_file
 
 class MySQLConnection(ConnectionWrapper):
diff --git a/trac/db/postgres_backend.py b/trac/db/postgres_backend.py
--- a/trac/db/postgres_backend.py
+++ b/trac/db/postgres_backend.py
@@ -15,13 +15,13 @@
 # Author: Christopher Lenz <cmlenz@gmx.de>
 
 import re, sys, os, time
+from subprocess import call
 
 from trac.core import *
 from trac.config import Option
 from trac.db.api import IDatabaseConnector, _parse_db_str
 from trac.db.util import ConnectionWrapper
-from trac.util import get_pkginfo
-from subprocess import Popen, PIPE
+from trac.util import Null, get_pkginfo
 from trac.util.compat import close_fds
 
 psycopg = None
@@ -36,10 +36,8 @@
 
     implements(IDatabaseConnector)
 
-    pg_dump_bin = Option('trac', 'pg_dump_bin', 'pg_dump',
+    pg_dump_path = Option('trac', 'pg_dump_path', 'pg_dump',
         """Location of pg_dump for Postgres database backups""")
-    compression = Option('trac', 'backup_compression', '8',
-        """Gzip backup compression level (if supported by backend)""")
 
     def __init__(self):
         self._version = None
@@ -102,11 +100,10 @@
                    '_'.join(index.columns), table.name, '","'.join(index.columns))
 
     def backup(self, dest_file):
-        # pg_dump -n schemaname dbname | gzip > filename.gz
         db_url = self.env.config.get('trac', 'database')
         scheme, db_prop = _parse_db_str(db_url)
         db_name = os.path.basename(db_prop['path'])
-        args = [self.pg_dump_bin, '-C', '-d', '-x', '-Z', self.compression,
+        args = [self.pg_dump_path, '-C', '-d', '-x', '-Z', '8',
                 '-U', db_prop['user'],]
         port = db_prop.get('port', '5432')
         if 'host' in db_prop['params']:
@@ -124,26 +121,19 @@
         else:
             args.extend([db_name])
 
-        dest_file = "%s.gz" % (dest_file,)
-        args.extend(['>', dest_file])
-        if sys.platform == 'win':
-            # XXX TODO verify on windows
-            args = ['cmd', '/c', ' '.join(args)]
-        else:
-            args = ['bash', '-c', ' '.join(args)]
-        
+        dest_file += ".gz"
         environ = os.environ.copy()
         if 'password' in db_prop:
             environ['PGPASSWORD'] = db_prop['password']
-        p = Popen(args, env=environ, shell=False, bufsize=0, stdin=None,
-                  stdout=PIPE, stderr=PIPE, close_fds=close_fds)
-        err = p.wait()
-        if err:
-            raise TracError("Backup attempt exited with error code %s." % err)
-        p.stdout.close()
-        p.stderr.close()
-        if not os.path.exists(dest_file):
-            raise TracError("Backup attempt failed")
+        out = open(dest_file, "wb")
+        try:
+            ret = call(args, env=environ, shell=False, bufsize=0, stdin=None,
+                       stdout=out, stderr=Null(), close_fds=close_fds)
+        finally:
+            out.close()
+        if ret != 0:
+            raise TracError("Backup attempt failed "
+                            "(pg_dump return code: %d)" % ret)
         return dest_file
 
 
diff --git a/trac/util/__init__.py b/trac/util/__init__.py
--- a/trac/util/__init__.py
+++ b/trac/util/__init__.py
@@ -542,6 +542,54 @@
         else:
             return 0
 
+
+class Null(object):
+    """A class that does nothing, but does it well"""
+    def __new__(cls, *args, **kwargs):
+        try:
+            return cls._inst
+        except AttributeError:
+            cls._inst = object.__new__(cls, *args, **kwargs)
+            return cls._inst
+    
+    def __init__(self, *args, **kwargs):
+        pass
+    
+    def __call__(self, *args, **kwargs):
+        return self
+    
+    def __repr__(self):
+        return 'Null()'
+    
+    def __str__(self):
+        return ''
+    
+    def __eq__(self, other):
+        return self is other
+    
+    def __hash__(self):
+        return hash(None)
+    
+    def __len__(self):
+        return 0
+    
+    def __iter__(self):
+        return iter(())
+    
+    def __contains__(self, item):
+        return False
+    
+    __getattr__ = __setattr__ = __delattr__ \
+        = __getitem__ = __setitem__ = __delitem__ \
+        = __add__ = __sub__ = __mul__ = __div__ = __truediv__ \
+        = __floordiv__ = __mod__ = __divmod__ = __pow__ \
+        = __lshift__ = __rshift__ = __and__ = __xor__ = __or__ \
+        = __radd__ = __rsub__ = __rmul__ = __rdiv__ = __rtruediv__ \
+        = __rfloordiv__ = __rmod__ = __rdivmod__ = __rpow__ \
+        = __rlshift__ = __rrshift__ = __rand__ = __rxor__ = __ror__ \
+        = __call__
+
+
 def content_disposition(type, filename=None):
     """Generate a properly escaped Content-Disposition header"""
     if isinstance(filename, unicode):

