implement multiple archive creation

This commit is contained in:
fatalerrors
2026-07-08 18:36:30 +02:00
parent 022d4f9e77
commit 9ba36582e4

View File

@@ -115,9 +115,11 @@ utaz()
# shellcheck disable=SC2329
_unlha()
{
# lha typically extracts into the current directory
# We ensure it hits the target directory
(cd "$2" && lha -x "../$1") >/dev/null 2>&1
# lha extracts into the current directory, so we cd into the target
# directory and feed it an absolute path to the archive.
local src
src=$(realpath -- "$1") || return 1
(cd "$2" && lha -x "$src") >/dev/null 2>&1
}
local _ununace
@@ -146,8 +148,11 @@ utaz()
# shellcheck disable=SC2329
_uncpio()
{
# CPIO requires careful directory handling
(cd "$2" && cpio -id < "../$1") >/dev/null 2>&1
# cpio extracts into the current directory, so we cd into the target
# directory and read from an absolute path to the archive.
local src
src=$(realpath -- "$1") || return 1
(cd "$2" && cpio -id < "$src") >/dev/null 2>&1
}
local _uncabextract
@@ -249,8 +254,10 @@ utaz()
esac
fi
[[ -n ${createdir} && -n ${nodir} ]] && \
[[ -n ${createdir} && -n ${nodir} ]] && {
disp E "The --create-dir and --no-dir options are mutually exclusive."
return 1
}
for zitem in "${FILES[@]}"; do
# Build list of input files to process, with whitespace-safe handling.
@@ -353,6 +360,10 @@ utaz()
fi
disp I "Processing archive ${f} with ${extractor}..."
# Track whether we create the target directory so a corrupted
# archive never wipes a pre-existing directory of the same name.
local dir_created=0
[[ -d "${dir}" ]] || dir_created=1
if ! mkdir -p "${dir}"; then
disp E "The filesystem can't create directories, exit!" &&
return 1
@@ -373,7 +384,7 @@ utaz()
;;
*)
disp E "The compressed file ${f} seems corrupted, failed."
rm -rf "${dir}" >/dev/null 2>&1
[[ ${dir_created} -eq 1 ]] && rm -rf "${dir}" >/dev/null 2>&1
continue
;;
esac
@@ -444,6 +455,17 @@ taz()
printf "%s\n" "$cpus"
}
# Render a byte count as a human-readable size (B/KB/MB/GB/...), base 1024.
local _taz_human
# shellcheck disable=SC2329
_taz_human()
{
if command -v numfmt >/dev/null 2>&1; then
numfmt --to=iec --suffix=B -- "$1" 2>/dev/null && return 0
fi
printf "%sB\n" "$1"
}
local _doxz
# shellcheck disable=SC2329
_doxz()
@@ -575,6 +597,7 @@ taz()
return 1
fi
eval set -- "$PARSED"
local compforms=()
while true; do
case "$1" in
-h|--help)
@@ -608,7 +631,12 @@ taz()
;;
-f|--format)
local compform=$2
# Accept comma/space separated lists and repeated -f flags.
local _f
# shellcheck disable=SC2086 # intentional word splitting on the list
for _f in ${2//,/ }; do
compforms+=("$_f")
done
shift 2
;;
@@ -628,7 +656,7 @@ taz()
;;
-[1-9])
complevel="${1#-}"
local complevel="${1#-}"
shift
;;
--)
@@ -647,11 +675,32 @@ taz()
local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz}
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-auto}
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6}
[[ $verbose -gt 1 && $quiet -gt 1 ]] &&
[[ ! $compform ]] && local compform=${TAZ_DEFAULT_FORMAT:-lz}
[[ ! $nproc ]] && local nproc=${TAZ_DEFAULT_THREADS:-auto}
[[ ! $complevel ]] && local complevel=${TAZ_DEFAULT_LEVEL:-6}
[[ -n $verbose && -n $quiet ]] && {
disp E "The --verbose and --quiet options can't be used together."
return 1
}
# Fall back to the configured (or built-in) default format list.
local _f
if [[ ${#compforms[@]} -eq 0 ]]; then
local _deffmt=${TAZ_DEFAULT_FORMAT:-lz}
# shellcheck disable=SC2086 # intentional word splitting on the list
for _f in ${_deffmt//,/ }; do
compforms+=("$_f")
done
fi
# Reject unknown formats early so we never dispatch to a missing _do*.
local _supported=" lz xz bz2 gz lzo tar "
for _f in "${compforms[@]}"; do
[[ $_supported == *" $_f "* ]] || {
disp E "Unsupported format '${_f}' (supported: lz xz bz2 gz lzo tar)."
return 1
}
done
# Backward compatibility: 0 previously meant auto-detect.
[[ $nproc == 0 ]] && nproc=auto
@@ -662,6 +711,10 @@ taz()
return 1
fi
# Map each format to the extension its compressor appends. "tar" performs
# no compression, so its candidate is the tar (or source file) itself.
local -A _taz_ext=( [lz]=lz [xz]=xz [bz2]=bz2 [gz]=gz [lzo]=lzo [tar]="" )
for item in "${FILES[@]}"; do
local donetar=0
disp I "Processing $item..."
@@ -680,31 +733,91 @@ taz()
local fname=$item
[[ $donetar -gt 0 ]] && fname=$item.tar
# Skip compression part if tar is asked
if [[ $compform != "tar" ]]; then
disp I "\t Compressing archive..."
local exec_code=0
"_do$compform" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$?
[[ ! $exec_code -eq 0 ]] && case $exec_code in
# Compress the archive with every requested format; the smallest
# resulting file is kept and the others are discarded.
local candidates=() fmt exec_code aborted=0
for fmt in "${compforms[@]}"; do
if [[ $fmt == tar ]]; then
candidates+=("$fname")
continue
fi
disp I "\t Compressing archive with ${fmt}..."
exec_code=0
"_do$fmt" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$?
case $exec_code in
0)
candidates+=("$fname.${_taz_ext[$fmt]}")
;;
127)
disp E "Compression program unavailable, aborting."
return 127
disp E "Compression program for '${fmt}' unavailable, aborting."
aborted=1
break
;;
*)
disp E "Compression program returned an error, not deleting anything if asked, skipping to next item."
continue
disp W "Compression with '${fmt}' failed, skipping this format."
;;
esac
done
[[ $donetar -gt 0 ]] && rm "$fname"
# Remove partial output produced before an unrecoverable abort.
local c
if [[ $aborted -eq 1 ]]; then
for c in "${candidates[@]}"; do
[[ $c != "$fname" ]] && rm -f -- "$c"
done
[[ $donetar -gt 0 ]] && rm -f -- "$fname"
return 127
fi
# Select the smallest archive among the produced candidates.
local winner="" winner_size=-1 sz
local -A _csize=()
for c in "${candidates[@]}"; do
[[ -f $c ]] || continue
sz=$(stat -c%s -- "$c" 2>/dev/null) || continue
_csize["$c"]=$sz
if [[ $winner_size -lt 0 || $sz -lt $winner_size ]]; then
winner_size=$sz
winner=$c
fi
done
if [[ -z $winner ]]; then
disp E "No archive could be produced for ${item}, skipping to next item."
[[ $donetar -gt 0 ]] && rm -f -- "$fname"
continue
fi
# In verbose mode, list every produced archive with its size and how
# much larger it is than the smallest one that will be kept.
if [[ -n $verbose && ${#_csize[@]} -gt 0 ]]; then
disp I "\t Produced archives:"
for c in "${candidates[@]}"; do
[[ -n ${_csize[$c]+x} ]] || continue
if [[ $c == "$winner" ]]; then
disp "\t ${c} $(_taz_human "${_csize[$c]}") (kept, smallest)"
else
disp "\t ${c} $(_taz_human "${_csize[$c]}") (+$(_taz_human "$(( _csize[$c] - winner_size ))"))"
fi
done
fi
# Discard every archive but the smallest, then drop the intermediate
# tar unless it is the kept archive.
for c in "${candidates[@]}"; do
[[ $c != "$winner" ]] && rm -f -- "$c"
done
[[ $donetar -gt 0 && $fname != "$winner" ]] && rm -f -- "$fname"
[[ ${#compforms[@]} -gt 1 ]] &&
disp I "\t Kept smallest archive: ${winner} ($(_taz_human "$winner_size"))."
if [[ $willrm ]]; then
disp I "\t Deleting original source as asked... "
rm -r "$item"
fi
done
unset quiet
}
export -f taz
# ------------------------------------------------------------------------------