many bugfixes
This commit is contained in:
+227
-84
@@ -46,6 +46,14 @@ load_conf "compress"
|
|||||||
# -n, --no-dir Never create a host directory
|
# -n, --no-dir Never create a host directory
|
||||||
utaz()
|
utaz()
|
||||||
{
|
{
|
||||||
|
# Extractor helpers. Every one takes the archive path as $1 and an existing
|
||||||
|
# target directory as $2, and returns the extractor exit status (0 = ok,
|
||||||
|
# 1 = warning, anything else = failure). The matching one is selected by
|
||||||
|
# file extension and invoked by name (see the dispatch "case" below), and
|
||||||
|
# the name minus its "_un" prefix is the binary checked beforehand.
|
||||||
|
# NOTE: bash defines functions globally, so these helpers are (re)defined in
|
||||||
|
# the shell on each call and the "local" lines below only declare an unused
|
||||||
|
# variable of the same name; they are kept as a marker for shellcheck.
|
||||||
local _ununzip
|
local _ununzip
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_ununzip()
|
_ununzip()
|
||||||
@@ -92,11 +100,15 @@ utaz()
|
|||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unlzip()
|
_unlzip()
|
||||||
{
|
{
|
||||||
if command -v plzip >/dev/null 2>&1; then
|
# Only *.tar.lz / *.tlz reach this helper: decompress to stdout and
|
||||||
plzip -d -c "$1" > "$2/$(basename "${1%.*}")"
|
# untar from the pipe. pipefail (subshell-scoped) makes a lzip failure
|
||||||
else
|
# visible instead of being masked by tar's exit status.
|
||||||
lzip -d -c "$1" > "$2/$(basename "${1%.*}")"
|
local lzcmd=lzip
|
||||||
fi
|
command -v plzip >/dev/null 2>&1 && lzcmd=plzip
|
||||||
|
(
|
||||||
|
set -o pipefail
|
||||||
|
"$lzcmd" -d -c -- "$1" | tar -xf - -C "$2"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
local _ununrar
|
local _ununrar
|
||||||
@@ -142,8 +154,16 @@ utaz()
|
|||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unzstd()
|
_unzstd()
|
||||||
{
|
{
|
||||||
# Zstd decompresses files directly, often requiring tar for archives
|
# A tar.zst/.tzst is untarred; a bare .zst is a single compressed file
|
||||||
tar --zstd -xf "$1" -C "$2"
|
# and is simply decompressed into the target directory.
|
||||||
|
case $1 in
|
||||||
|
*.tar.zst|*.tzst)
|
||||||
|
tar --zstd -xf "$1" -C "$2"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
zstd -d -q -c -- "$1" > "$2/$(basename "${1%.*}")"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
local _uncpio
|
local _uncpio
|
||||||
@@ -178,10 +198,43 @@ utaz()
|
|||||||
_unrpm()
|
_unrpm()
|
||||||
{
|
{
|
||||||
# Extracts CPIO-based payload from an RPM package
|
# Extracts CPIO-based payload from an RPM package
|
||||||
# Needs rpm2cpio and cpio
|
# Needs rpm2cpio and cpio. pipefail (subshell-scoped) so that a failing
|
||||||
rpm2cpio "$1" | (cd "$2/" && cpio -idmv) >/dev/null 2>&1
|
# rpm2cpio is not masked by cpio's exit status.
|
||||||
|
(
|
||||||
|
set -o pipefail
|
||||||
|
rpm2cpio "$1" | (cd "$2/" && cpio -idm)
|
||||||
|
) >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Move every entry (hidden ones included) of directory $1 up into its
|
||||||
|
# parent, then remove $1. Refuses (returns 1, $1 left untouched) if any
|
||||||
|
# entry would overwrite something already present in the parent.
|
||||||
|
# The directory is first renamed to a temporary name so that an archive
|
||||||
|
# holding a top-level entry named like the host directory (foo.zip -> foo/foo)
|
||||||
|
# does not collide with itself. Runs in a subshell so the shopt calls do not
|
||||||
|
# leak into the interactive shell.
|
||||||
|
local _utaz_flatten
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
_utaz_flatten()
|
||||||
|
(
|
||||||
|
shopt -s nullglob dotglob
|
||||||
|
local dir=$1 parent tmp child
|
||||||
|
parent=$(dirname -- "$dir")
|
||||||
|
tmp="${parent}/.utaz.$$.${RANDOM}"
|
||||||
|
mv -- "$dir" "$tmp" || return 1
|
||||||
|
for child in "$tmp"/*; do
|
||||||
|
if [[ -e "${parent}/${child##*/}" || -L "${parent}/${child##*/}" ]]; then
|
||||||
|
disp W "${parent}/${child##*/} already exists, not overwriting."
|
||||||
|
mv -- "$tmp" "$dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for child in "$tmp"/*; do
|
||||||
|
mv -- "$child" "${parent}/" || return 1
|
||||||
|
done
|
||||||
|
rmdir -- "$tmp"
|
||||||
|
)
|
||||||
|
|
||||||
local PARSED
|
local PARSED
|
||||||
PARSED=$(getopt -o hdcn --long help,delete,create-dir,no-dir -n 'utaz' -- "$@")
|
PARSED=$(getopt -o hdcn --long help,delete,create-dir,no-dir -n 'utaz' -- "$@")
|
||||||
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
@@ -189,9 +242,11 @@ utaz()
|
|||||||
disp E "Invalid options, use \"utaz --help\" to display usage."
|
disp E "Invalid options, use \"utaz --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# getopt emits a shell-quoted, normalised argument list ("--" terminates the
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
# options); re-evaluating it restores the arguments with spaces and quotes
|
||||||
set -- "${_pargs[@]}"
|
# intact. Declared local so flags never leak in from the caller's environment.
|
||||||
|
local willrm="" createdir="" nodir=""
|
||||||
|
eval set -- "$PARSED"
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help)
|
-h|--help)
|
||||||
@@ -215,7 +270,8 @@ utaz()
|
|||||||
printf "\t- lha, lzh\n"
|
printf "\t- lha, lzh\n"
|
||||||
printf "\t- ace\n"
|
printf "\t- ace\n"
|
||||||
printf "\t- 7z, p7z\n"
|
printf "\t- 7z, p7z\n"
|
||||||
printf "\t- zst\n"
|
printf "\t- tar\n"
|
||||||
|
printf "\t- zst, tar.zst, .tzst\n"
|
||||||
printf "\t- cpio\n"
|
printf "\t- cpio\n"
|
||||||
printf "\t- cab\n"
|
printf "\t- cab\n"
|
||||||
printf "\t- deb\n"
|
printf "\t- deb\n"
|
||||||
@@ -223,15 +279,15 @@ utaz()
|
|||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
-d|--delete)
|
-d|--delete)
|
||||||
local willrm=1
|
willrm=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-c|--create-dir)
|
-c|--create-dir)
|
||||||
local createdir=1
|
createdir=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-n|--no-dir)
|
-n|--no-dir)
|
||||||
local nodir=1
|
nodir=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--)
|
--)
|
||||||
@@ -250,8 +306,8 @@ utaz()
|
|||||||
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
|
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
|
||||||
|
|
||||||
# Apply defaults from [compress] configuration if not overridden by flags
|
# Apply defaults from [compress] configuration if not overridden by flags
|
||||||
[[ -z ${willrm+x} ]] && [[ ${UTAZ_DEFAULT_DELETE:-0} -eq 1 ]] && willrm=1
|
[[ -z $willrm ]] && [[ ${UTAZ_DEFAULT_DELETE:-0} -eq 1 ]] && willrm=1
|
||||||
if [[ -z ${createdir+x} && -z ${nodir+x} ]]; then
|
if [[ -z $createdir && -z $nodir ]]; then
|
||||||
case "${UTAZ_DEFAULT_DIR_MODE:-auto}" in
|
case "${UTAZ_DEFAULT_DIR_MODE:-auto}" in
|
||||||
always) createdir=1 ;;
|
always) createdir=1 ;;
|
||||||
never) nodir=1 ;;
|
never) nodir=1 ;;
|
||||||
@@ -263,12 +319,19 @@ utaz()
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Loop variables are local: this function lives in the interactive shell.
|
||||||
|
local zitem f
|
||||||
for zitem in "${FILES[@]}"; do
|
for zitem in "${FILES[@]}"; do
|
||||||
|
# A name starting with "-" would be taken as an option by the tools below
|
||||||
|
[[ $zitem == -* ]] && zitem="./${zitem}"
|
||||||
|
|
||||||
# Build list of input files to process, with whitespace-safe handling.
|
# Build list of input files to process, with whitespace-safe handling.
|
||||||
local targets=()
|
local targets=()
|
||||||
if [[ -f "$zitem" ]]; then
|
if [[ -f "$zitem" ]]; then
|
||||||
targets=("$zitem")
|
targets=("$zitem")
|
||||||
elif [[ -d "$zitem" ]]; then
|
elif [[ -d "$zitem" ]]; then
|
||||||
|
# Only direct children are considered (no recursion); NUL-separated
|
||||||
|
# so names containing spaces or newlines survive.
|
||||||
mapfile -d '' -t targets < <(find "$zitem" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
|
mapfile -d '' -t targets < <(find "$zitem" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
|
||||||
if [[ ${#targets[@]} -eq 0 ]]; then
|
if [[ ${#targets[@]} -eq 0 ]]; then
|
||||||
disp I "Directory ${zitem} is empty, skipping."
|
disp I "Directory ${zitem} is empty, skipping."
|
||||||
@@ -280,7 +343,11 @@ utaz()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for f in "${targets[@]}"; do
|
for f in "${targets[@]}"; do
|
||||||
|
# Host directory name: archive name without its extension. A
|
||||||
|
# trailing ".tar" is dropped too, so foo.tar.gz gives "foo", not
|
||||||
|
# "foo.tar".
|
||||||
local dir="${f%.*}"
|
local dir="${f%.*}"
|
||||||
|
[[ $dir == *.tar ]] && dir="${dir%.tar}"
|
||||||
local extractor=""
|
local extractor=""
|
||||||
case "$f" in
|
case "$f" in
|
||||||
*.zip)
|
*.zip)
|
||||||
@@ -319,7 +386,7 @@ utaz()
|
|||||||
*.7z|*.p7z)
|
*.7z|*.p7z)
|
||||||
extractor="_un7z"
|
extractor="_un7z"
|
||||||
;;
|
;;
|
||||||
*.zst)
|
*.zst|*.tzst)
|
||||||
extractor="_unzstd"
|
extractor="_unzstd"
|
||||||
;;
|
;;
|
||||||
*.cpio)
|
*.cpio)
|
||||||
@@ -340,7 +407,9 @@ utaz()
|
|||||||
;; # Skip non-archive files
|
;; # Skip non-archive files
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Verify binary existence
|
# Verify the extractor's binary exists. The helper name minus its
|
||||||
|
# "_un" prefix is the program name (_un7z -> 7z); deb/rpm/lzip need
|
||||||
|
# special handling because their programs are named differently.
|
||||||
local cmd=${extractor//_un/}
|
local cmd=${extractor//_un/}
|
||||||
if [[ $cmd == "deb" ]]; then
|
if [[ $cmd == "deb" ]]; then
|
||||||
command -v -- dpkg-deb >/dev/null 2>&1 || {
|
command -v -- dpkg-deb >/dev/null 2>&1 || {
|
||||||
@@ -356,6 +425,12 @@ utaz()
|
|||||||
disp E "The program 'cpio' is not installed, aborting."
|
disp E "The program 'cpio' is not installed, aborting."
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
elif [[ $cmd == "lzip" ]]; then
|
||||||
|
# plzip is a drop-in parallel replacement for lzip
|
||||||
|
command -v -- lzip >/dev/null 2>&1 || command -v -- plzip >/dev/null 2>&1 || {
|
||||||
|
disp E "Neither 'lzip' nor 'plzip' is installed, skipping ${f}."
|
||||||
|
continue
|
||||||
|
}
|
||||||
else
|
else
|
||||||
command -v -- "${cmd}" >/dev/null 2>&1 || {
|
command -v -- "${cmd}" >/dev/null 2>&1 || {
|
||||||
disp E "Binary ${cmd} necessary to extract ${f} is missing."
|
disp E "Binary ${cmd} necessary to extract ${f} is missing."
|
||||||
@@ -368,18 +443,19 @@ utaz()
|
|||||||
# archive never wipes a pre-existing directory of the same name.
|
# archive never wipes a pre-existing directory of the same name.
|
||||||
local dir_created=0
|
local dir_created=0
|
||||||
[[ -d "${dir}" ]] || dir_created=1
|
[[ -d "${dir}" ]] || dir_created=1
|
||||||
if ! mkdir -p "${dir}"; then
|
if ! mkdir -p -- "${dir}"; then
|
||||||
disp E "The filesystem can't create directories, exit!" &&
|
disp E "Can't create directory ${dir}, skipping ${f}."
|
||||||
return 1
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${extractor} "${f}" "${dir}"
|
${extractor} "${f}" "${dir}"
|
||||||
case $? in
|
case $? in
|
||||||
0)
|
0)
|
||||||
[[ -n ${willrm} ]] &&
|
[[ -n ${willrm} ]] &&
|
||||||
rm -f "${f}" && disp I "File ${zitem}/${f} deleted."
|
rm -f -- "${f}" && disp I "File ${f} deleted."
|
||||||
;;
|
;;
|
||||||
1)
|
1)
|
||||||
|
# Most extractors use 1 for "finished with warnings"
|
||||||
if [[ -n ${willrm} ]]; then
|
if [[ -n ${willrm} ]]; then
|
||||||
disp W "Compression program returned a warning: deletion canceled."
|
disp W "Compression program returned a warning: deletion canceled."
|
||||||
else
|
else
|
||||||
@@ -388,40 +464,38 @@ utaz()
|
|||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
disp E "The compressed file ${f} seems corrupted, failed."
|
disp E "The compressed file ${f} seems corrupted, failed."
|
||||||
[[ ${dir_created} -eq 1 ]] && rm -rf "${dir}" >/dev/null 2>&1
|
[[ ${dir_created} -eq 1 ]] && rm -rf -- "${dir}" >/dev/null 2>&1
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# Decide whether the host directory is kept. Flattening is never
|
||||||
|
# done on a directory that existed before extraction, as it would
|
||||||
|
# also move the user's own files out of it.
|
||||||
|
local flatten=0
|
||||||
if [[ -n ${createdir} ]]; then
|
if [[ -n ${createdir} ]]; then
|
||||||
disp I "Archive extracted successfully in subdirectory."
|
:
|
||||||
|
elif [[ ${dir_created} -ne 1 ]]; then
|
||||||
|
:
|
||||||
elif [[ -n ${nodir} ]]; then
|
elif [[ -n ${nodir} ]]; then
|
||||||
shopt -s nullglob
|
flatten=1
|
||||||
for child in "${dir}"/*; do
|
else
|
||||||
mv -- "$child" .
|
# Auto mode: flatten only if the archive already holds a single
|
||||||
done
|
# top-level directory (it then acts as the host directory).
|
||||||
shopt -u nullglob
|
# nullglob makes the array empty when nothing matches; dotglob
|
||||||
rmdir -- "${dir}"
|
# makes hidden entries count, so ".git" alone is not ignored.
|
||||||
|
local _old_glob
|
||||||
|
_old_glob=$(shopt -p nullglob dotglob)
|
||||||
|
shopt -s nullglob dotglob
|
||||||
|
local contents=( "${dir}"/* )
|
||||||
|
eval "$_old_glob"
|
||||||
|
[[ ${#contents[@]} -eq 1 && -d "${contents[0]}" && ! -L "${contents[0]}" ]] && flatten=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $flatten -eq 1 ]] && _utaz_flatten "${dir}"; then
|
||||||
disp I "Archive extracted successfully, no subdirectory needed."
|
disp I "Archive extracted successfully, no subdirectory needed."
|
||||||
else
|
else
|
||||||
# Set nullglob to ensure the array is empty if no files match
|
disp I "Archive extracted successfully in subdirectory."
|
||||||
shopt -s nullglob
|
|
||||||
local contents=( "${dir}"/* )
|
|
||||||
|
|
||||||
# Check if exactly one item exists and if that item is a directory
|
|
||||||
if [[ ${#contents[@]} -eq 1 ]] && [[ -d "${contents[0]}" ]]; then
|
|
||||||
# Single directory detected
|
|
||||||
shopt -s nullglob
|
|
||||||
for child in "${contents[0]}"/*; do
|
|
||||||
mv -- "$child" .
|
|
||||||
done
|
|
||||||
shopt -u nullglob
|
|
||||||
rmdir -- "${dir}"
|
|
||||||
disp I "Archive extracted successfully, no subdirectory needed."
|
|
||||||
else
|
|
||||||
disp I "Archive extracted successfully in subdirectory."
|
|
||||||
fi
|
|
||||||
shopt -u nullglob
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
@@ -445,6 +519,14 @@ export -f utaz
|
|||||||
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
|
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
|
||||||
taz()
|
taz()
|
||||||
{
|
{
|
||||||
|
# Compressor helpers. All share the same signature:
|
||||||
|
# $1 = file to compress, $2 = thread count, $3 = level (1-9),
|
||||||
|
# $4 = non-empty for verbose output
|
||||||
|
# and keep the source (--keep), writing "$1.<ext>" next to it. They return
|
||||||
|
# 127 when no suitable program is installed (fatal for the whole run), any
|
||||||
|
# other non-zero status on a compression failure (that format is skipped).
|
||||||
|
# As with utaz, nested functions are global, "local" does not scope them.
|
||||||
|
|
||||||
# Resolve runtime CPU count for --parallel=auto.
|
# Resolve runtime CPU count for --parallel=auto.
|
||||||
local _taz_detect_cpus
|
local _taz_detect_cpus
|
||||||
_taz_detect_cpus()
|
_taz_detect_cpus()
|
||||||
@@ -532,7 +614,7 @@ taz()
|
|||||||
disp E "Programs 'pigz' or 'gzip' are not installed, aborting."
|
disp E "Programs 'pigz' or 'gzip' are not installed, aborting."
|
||||||
return 127
|
return 127
|
||||||
}
|
}
|
||||||
local command="gzip --compress"
|
local command=gzip
|
||||||
local procopt=()
|
local procopt=()
|
||||||
[[ $2 -gt 1 ]] &&
|
[[ $2 -gt 1 ]] &&
|
||||||
disp W "gzip doesn't support multithreading, falling back to 1 thread." &&
|
disp W "gzip doesn't support multithreading, falling back to 1 thread." &&
|
||||||
@@ -601,10 +683,12 @@ taz()
|
|||||||
disp E "Invalid options, use \"taz --help\" to display usage."
|
disp E "Invalid options, use \"taz --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# See utaz: restore the getopt-normalised argument list, and keep every
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
# flag local so nothing leaks in from the caller's environment.
|
||||||
set -- "${_pargs[@]}"
|
local willrm="" keepall="" verbose="" quiet="" nproc="" complevel=""
|
||||||
|
eval set -- "$PARSED"
|
||||||
local compforms=()
|
local compforms=()
|
||||||
|
local _f _list
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help)
|
-h|--help)
|
||||||
@@ -614,7 +698,7 @@ taz()
|
|||||||
printf "\t-h, --help\tDisplay that help screen\n"
|
printf "\t-h, --help\tDisplay that help screen\n"
|
||||||
printf "\t-d, --delete\tDelete source file or directory after success\n"
|
printf "\t-d, --delete\tDelete source file or directory after success\n"
|
||||||
printf "\t-f, --format\tChose archive format in the given list. If several format are\n"
|
printf "\t-f, --format\tChose archive format in the given list. If several format are\n"
|
||||||
printf "\t\t\tgiven, the smalest is kept\n"
|
printf "\t\t\tgiven, the smalest produced archive is kept\n"
|
||||||
printf "\t-K, --keep-all\tKeep every produced version instead of only the smallest\n"
|
printf "\t-K, --keep-all\tKeep every produced version instead of only the smallest\n"
|
||||||
printf "\t-p, --parallel\tNumber of threads, or 'auto' for runtime CPU count\n"
|
printf "\t-p, --parallel\tNumber of threads, or 'auto' for runtime CPU count\n"
|
||||||
printf "\t-v, --verbose\tDisplay progress where possible\n"
|
printf "\t-v, --verbose\tDisplay progress where possible\n"
|
||||||
@@ -634,42 +718,41 @@ taz()
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
-d|--delete)
|
-d|--delete)
|
||||||
local willrm=1
|
willrm=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-f|--format)
|
-f|--format)
|
||||||
# Accept comma/space separated lists and repeated -f flags.
|
# Accept comma/space separated lists and repeated -f flags.
|
||||||
local _f
|
# "read -a" splits without pathname expansion (unlike an unquoted
|
||||||
# shellcheck disable=SC2086 # intentional word splitting on the list
|
# expansion, which would glob "*" against the current directory).
|
||||||
for _f in ${2//,/ }; do
|
IFS=', ' read -ra _list <<< "$2"
|
||||||
compforms+=("$_f")
|
compforms+=("${_list[@]}")
|
||||||
done
|
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-K|--keep-all)
|
-K|--keep-all)
|
||||||
local keepall=1
|
keepall=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-p|--parallel)
|
-p|--parallel)
|
||||||
local nproc=$2
|
nproc=$2
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-v|--verbose)
|
-v|--verbose)
|
||||||
local verbose=1
|
verbose=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-q|--quiet)
|
-q|--quiet)
|
||||||
local quiet=1
|
quiet=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-[1-9])
|
-[1-9])
|
||||||
local complevel="${1#-}"
|
complevel="${1#-}"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--)
|
--)
|
||||||
@@ -686,27 +769,32 @@ taz()
|
|||||||
# The remaining arguments after -- are the files/directories to process,
|
# The remaining arguments after -- are the files/directories to process,
|
||||||
# "." is used if none is given
|
# "." is used if none is given
|
||||||
local FILES=("$@")
|
local FILES=("$@")
|
||||||
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
|
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
|
||||||
|
|
||||||
[[ ! $compform ]] && local compform=${TAZ_DEFAULT_FORMAT:-lz}
|
# Defaults from the [compress] configuration for anything not set by flags
|
||||||
[[ ! $nproc ]] && local nproc=${TAZ_DEFAULT_THREADS:-auto}
|
[[ -z $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-auto}
|
||||||
[[ ! $complevel ]] && local complevel=${TAZ_DEFAULT_LEVEL:-6}
|
[[ -z $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6}
|
||||||
|
if [[ ! $complevel =~ ^[1-9]$ ]]; then
|
||||||
|
disp E "Invalid compression level '${complevel}' (expected 1 to 9)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
[[ -n $verbose && -n $quiet ]] && {
|
[[ -n $verbose && -n $quiet ]] && {
|
||||||
disp E "The --verbose and --quiet options can't be used together."
|
disp E "The --verbose and --quiet options can't be used together."
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
# disp() hides info messages when QUIET=1; being local, this only lasts
|
||||||
|
# for the duration of this call (dynamic scoping).
|
||||||
|
[[ -n $quiet ]] && local QUIET=1
|
||||||
|
|
||||||
# Fall back to the configured (or built-in) default format list.
|
# Fall back to the configured (or built-in) default format list.
|
||||||
local _f
|
|
||||||
if [[ ${#compforms[@]} -eq 0 ]]; then
|
if [[ ${#compforms[@]} -eq 0 ]]; then
|
||||||
local _deffmt=${TAZ_DEFAULT_FORMAT:-lz}
|
IFS=', ' read -ra _list <<< "${TAZ_DEFAULT_FORMAT:-lz}"
|
||||||
# shellcheck disable=SC2086 # intentional word splitting on the list
|
compforms+=("${_list[@]}")
|
||||||
for _f in ${_deffmt//,/ }; do
|
|
||||||
compforms+=("$_f")
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Reject unknown formats early so we never dispatch to a missing _do*.
|
# Reject unknown formats early so we never dispatch to a missing _do*.
|
||||||
|
# Membership test: the padded list is matched against " <fmt> " so that
|
||||||
|
# only whole words match (e.g. "z" or "lz xz" are rejected).
|
||||||
local _supported=" lz xz bz2 gz lzo tar "
|
local _supported=" lz xz bz2 gz lzo tar "
|
||||||
for _f in "${compforms[@]}"; do
|
for _f in "${compforms[@]}"; do
|
||||||
[[ $_supported == *" $_f "* ]] || {
|
[[ $_supported == *" $_f "* ]] || {
|
||||||
@@ -724,25 +812,62 @@ taz()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Map each format to the extension its compressor appends. "tar" performs
|
# Map each format to the extension its compressor appends to the input
|
||||||
# no compression, so its candidate is the tar (or source file) itself.
|
# name. "tar" is handled separately (its output is always "$item.tar").
|
||||||
local -A _taz_ext=( [lz]=lz [xz]=xz [bz2]=bz2 [gz]=gz [lzo]=lzo [tar]="" )
|
local -A _taz_ext=( [lz]=lz [xz]=xz [bz2]=bz2 [gz]=gz [lzo]=lzo )
|
||||||
|
|
||||||
|
local item
|
||||||
for item in "${FILES[@]}"; do
|
for item in "${FILES[@]}"; do
|
||||||
local donetar=0
|
local donetar=0
|
||||||
|
# Arguments naming the tar members; the default keeps the path as typed
|
||||||
|
local tar_src=()
|
||||||
|
|
||||||
|
# Normalise the name, since archive names are built as "$item.<ext>":
|
||||||
|
# - strip trailing slashes (shell completion adds them), otherwise
|
||||||
|
# "dir/.tar" would be created inside the directory being archived;
|
||||||
|
# - "." / ".." (or "x/..") have no usable name, so resolve them;
|
||||||
|
# - protect names starting with "-" from being read as options.
|
||||||
|
while [[ ${#item} -gt 1 && $item == */ ]]; do
|
||||||
|
item=${item%/}
|
||||||
|
done
|
||||||
|
case ${item##*/} in
|
||||||
|
.|..)
|
||||||
|
item=$(realpath -- "$item") || continue
|
||||||
|
# Store the bare directory name, not its absolute path
|
||||||
|
tar_src=(-C "${item%/*}" -- "${item##*/}")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [[ $item == / ]]; then
|
||||||
|
disp E "Refusing to archive the root directory, skipping."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[[ $item == -* ]] && item="./${item}"
|
||||||
|
[[ ${#tar_src[@]} -eq 0 ]] && tar_src=(-- "$item")
|
||||||
|
|
||||||
disp I "Processing $item..."
|
disp I "Processing $item..."
|
||||||
|
if [[ ! -e $item ]]; then
|
||||||
|
disp W "Path ${item} does not exist, skipping."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -d "$item" ]]; then
|
if [[ -d "$item" ]]; then
|
||||||
|
# Directories can only be compressed through an intermediate tar.
|
||||||
|
if [[ -e "$item.tar" ]]; then
|
||||||
|
disp E "${item}.tar already exists, not overwriting it, skipping."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
disp I "\t Creating $item.tar... "
|
disp I "\t Creating $item.tar... "
|
||||||
|
|
||||||
if ! tar -cf "$item.tar" "$item"; then
|
if ! tar -cf "$item.tar" "${tar_src[@]}"; then
|
||||||
disp E "tar file creation failed, skipping to next item."
|
disp E "tar file creation failed, skipping to next item."
|
||||||
|
rm -f -- "$item.tar"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local donetar=1
|
donetar=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# File actually handed to the compressors
|
||||||
local fname=$item
|
local fname=$item
|
||||||
[[ $donetar -gt 0 ]] && fname=$item.tar
|
[[ $donetar -gt 0 ]] && fname=$item.tar
|
||||||
|
|
||||||
@@ -752,11 +877,27 @@ taz()
|
|||||||
for fmt in "${compforms[@]}"; do
|
for fmt in "${compforms[@]}"; do
|
||||||
if [[ $fmt == tar ]]; then
|
if [[ $fmt == tar ]]; then
|
||||||
tar_requested=1
|
tar_requested=1
|
||||||
candidates+=("$fname")
|
if [[ $donetar -eq 0 ]]; then
|
||||||
|
# Plain file: "tar" means wrapping it in a tar. It must be
|
||||||
|
# a separate candidate, never the source file itself, as
|
||||||
|
# non-winning candidates get deleted below.
|
||||||
|
if [[ -e "$item.tar" ]]; then
|
||||||
|
disp W "${item}.tar already exists, skipping the tar format."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! tar -cf "$item.tar" "${tar_src[@]}"; then
|
||||||
|
disp W "tar creation failed, skipping this format."
|
||||||
|
rm -f -- "$item.tar"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
candidates+=("$item.tar")
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
disp I "\t Compressing archive with ${fmt}..."
|
disp I "\t Compressing archive with ${fmt}..."
|
||||||
|
# "_do<fmt>" is resolved dynamically (formats were validated above).
|
||||||
|
# "|| exec_code=$?" captures the status without tripping set -e.
|
||||||
exec_code=0
|
exec_code=0
|
||||||
"_do$fmt" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$?
|
"_do$fmt" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$?
|
||||||
case $exec_code in
|
case $exec_code in
|
||||||
@@ -785,6 +926,8 @@ taz()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Select the smallest archive among the produced candidates.
|
# Select the smallest archive among the produced candidates.
|
||||||
|
# _csize maps each existing candidate to its size in bytes; winner_size
|
||||||
|
# starts at -1 meaning "no candidate seen yet".
|
||||||
local winner="" winner_size=-1 sz
|
local winner="" winner_size=-1 sz
|
||||||
local -A _csize=()
|
local -A _csize=()
|
||||||
for c in "${candidates[@]}"; do
|
for c in "${candidates[@]}"; do
|
||||||
@@ -812,7 +955,7 @@ taz()
|
|||||||
if [[ $c == "$winner" ]]; then
|
if [[ $c == "$winner" ]]; then
|
||||||
disp "\t ${c} $(_taz_human "${_csize[$c]}") (kept, smallest)"
|
disp "\t ${c} $(_taz_human "${_csize[$c]}") (kept, smallest)"
|
||||||
else
|
else
|
||||||
disp "\t ${c} $(_taz_human "${_csize[$c]}") (+$(_taz_human "$(( _csize[$c] - winner_size ))"))"
|
disp "\t ${c} $(_taz_human "${_csize[$c]}") (+$(_taz_human "$(( ${_csize[$c]} - winner_size ))"))"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -837,7 +980,7 @@ taz()
|
|||||||
|
|
||||||
if [[ $willrm ]]; then
|
if [[ $willrm ]]; then
|
||||||
disp I "\t Deleting original source as asked... "
|
disp I "\t Deleting original source as asked... "
|
||||||
rm -r "$item"
|
rm -r -- "$item"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user