justin_hanekom

tar.py

Sep 14th, 2025
545
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.63 KB | Source Code | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. # File: tar.py
  4. # SPDX-License-Identifier: Unlicense
  5.  
  6. # This is free and unencumbered software released into the public domain.
  7. #
  8. # Anyone is free to copy, modify, publish, use, compile, sell, or
  9. # distribute this software, either in source code form or as a compiled
  10. # binary, for any purpose, commercial or non-commercial, and by any
  11. # means.
  12.  
  13. # Tectonics:
  14. #   $ black tar.py
  15.  
  16. import argparse
  17. import shutil
  18. import pathlib
  19. import subprocess
  20. import sys
  21.  
  22. VERSION = "1.0.0"
  23.  
  24.  
  25. def main(prog, args, version):
  26.     """Archives src_dirs to dest_tar."""
  27.     opts = parse_cmdline(prog, args, version)
  28.     cmd_args = tar_cmd(opts)
  29.     kwargs = {"check": True, "text": True}
  30.     if opts.get("combine_output", False):
  31.         kwargs["stderr"] = subprocess.STDOUT
  32.     subprocess.run(cmd_args, **kwargs)
  33.  
  34.  
  35. def parse_cmdline(prog, args, version):
  36.     """Parses the command line aruments in args.
  37.  
  38.    Shows a help message if help is requested, or the program version if
  39.    version is requested. In either of these cases the program terminates.
  40.    """
  41.     DEST = "DEST_TAR"
  42.     parser = argparse.ArgumentParser(
  43.         description=f"Archives the source files/directories to {DEST}.",
  44.         epilog=f"""The extension of the {DEST} file name is used
  45.            to determine how to compress the destination tar file.
  46.            A parallel compression tool will be used if possible.""",
  47.         formatter_class=argparse.ArgumentDefaultsHelpFormatter,
  48.         allow_abbrev=False,
  49.     )
  50.  
  51.     # source option
  52.     parser.add_argument(
  53.         "-s",
  54.         "--src",
  55.         action="extend",
  56.         nargs="+",
  57.         required=True,
  58.         help="files/directoriess to archive",
  59.     )
  60.  
  61.     # destination option
  62.     parser.add_argument(
  63.         "-d",
  64.         "--dest",
  65.         help="name of the destination tar file",
  66.         metavar=f"{DEST}",
  67.     )
  68.  
  69.     # mode option
  70.     parser.add_argument(
  71.         "-m",
  72.         "--mode",
  73.         default="create",
  74.         choices=["create", "append", "update"],
  75.         help="mode used for writing the tar file",
  76.     )
  77.  
  78.     # nice option
  79.     parser.add_argument(
  80.         "-n",
  81.         "--nice",
  82.         nargs="?",
  83.         const=10,
  84.         default=0,
  85.         type=int,
  86.         help="""niceness factor (10 if specified with no value);
  87.                values range from  -20  (most favourable) to 19
  88.                (least favourable)""",
  89.     )
  90.  
  91.     # combine-output option
  92.     parser.add_argument(
  93.         "-c",
  94.         "--combine-output",
  95.         action="store_true",
  96.         default=False,
  97.         help="redirect error messages to stdout",
  98.         dest="combine_output",
  99.     )
  100.  
  101.     # verbose option
  102.     parser.add_argument(
  103.         "-v",
  104.         "--verbose",
  105.         action="store_true",
  106.         default=False,
  107.         help="verbosely list files processed",
  108.     )
  109.  
  110.     # version option
  111.     parser.add_argument("--version", action="version", version=str(version))
  112.  
  113.     # additional tar arguments
  114.     parser.add_argument(
  115.         "tar-opts",
  116.         action="extend",
  117.         nargs="*",
  118.         help="additional arguments to pass to tar",
  119.     )
  120.  
  121.     result = vars(parser.parse_args(args))
  122.     if result["nice"]:
  123.         if result["nice"] < -20:
  124.             result["nice"] = -20
  125.         elif result["nice"] > 19:
  126.             result["nice"] = 19
  127.     return result
  128.  
  129.  
  130. def tar_cmd(args):
  131.     """Returns the tar process to be be run as a list of strings."""
  132.     # the result list either starts with tar,
  133.     # or with "nice -n <val> tar"
  134.     result = []
  135.     if "nice" in args and args["nice"] != 0:
  136.         result = [which_or_name("nice"), "-n", str(args["nice"])]
  137.  
  138.     # add standard tar options
  139.     result.extend([which_or_name("tar"), f"--{args['mode']}", "--recursion"])
  140.  
  141.     # add parallel compress program if parallel compression is possible
  142.     compressor = compress_program(args["dest"])
  143.     if compressor:
  144.         result.extend(["--use-compress-program", compressor])
  145.     else:
  146.         result.append("--auto-compress")
  147.  
  148.     # verbose option
  149.     if args.get("verbose", False):
  150.         result.extend(["--verbose", "--totals"])
  151.  
  152.     # add permissions
  153.     result.extend(["--preserve-permissions", "--atime-preserve"])
  154.  
  155.     # add exclusions
  156.     for name in ["/boot", ".cache", "cache", "/dev", "/proc", "sys"]:
  157.         result.extend(["--exclude", name])
  158.     result.extend(["--exclude-caches-all", "--exclude", args["dest"]])
  159.  
  160.     # add tar-opts if they exist
  161.     if "tar-opts" in args:
  162.         result.extend(args["tar-opts"])
  163.  
  164.     # add src and dest
  165.  
  166.     # add --absolute-names option to silence warnings about absolute paths
  167.     # result.append("--absolute-names")
  168.     if args["dest"]:
  169.         result.extend(["--file", args["dest"]])
  170.     result.extend(args["src"])
  171.  
  172.     return result
  173.  
  174.  
  175. def compress_program(tar_file):
  176.     """Returns the compress program that tar should use based on the
  177.    name of the file that is going to be written to.
  178.    """
  179.     match pathlib.PurePath(tar_file).suffix.lower():
  180.         # bzip2
  181.         case ".bz" | ".bz2":
  182.             cmd = shutil.which("pbzip")
  183.             if cmd:
  184.                 return f"{cmd} --keep "
  185.             cmd = shutil.which("lrzip")
  186.             if cmd:
  187.                 return f"{cmd} -b "
  188.             cmd = shutil.which("lbzip2")
  189.             if cmd:
  190.                 return f"{cmd} --keep "
  191.  
  192.         # gzip
  193.         case ".gz" | "gzip" | "tgz":
  194.             cmd = shutil.which("pigz")
  195.             if cmd:
  196.                 return f"{cmd} --keep "
  197.             cmd = shutil.which("lrzip")
  198.             if cmd:
  199.                 return f"{cmd} -g "
  200.             cmd = shutil.which("crabz")
  201.             if cmd:
  202.                 return f"{cmd} -g "
  203.  
  204.         # TODO lrz
  205.         case ".lrz":
  206.             pass
  207.  
  208.         # lz
  209.         case "lz" | "tlz":
  210.             cmd = shutil.which("plzip")
  211.             if cmd:
  212.                 return f"{cmd} --keep "
  213.  
  214.         # TODO lz4 lempel-ziv
  215.         case ".lz4":
  216.             pass
  217.  
  218.         # lzo
  219.         case ".lzo":
  220.             cmd = shutil.which("lrzip")
  221.             if cmd:
  222.                 return f"{cmd} -l "
  223.  
  224.         # TODO lzw
  225.         case ".lzw":
  226.             pass
  227.  
  228.         # lzma | xz
  229.         case "lzma" | ".xz":
  230.             cmd = shutil.which("pixz")
  231.             if cmd:
  232.                 return f"{cmd}"
  233.             cmd = shutil.which(pxz)
  234.             if cmd:
  235.                 return f"{cmd} --keep "
  236.  
  237.         # TODO zip
  238.         case ".zip":
  239.             pass
  240.  
  241.         # zstd
  242.         case ".zst" | ".zstd":
  243.             cmd = shutil.which("zstd")
  244.             if cmd:
  245.                 return f"{cmd} -T0 "
  246.  
  247.  
  248. def which_or_name(cmd):
  249.     """Returns the fully qualified name of cmd. If not found then returns
  250.    the original value."""
  251.     path = shutil.which(cmd)
  252.     return path if path != "" else cmd
  253.  
  254.  
  255. if __name__ == "__main__":
  256.     main(sys.argv[0], sys.argv[1:], VERSION)
  257.  
Tags: tar
Advertisement