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 # shellcheck disable=SC2329
_unlha() _unlha()
{ {
# lha typically extracts into the current directory # lha extracts into the current directory, so we cd into the target
# We ensure it hits the target directory # directory and feed it an absolute path to the archive.
(cd "$2" && lha -x "../$1") >/dev/null 2>&1 local src
src=$(realpath -- "$1") || return 1
(cd "$2" && lha -x "$src") >/dev/null 2>&1
} }
local _ununace local _ununace
@@ -146,8 +148,11 @@ utaz()
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_uncpio() _uncpio()
{ {
# CPIO requires careful directory handling # cpio extracts into the current directory, so we cd into the target
(cd "$2" && cpio -id < "../$1") >/dev/null 2>&1 # 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 local _uncabextract
@@ -249,8 +254,10 @@ utaz()
esac esac
fi fi
[[ -n ${createdir} && -n ${nodir} ]] && \ [[ -n ${createdir} && -n ${nodir} ]] && {
disp E "The --create-dir and --no-dir options are mutually exclusive." disp E "The --create-dir and --no-dir options are mutually exclusive."
return 1
}
for zitem in "${FILES[@]}"; do for zitem in "${FILES[@]}"; do
# Build list of input files to process, with whitespace-safe handling. # Build list of input files to process, with whitespace-safe handling.
@@ -353,6 +360,10 @@ utaz()
fi fi
disp I "Processing archive ${f} with ${extractor}..." 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 if ! mkdir -p "${dir}"; then
disp E "The filesystem can't create directories, exit!" && disp E "The filesystem can't create directories, exit!" &&
return 1 return 1
@@ -373,7 +384,7 @@ utaz()
;; ;;
*) *)
disp E "The compressed file ${f} seems corrupted, failed." 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 continue
;; ;;
esac esac
@@ -444,6 +455,17 @@ taz()
printf "%s\n" "$cpus" 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 local _doxz
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_doxz() _doxz()
@@ -575,6 +597,7 @@ taz()
return 1 return 1
fi fi
eval set -- "$PARSED" eval set -- "$PARSED"
local compforms=()
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -608,7 +631,12 @@ taz()
;; ;;
-f|--format) -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 shift 2
;; ;;
@@ -628,7 +656,7 @@ taz()
;; ;;
-[1-9]) -[1-9])
complevel="${1#-}" local complevel="${1#-}"
shift shift
;; ;;
--) --)
@@ -647,11 +675,32 @@ taz()
local FILES=("$@") local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".") [[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz} [[ ! $compform ]] && local compform=${TAZ_DEFAULT_FORMAT:-lz}
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-auto} [[ ! $nproc ]] && local nproc=${TAZ_DEFAULT_THREADS:-auto}
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6} [[ ! $complevel ]] && local complevel=${TAZ_DEFAULT_LEVEL:-6}
[[ $verbose -gt 1 && $quiet -gt 1 ]] && [[ -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
}
# 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. # Backward compatibility: 0 previously meant auto-detect.
[[ $nproc == 0 ]] && nproc=auto [[ $nproc == 0 ]] && nproc=auto
@@ -662,6 +711,10 @@ taz()
return 1 return 1
fi 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 for item in "${FILES[@]}"; do
local donetar=0 local donetar=0
disp I "Processing $item..." disp I "Processing $item..."
@@ -680,31 +733,91 @@ taz()
local fname=$item local fname=$item
[[ $donetar -gt 0 ]] && fname=$item.tar [[ $donetar -gt 0 ]] && fname=$item.tar
# Skip compression part if tar is asked # Compress the archive with every requested format; the smallest
if [[ $compform != "tar" ]]; then # resulting file is kept and the others are discarded.
disp I "\t Compressing archive..." local candidates=() fmt exec_code aborted=0
local exec_code=0 for fmt in "${compforms[@]}"; do
"_do$compform" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$? if [[ $fmt == tar ]]; then
[[ ! $exec_code -eq 0 ]] && case $exec_code in 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) 127)
disp E "Compression program unavailable, aborting." disp E "Compression program for '${fmt}' unavailable, aborting."
return 127 aborted=1
break
;; ;;
*) *)
disp E "Compression program returned an error, not deleting anything if asked, skipping to next item." disp W "Compression with '${fmt}' failed, skipping this format."
continue
;; ;;
esac 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 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 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
unset quiet
} }
export -f taz export -f taz
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------