~dricottone/my-utils

5284ab29e23d08d11a2a2d003228b4299e539278 — Dominic Ricottone 2 years ago 6223cfd
Rewrote parts of untar, mktar,, tarcat, etc

Added --to-directory and --from-directory options throughout.

Added support for a variety of other archive file types.
6 files changed, 254 insertions(+), 35 deletions(-)

M archives/archive.bash -rw-r--r-- => -rwxr-xr-x
M archives/completion.bash
M archives/mktar
M archives/mktar-batch
M archives/unarchive.bash
M archives/untar
M archives/archive.bash => archives/archive.bash +28 -7
@@ 16,6 16,7 @@ archive() {
  local archive_flags=""
  local archive_late_flags=""
  local archive_fn=""
  local from_directory=""

  local positional=()



@@ 191,8 192,13 @@ archive() {
      error_msg "unsupported archive type '${1}'"
      ;;

    --directory)
      from_directory="$2"
      shift; shift
      ;;

    --name)
      archive_fn="$2"
      archive_fn="$(realpath "$2")"
      shift; shift
      ;;



@@ 202,7 208,7 @@ archive() {
      ;;

    *)
      positional+=("$1")
      positional+=("$(realpath "$1")")
      shift
      ;;
    esac


@@ 214,12 220,23 @@ archive() {
    usage_msg
  fi

  # error if input file does not exist
  local targets=()
  for target in "${positional[@]}"; do
    # error if target file does not exist
    if [[ ! -f "$target" ]] && [[ ! -d "$target" ]]; then
      error_msg "no such file '${target}'"
    fi

    # make paths relative to from_directory
    if [[ ! -z "$from_directory" ]]; then
      targets+="$(realpath --relative-base="$from_directory" "$target")"
    else
      targets+="$(realpath --relative-base="$(pwd)" "$target")"
    fi
  done
  debug_msg "Input files have been resolved to relative paths"
  debug_msg "Was:${positional[@]}"
  debug_msg "Is now:${targets[@]}"

  # error if no archive file given
  if [[ -z "$archive_fn" ]]; then


@@ 250,15 267,19 @@ archive() {
  if [[ "$verbose" -ne 1 ]]; then
    verbosity=/dev/null
  elif [[ "$encrypt" -eq 1 ]]; then
    debug_msg "$archive_cmd $archive_flags $archive_late_flags ${positional[@]} 2>$verbosity | $encrypt_cmd $encrypt_flags $archive_fn $passphrase_flags 2>$verbosity"
    debug_msg "cd ${from_directory:-.} && $archive_cmd $archive_flags $archive_late_flags ${targets[@]} 2>$verbosity | $encrypt_cmd $encrypt_flags $archive_fn $passphrase_flags 2>$verbosity"
  else
    debug_msg "$archive_cmd $archive_flags $archive_fn $archive_late_flags ${positional[@]} 2>$verbosity"
    debug_msg "cd ${from_directory:-.} && $archive_cmd $archive_flags $archive_fn $archive_late_flags ${targets[@]} 2>$verbosity"
  fi

  # archive routine
  if [[ ! -z "$from_directory" ]] && ! cd "$from_directory" >/dev/null 2> >(/usr/bin/sed -e "s/.*cd/cd/" >$verbosity); then
    error_msg "could not access directory '${from_directory}'"
    echo "ping"
  fi
  if [[ "$encrypt" -eq 1 ]]; then
    ( \
      $archive_cmd $archive_flags $archive_late_flags "${positional[@]}" 2>$verbosity \
      $archive_cmd $archive_flags $archive_late_flags "${targets[@]}" 2>$verbosity \
        || error_msg "could not archive '${archive_fn}'" \
    ) \
    | ( \


@@ 266,7 287,7 @@ archive() {
        || error_msg "could not encrypt '${archive_fn}'" \
    )
  else
    $archive_cmd $archive_flags "$archive_fn" $archive_late_flags "${positional[@]}" 2>$verbosity \
    $archive_cmd $archive_flags "$archive_fn" $archive_late_flags "${targets[@]}" 2>$verbosity \
      || error_msg "could not archive '${archive_fn}'"
  fi
}

M archives/completion.bash => archives/completion.bash +5 -5
@@ 27,9 27,9 @@ complete -o default -f -X '!*.@(zip|cbr|epub)' rmzip
complete -o default -f -X '!*.@(zip|cbr|epub)' zipls

# rmtar, tarcat, tarls, untar
# complete with filenames like '*.@(tar|tar.@(gz|xz|zst|bz2)|tar.@(gz|xz|zst|bz2).(gpg|age))'
complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).(gpg|age))' rmtar
complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).(gpg|age))' tarcat
complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).(gpg|age))' tarls
complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).(gpg|age))' untar
# complete with filenames like '*.@(zip|cbz|docx|pptx|xlsx|rar|rpa|7z|tar|tar.@(gz|xz|zst|bz2)|tar.@(gz|xz|zst|bz2).@(gpg|age))'
complete -o default -f -X '!*.@(zip|cbz|docx|pptx|xlsx|rar|rpa|7z|tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).@(gpg|age))' rmtar
complete -o default -f -X '!*.@(zip|cbz|docx|pptx|xlsx|rar|rpa|7z|tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).@(gpg|age))' tarcat
complete -o default -f -X '!*.@(zip|cbz|docx|pptx|xlsx|rar|rpa|7z|tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).@(gpg|age))' tarls
complete -o default -f -X '!*.@(zip|cbz|docx|pptx|xlsx|rar|rpa|7z|tar|tar.@(gz|xz|zst|zstd|bz2)|tar.@(gz|xz|zst|bz2).@(gpg|age))' untar


M archives/mktar => archives/mktar +16 -8
@@ 6,13 6,14 @@ read -r -d '' help_message <<-EOF
	Archive utility
	Usage: mktar -n OUTFILE [OPTIONS] INFILES
	Options:
	 --checksum=ALGO             create checksum with [none|sha1|sha256]
	 -h, --help                  print this message
	 -n FILE, --name FILE        name of archive
	 -p PASS, --passphrase PASS  passphrase for encryption
	 -q, --quiet                 suppress error messages and prompts
	 -v, --verbose               show additional messages
	 -V, --version               print version number and exit
	 --checksum=ALGO               create checksum with [none|sha1|sha256]
	 -d DIR, --from-directory DIR  archive from a directory
	 -h, --help                    print this message
	 -n FILE, --name FILE          name of archive
	 -p PASS, --passphrase PASS    passphrase for encryption
	 -q, --quiet                   suppress error messages and prompts
	 -v, --verbose                 show additional messages
	 -V, --version                 print version number and exit
EOF

source /usr/local/lib/mylib.bash


@@ 20,6 21,7 @@ source /usr/local/lib/archive.bash

archive_fn=""
checksum=-1
from_directory=""
positional=()
quiet=0
verbose=0


@@ 49,6 51,12 @@ while [[ $# -gt 0 ]]; do
    error_msg "Unknown checksum '${attempted_checksum}'"
    ;;

  -d|--from-directory)
    debug_msg "Setting from_directory option to '${2}' (was ${from_directory})"
    from_directory="--directory $2"
    shift; shift
    ;;

  -h|--help)
    help_msg
    shift


@@ 121,7 129,7 @@ if [[ "$encrypt" -ge 1 && -z "$passphrase" ]]; then
  passphrase=
fi

if ! archive --archive=$archive_action --name "$archive_fn" $passphrase "${positional[@]}"; then
if ! archive --archive=$archive_action --name "$archive_fn" $passphrase $from_directory "${positional[@]}"; then
  exit 1
fi


M archives/mktar-batch => archives/mktar-batch +32 -9
@@ 4,18 4,41 @@ name="mktar-batch"
version="1.1"
read -r -d '' help_message <<-EOF
	Archive utility for scripting
	Usage: mktar-batch [--compress=ALGO] [--encrypt=ALSO --passphrase PASSWD] [--checksum=ALGO] [--name OUTFILE] INFILES
	Usage: mktar-batch [--compress=ALGO] [--encrypt=ALGO [--passphrase PASS]] [--checksum=ALGO] [--name OUTFILE] INFILES
	Options:
	 --compress=ALGO                 compress archive with [none|gzip|xz|zstd|bzip2]
	 --checksum=ALGO                 create checksum with [none|sha1|sha256]
	 --encrypt=ALGO                  encrypt files with [none|gpg|age]
	 -h, --help                      print this message
	 -n FILE, --name FILE            name of backup file (Default: archive.tar)
	 -p PASSWD, --passphrase PASSWD  passphase for encryption
	 -v, --verbose                   show additional messages
	 -V, --version                   print version number and exit
	 --compress=ALGO             compress archive
	 --checksum=ALGO             create checksum
	 --encrypt=ALGO              encrypt archive
	 -h, --help                  print this message
	 -n FILE, --name FILE        name of backup file
	 -p PASS, --passphrase PASS  passphase for encryption
	 -v, --verbose               show additional messages
	 -V, --version               print version number and exit
	
	Compress algorithms and their ALGO codes:
	 n/a    none or no
	 GZip   gzip or gz
	 xz     zx
	 ZStd   zstd or zst
	 bzip2  bz2
	
	Checksum algorithms and their ALGO codes:
	 n/a     none or no
	 SHA1    sha1
	 SHA256  sha256
	
	Encrypt algorithms and their ALGO codes:
	 n/a   none or no
	 GPG   gpg
	 age   age
EOF

# TODO: add support for compress algorithms:
#        WinRAR  rar
#        Ren'Py  rpa
#        PKZip   zip
#        7-Zip   7z

source /usr/local/lib/mylib.bash
source /usr/local/lib/archive.bash


M archives/unarchive.bash => archives/unarchive.bash +163 -5
@@ 2,10 2,41 @@

# See tarcat for simple usage

is_flagged_for_list() {
  #NOTE: Origins of these flags:
  #  "t"   = tar
  #  "tf"  = tar
  #  "Ot"  = tar
  #  "Otf" = tar
  #  "l"   = unrar, 7z
  #  "-l"  = unzip, unrpa
  if contains "$1" "l" "-l" "t" "tf" "Ot" "Otf"; then
    return 0
  fi
  return 1
}

is_flagged_for_stdout() {
  #NOTE: Origins of these flags:
  #  "Ox"    = tar
  #  "Oxf"   = tar
  #  "p"     = unrar
  #  "-p"    = unzip
  #  "x -so" = 7z
  if contains "$1" "p" "-p" "Ox" "Oxf"; then
    return 0
  fi
  return 1
}

unarchive() {
  local tar_bin=/usr/bin/tar
  local gpg_bin=/usr/bin/gpg
  local age_bin=age
  local unzip_bin=/usr/bin/unzip
  local unrar_bin=/usr/bin/unrar
  local unrpa_bin=unrpa
  local _7z_bin=/usr/bin/7z

  local decrypt=""
  local decrypt_cmd=""


@@ 15,6 46,7 @@ unarchive() {
  local unarchive_cmd=""
  local unarchive_flags=""
  local unarchive_late_flags=""
  local to_directory=""

  while [[ $# -gt 0 ]]; do
    case $1 in


@@ 184,12 216,110 @@ unarchive() {
      shift
      ;;

    --archive=zip)
      decrypt=0
      decrypt_cmd=""
      decrypt_flags=""
      passphrase_flags=""
      unarchive_cmd="${unzip_bin}"
      if is_flagged_for_list "$unarchive_flags"; then
        unarchive_flags="-l"
      elif is_flagged_for_stdout "$unarchive_flags"; then
        unarchive_flags="-p"
      else
        unarchive_flags=""
      fi
      unarchive_late_flags=""
      shift
      ;;

    --archive=rar)
      decrypt=0
      decrypt_cmd=""
      decrypt_flags=""
      passphrase_flags=""
      unarchive_cmd="${unrar_bin}"
      if is_flagged_for_list "$unarchive_flags"; then
        unarchive_flags="l"
      elif is_flagged_for_stdout "$unarchive_flags"; then
        unarchive_flags="p"
      else
        unarchive_flags="x"
      fi
      unarchive_late_flags=""
      shift
      ;;

    --archive=rpa)
      decrypt=0
      decrypt_cmd=""
      decrypt_flags=""
      passphrase_flags=""
      unarchive_cmd="${unrpa_bin}"
      if is_flagged_for_list "$unarchive_flags"; then
        unarchive_flags="-l"
      elif is_flagged_for_stdout "$unarchive_flags"; then
        error_msg "unrpa does not support --stdout"
      else
        unarchive_flags=""
      fi
      unarchive_late_flags=""
      shift
      ;;

    --archive=7z)
      decrypt=0
      decrypt_cmd=""
      decrypt_flags=""
      passphrase_flags=""
      unarchive_cmd="${_7z_bin}"
      if is_flagged_for_list "$unarchive_flags"; then
        unarchive_flags="l -bso0 -bsp0"
      elif is_flagged_for_stdout "$unarchive_flags"; then
        unarchive_flags="x -so -bsp0"
      else
        unarchive_flags="x -bso0 -bsp0"
      fi
      unarchive_late_flags=""
      shift
      ;;

    --archive=*)
      error_msg "unsupported archive type '${1}'"
      ;;

    --directory)
      to_directory="$2"
      shift; shift
      ;;

    --list)
      unarchive_flags="$(/usr/bin/sed -e 's/x/t/' <<<"$unarchive_flags")"
      case "$unarchive_cmd" in
      "$unzip_bin")
        #NOTE: `-l` is incompatible with all other flags, so overwrite
        unarchive_flags="-l"
        ;;

      "$unrar_bin")
        #NOTE: `l` is incompatible with all other flags, so overwrite
        unarchive_flags="l"
        ;;

      "$unrpa_bin")
        #NOTE: `-l` is incompatible with all other flags, so overwrite
        unarchive_flags="-l"
        ;;

      "$_7z_bin")
        #NOTE: `l` is incompatible with all other flags, so overwrite
        unarchive_flags="l -bso0 -bsp0"
        ;;

      *)
        #NOTE: `t` is incompatible with `x` (which I set by default), so partially overwrite
        unarchive_flags="$(/usr/bin/sed -e 's/x/t/' <<<"$unarchive_flags")"
        ;;
      esac
      shift
      ;;



@@ 199,12 329,36 @@ unarchive() {
      ;;

    --stdout)
      unarchive_flags="O${unarchive_flags}"
      case "$unarchive_cmd" in
      "$unzip_bin")
        #NOTE: `-p` is incompatible with all other flags, so overwrite
        unarchive_flags="-p"
        ;;

      "$unrar_bin")
        #NOTE: `p` is incompatible with all other flags, so overwrite
        unarchive_flags="p"
        ;;

      "$unrpa_bin")
        error_msg "unrpa does not support --stdout"
        ;;

      "$_7z_bin")
        #NOTE: `x` is incompatible with all other flags, so overwrite and append `-so`
        unarchive_flags="x -so -bsp0"
        ;;

      *)
        #NOTE: while the effect is unusual, `O` is not incompatible with `x`/`t`, so append
        unarchive_flags="O${unarchive_flags}"
        ;;
      esac
      shift
      ;;

    *)
      archive_fn="$1"
      archive_fn="$(realpath "$1")"
      shift
      ;;
    esac


@@ 228,12 382,16 @@ unarchive() {
  if [[ "$verbose" -ne 1 ]]; then
    verbosity=/dev/null
  elif [[ "$decrypt" -eq 1 ]]; then
    debug_msg "$decrypt_cmd $decrypt_flags $archive_fn $passphrase_flags 2>$verbosity | $unarchive_cmd $unarchive_flags $unarchive_late_flags 2>$verbosity"
    debug_msg "cd ${to_directory:-.} && $decrypt_cmd $decrypt_flags $archive_fn $passphrase_flags 2>$verbosity | $unarchive_cmd $unarchive_flags $unarchive_late_flags 2>$verbosity"
  else
    debug_msg "$unarchive_cmd $unarchive_flags $archive_fn $unarchive_late_flags 2>$verbosity"
    debug_msg "cd ${to_directory:-.} && $unarchive_cmd $unarchive_flags $archive_fn $unarchive_late_flags 2>$verbosity"
  fi

  # unarchive routine
  if [[ ! -z "$to_directory" ]] && ! cd "$to_directory" >/dev/null 2> >(/usr/bin/sed -e "s/.*cd/cd/" >$verbosity); then
    error_msg "could not access directory '${to_directory}'"
    echo "ping"
  fi
  if [[ "$decrypt" -eq 1 ]]; then
    ( \
      $decrypt_cmd $decrypt_flags $passphrase_flags "$archive_fn" 2>$verbosity \

M archives/untar => archives/untar +10 -1
@@ 7,6 7,7 @@ help_message=$(/usr/bin/cat <<-EOF
	Usage: untar TARGET [..] [OPTIONS]
	Options:
	 -h, --help                  print this message and exit
	 -d DIR, --to-directory DIR  unarchive to a directory
	 -p PASS, --passphrase PASS  passphrase for decryption
	 -q, --quiet                 suppress error messages
	 -v, --version               print version number and exit


@@ 19,10 20,17 @@ source /usr/local/lib/unarchive.bash
quiet=0
passphrase=""
positional=()
to_directory=""
verbose=0
while [[ $# -gt 0 ]]; do
  case $1 in

  -d|--to-directory)
    debug_msg "Setting to_directory option to '${2}' (was ${to_directory})"
    to_directory="--directory $2"
    shift; shift
    ;;

  -h|--help)
    help_msg
    shift


@@ 35,6 43,7 @@ while [[ $# -gt 0 ]]; do
    ;;

  -p|--passphrase)
    debug_msg "Setting passphrase option to '${2}' (was ${passphrase})"
    passphrase="--passphrase $2"
    shift; shift
    ;;


@@ 60,7 69,7 @@ done
code=0
for archive_fn in "${positional[@]}"; do
  archive_action="$(archive_extension "$archive_fn" | tee >(>&2 /usr/bin/head -n -1) | /usr/bin/tail -n 1)"
  if ! unarchive --archive=$archive_action "$archive_fn" $passphrase; then
  if ! unarchive --archive=$archive_action "$archive_fn" $passphrase $to_directory; then
    code=1
  fi
done