Je vais vous dire exactement ce dont j'ai besoin pour clarifier la question énigmatique dans le titre. Je fais actuellement des sauvegardes MySQL planifiées de toutes mes bases de données avec quelque chose comme :
mysqldump ... | gzip -c > mysql-backup.gz
C'est correct, mais je suis prêt à créer un fichier séparé pour chaque base de données, car cela facilitera l'examen des données vidées ou la restauration d'une seule base de données :
for db in $dbs; do mysqldump ... $db | gzip -c > mysql-backup-$db.gz; done
J'aimerais stocker tous les vidages pour chaque sauvegarde dans un seul .tar
fichier, c'est-à-dire mysql-backup.tar.gz
avec toutes les bases de données sous-évaluées à l'intérieur. Je sais que je peux simplement laisser .sql
fichiers non compressés puis tar -cz *.sql
, mais 1) Je cherche un moyen qui n'a pas besoin de stocker temporairement de gros fichiers . Dans mon script actuel, en fait, mysqldump
est redirigé vers gzip
, donc aucun gros fichier n'est créé.
2) Existe-t-il une manière similaire de créer .tar.gz
depuis stdin ?
3) Est-ce que tar -c *.sql.gz
équivalent à tar -cz *.sql
?
Réponse acceptée :
J'ai bricolé du python pour faire ce que tu veux. Il utilise le fichier tar de python bibliothèque pour ajouter stdin à un fichier tar, puis revient simplement dans le tar pour réécrire l'en-tête avec la bonne taille à eof. L'utilisation serait :
rm -f mytar
for db in $dbs
do mysqldump ... $db | gzip -c |
tarappend -t mytar -f mysql-backup-$db.gz
done
tar tvf mytar
Voici le tarappend
script python :
#!/usr/bin/python
# concat stdin to end of tar file, with given name. meuh on stackexchange
# $Id: tarappend,v 1.3 2015/07/08 11:31:18 meuh $
import sys, os, tarfile, time, copy
from optparse import OptionParser
try:
import grp, pwd
except ImportError:
grp = pwd = None
usage = """%prog: ... | %prog -t tarfile -f filename
Appends stdin to tarfile under the given arbitrary filename.
tarfile is created if it does not exist.
"""
def doargs():
parser = OptionParser(usage=usage)
parser.add_option("-f", "--filename", help="filename to use")
parser.add_option("-t", "--tarfile", help="existing tar archive")
(options, args) = parser.parse_args()
if options.filename is None or options.tarfile is None:
parser.error("need filename and tarfile")
if len(args):
parser.error("unknown args: "+" ".join(args))
return options
def copygetlen(fsrc, fdst):
"""copy data from file-like object fsrc to file-like object fdst. return len"""
totlen = 0
while 1:
buf = fsrc.read(16*1024)
if not buf:
return totlen
fdst.write(buf)
totlen += len(buf)
class TarFileStdin(tarfile.TarFile):
def addstdin(self, tarinfo, fileobj):
"""Add stdin to archive. based on addfile() """
self._check("aw")
tarinfo = copy.copy(tarinfo)
buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
bufoffset = self.offset
self.fileobj.write(buf)
self.offset += len(buf)
tarinfo.size = copygetlen(fileobj, self.fileobj)
blocks, remainder = divmod(tarinfo.size, tarfile.BLOCKSIZE)
if remainder > 0:
self.fileobj.write(tarfile.NUL * (tarfile.BLOCKSIZE - remainder))
blocks += 1
self.offset += blocks * tarfile.BLOCKSIZE
# rewrite header with correct size
buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
self.fileobj.seek(bufoffset)
self.fileobj.write(buf)
self.fileobj.seek(self.offset)
self.members.append(tarinfo)
class TarInfoStdin(tarfile.TarInfo):
def __init__(self, name):
if len(name)>100:
raise ValueError(name+": filename too long")
if name.endswith("/"):
raise ValueError(name+": is a directory name")
tarfile.TarInfo.__init__(self, name)
self.size = 99
self.uid = os.getuid()
self.gid = os.getgid()
self.mtime = time.time()
if pwd:
self.uname = pwd.getpwuid(self.uid)[0]
self.gname = grp.getgrgid(self.gid)[0]
def run(tarfilename, newfilename):
tar = TarFileStdin.open(tarfilename, 'a')
tarinfo = TarInfoStdin(newfilename)
tar.addstdin(tarinfo, sys.stdin)
tar.close()
if __name__ == '__main__':
options = doargs()
run(options.tarfile, options.filename)