From 6bfc2804abc757aa077dca37bfb052214ecc9842 Mon Sep 17 00:00:00 2001 From: fatalerrors Date: Tue, 13 Oct 2026 11:21:53 +0200 Subject: [PATCH] utaz: add case management sensitivity support --- README.md | 5 ++-- doc/CHANGELOG.md | 23 ++++++++++++++++++ doc/profile.conf.example | 5 ++++ doc/profile.conf.fatalerrors | 5 ++++ profile.d/compress.sh | 47 ++++++++++++++++++++++++++++++------ 5 files changed, 76 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5e26f70..790be5d 100644 --- a/README.md +++ b/README.md @@ -215,11 +215,12 @@ change the default without having to pass flags every time. | Key | Default | Description | | --- | --- | --- | -| `TAZ_DEFAULT_FORMAT` | `tar.gz` | Archive format for `taz` (`tar.gz`, `tar.bz2`, `tar.xz`, `zip`, …) | +| `TAZ_DEFAULT_FORMAT` | `lz` | Archive format(s) for `taz`: `lz`, `xz`, `bz2`, `gz`, `lzo`, `tar`; comma-separated list keeps the smallest result | | `TAZ_DEFAULT_THREADS` | `auto` | Compression threads (`auto` = runtime CPU count, or explicit positive integer) | | `TAZ_DEFAULT_LEVEL` | `6` | Compression level (1–9) | | `UTAZ_DEFAULT_DELETE` | `0` | Set to `1` to delete the source archive after extraction | -| `UTAZ_DEFAULT_DIR_MODE` | `0` | Set to `1` to always extract into a subdirectory | +| `UTAZ_DEFAULT_DIR_MODE` | `auto` | Host directory policy: `auto` (only when the archive has no single top-level directory), `always` or `never` | +| `UTAZ_DEFAULT_IGNORE_CASE` | `0` | Set to `1` to match archive extensions case-insensitively (`.ZIP`, `.Tar.GZ`). Per call: `utaz -i` / `utaz -s` | **`[filefct]`** — file utilities diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index ff5d111..6113250 100755 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -7,6 +7,29 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`). --- +## [Unreleased] + +### Added + +- `utaz -i` / `--ignore-case` and `-s` / `--case-sensitive` switches to match + archive extensions case-insensitively or not; the default is set by the new + `UTAZ_DEFAULT_IGNORE_CASE` key of the `[compress]` section (default `0`). +- `utaz` supports bare `.zst` files and `.tzst` archives; `taz -q` is now + effective. + +### Fixed + +- `taz`: a plain file with the `tar` format could be deleted instead of being + archived; existing `.tar` files are no longer overwritten; trailing + slashes, `.`/`..` and names starting with `-` are handled; the `gzip` + fallback (no `pigz`) works again. +- `utaz`: single top-level directory flattening, `--no-dir` target location, + `foo.tar.gz` host directory name and `.tar.lz` extraction. +- `compress.sh`: option flags and loop variables no longer leak into the + interactive shell. + +--- + ## [4.1.0] — 2026-05-07 ### Added diff --git a/doc/profile.conf.example b/doc/profile.conf.example index a771d02..31cf713 100755 --- a/doc/profile.conf.example +++ b/doc/profile.conf.example @@ -49,6 +49,11 @@ PAGER=less # never — always flatten into the current directory #UTAZ_DEFAULT_DIR_MODE=auto +# utaz: Match archive extensions case-insensitively, so that FOO.ZIP or +# bar.Tar.GZ are recognised (0=no, 1=yes). Overridden per call with +# -i/--ignore-case or -s/--case-sensitive. +#UTAZ_DEFAULT_IGNORE_CASE=0 + # ============================================================================== [debug] # (no configurable keys yet) diff --git a/doc/profile.conf.fatalerrors b/doc/profile.conf.fatalerrors index 00c6898..2615789 100755 --- a/doc/profile.conf.fatalerrors +++ b/doc/profile.conf.fatalerrors @@ -36,6 +36,11 @@ TERM=xterm-256color # never — always flatten extraction into the current directory #UTAZ_DEFAULT_DIR_MODE=auto +# utaz: Match archive extensions case-insensitively, so that FOO.ZIP or +# bar.Tar.GZ are recognised (0=no, 1=yes). Overridden per call with +# -i/--ignore-case or -s/--case-sensitive. +#UTAZ_DEFAULT_IGNORE_CASE=0 + [debug] # Section used by debug.sh (nothing yet) diff --git a/profile.d/compress.sh b/profile.d/compress.sh index a2645c7..6af5fb2 100644 --- a/profile.d/compress.sh +++ b/profile.d/compress.sh @@ -44,6 +44,9 @@ load_conf "compress" # -d, --delete If decompression succeeded, delete the source file # -c, --create-dir Always create a host directory # -n, --no-dir Never create a host directory +# -i, --ignore-case Match archive extensions case-insensitively (.ZIP, .Tar.GZ) +# -s, --case-sensitive Match archive extensions case-sensitively (overrides the +# UTAZ_DEFAULT_IGNORE_CASE configuration key) utaz() { # Extractor helpers. Every one takes the archive path as $1 and an existing @@ -156,7 +159,11 @@ utaz() { # A tar.zst/.tzst is untarred; a bare .zst is a single compressed file # and is simply decompressed into the target directory. - case $1 in + # Called from utaz: "icase" is utaz's local, visible here through + # dynamic scoping. Lowercase the name so the match follows the mode. + local key=$1 + [[ -n $icase ]] && key=${key,,} + case $key in *.tar.zst|*.tzst) tar --zstd -xf "$1" -C "$2" ;; @@ -236,7 +243,7 @@ utaz() ) local PARSED - PARSED=$(getopt -o hdcn --long help,delete,create-dir,no-dir -n 'utaz' -- "$@") + PARSED=$(getopt -o hdcnis --long help,delete,create-dir,no-dir,ignore-case,case-sensitive -n 'utaz' -- "$@") # shellcheck disable=SC2181 # getopt return code is checked immediately after if [ $? -ne 0 ]; then disp E "Invalid options, use \"utaz --help\" to display usage." @@ -245,7 +252,7 @@ utaz() # getopt emits a shell-quoted, normalised argument list ("--" terminates the # options); re-evaluating it restores the arguments with spaces and quotes # intact. Declared local so flags never leak in from the caller's environment. - local willrm="" createdir="" nodir="" + local willrm="" createdir="" nodir="" icase="" csens="" eval set -- "$PARSED" while true; do case "$1" in @@ -257,7 +264,9 @@ utaz() printf "\t-h, --help\t\tDisplay that help screen\n" printf "\t-d, --delete\t\tIf decompression succeeded, delete the source file\n" printf "\t-c, --create-dir\tAlways create a host directory\n" - printf "\t-n, --no-dir\t\tNever create a host directory\n\n" + printf "\t-n, --no-dir\t\tNever create a host directory\n" + printf "\t-i, --ignore-case\tMatch archive extensions case-insensitively\n" + printf "\t-s, --case-sensitive\tMatch archive extensions case-sensitively (default)\n\n" printf "Supported archive format:\n" printf "\t- zip\n" printf "\t- tar.gz, .tgz\n" @@ -290,6 +299,14 @@ utaz() nodir=1 shift ;; + -i|--ignore-case) + icase=1 + shift + ;; + -s|--case-sensitive) + csens=1 + shift + ;; --) shift break @@ -319,6 +336,16 @@ utaz() return 1 } + # Extension matching: the flags win over the configured default. After this + # block "icase" is non-empty if and only if matching is case-insensitive. + [[ -n $icase && -n $csens ]] && { + disp E "The --ignore-case and --case-sensitive options are mutually exclusive." + return 1 + } + if [[ -z $icase && -z $csens && ${UTAZ_DEFAULT_IGNORE_CASE:-0} -eq 1 ]]; then + icase=1 + fi + # Loop variables are local: this function lives in the interactive shell. local zitem f for zitem in "${FILES[@]}"; do @@ -343,13 +370,19 @@ utaz() fi for f in "${targets[@]}"; do + # "key" is the name the extension patterns are matched against: + # lowercased in ignore-case mode, untouched otherwise. + local key=$f + [[ -n $icase ]] && key=${f,,} + # Host directory name: archive name without its extension. A # trailing ".tar" is dropped too, so foo.tar.gz gives "foo", not - # "foo.tar". + # "foo.tar". The ".tar" test uses "key" so it follows the case mode + # (the extension is 4 characters either way). local dir="${f%.*}" - [[ $dir == *.tar ]] && dir="${dir%.tar}" + [[ ${key%.*} == *.tar ]] && dir="${dir%.*}" local extractor="" - case "$f" in + case "$key" in *.zip) extractor="_ununzip" ;;