Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ zxccat logs-1.zxc logs-2.zxc | grep ERROR
# Read a compressed file page by page in less (POSIX installs)
zxcless app.log.zxc

# Search compressed files with grep's options (POSIX installs)
zxcgrep -n ERROR logs-*.zxc

# Benchmark mode (testing speed on your machine)
zxc -b assets.tar
```
Expand Down
6 changes: 3 additions & 3 deletions cmake/zxcInstall.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ if(ZXC_INSTALL)
zxc \"\$ENV{DESTDIR}${ZXC_SYMLINK_BINDIR}/${alias}\")
")
endforeach()
# "zxcless": less(1) on .zxc files, a shell script.
install(PROGRAMS src/cli/zxcless DESTINATION ${CMAKE_INSTALL_BINDIR})
foreach(alias unzxc zxccat zxcless)
# "zxcless" and "zxcgrep": less(1) and grep(1) on .zxc files, shell scripts.
install(PROGRAMS src/cli/zxcless src/cli/zxcgrep DESTINATION ${CMAKE_INSTALL_BINDIR})
foreach(alias unzxc zxccat zxcless zxcgrep)
install(CODE "
execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink
zxc.1 \"\$ENV{DESTDIR}${ZXC_SYMLINK_MANDIR}/man1/${alias}.1\")
Expand Down
6 changes: 3 additions & 3 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,12 @@ if not meson.is_subproject() and get_option('build_cli')
install_dir : get_option('bindir'),
)
endforeach
# "zxcless": less(1) on .zxc files, a shell script.
install_data('src/cli/zxcless',
# "zxcless" and "zxcgrep": less(1) and grep(1) on .zxc files, shell scripts.
install_data('src/cli/zxcless', 'src/cli/zxcgrep',
install_dir : get_option('bindir'),
install_mode : 'rwxr-xr-x',
)
foreach alias : ['unzxc', 'zxccat', 'zxcless']
foreach alias : ['unzxc', 'zxccat', 'zxcless', 'zxcgrep']
install_symlink(alias + '.1',
pointing_to : 'zxc.1',
install_dir : get_option('mandir') / 'man1',
Expand Down
6 changes: 6 additions & 0 deletions src/cli/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -1449,6 +1450,11 @@ int main(int argc, char** argv) {
int to_stdout = 0;
int cat_mode = 0;

#ifndef _WIN32
// Reader gone (zxc -dc f | head): die quietly, even if the parent ignored SIGPIPE
signal(SIGPIPE, SIG_DFL);
#endif

/* As "unzxc", default to decompression; as "zxccat", to zxc -dcf on every
* argument. An explicit mode flag below still wins. */
{
Expand Down
31 changes: 26 additions & 5 deletions src/cli/zxc.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
.\"
.TH "zxc" "1" "" "zxc" "User Commands"
.SH NAME
\f[B]zxc\f[R], \f[B]unzxc\f[R], \f[B]zxccat\f[R], \f[B]zxcless\f[R] \-
Compress or decompress .zxc files
\f[B]zxc\f[R], \f[B]unzxc\f[R], \f[B]zxccat\f[R], \f[B]zxcless\f[R],
\f[B]zxcgrep\f[R] \- Compress or decompress .zxc files
.SH SYNOPSIS
\f[B]zxc\f[R] [\f[I]OPTIONS\f[R]] [\f[I]INPUT\-FILE\f[R]]
[\f[I]OUTPUT\-FILE\f[R]]
Expand All @@ -15,15 +15,33 @@ Compress or decompress .zxc files
input that does not start like a ZXC archive (see \f[B]\-f\f[R]) comes
out unchanged.
.PP
\f[B]zxcless\f[R] [\f[I]LESS\-OPTIONS\f[R]] [\f[I]FILE\f[R]...]
\f[B]zxcless\f[R] [\f[B]\-\-dict=\f[R]\f[I]FILE\f[R]]
[\f[I]LESS\-OPTIONS\f[R]] [\f[I]FILE\f[R]...]
shows files, or standard input, in \f[B]less\f[R](1), decompressing ZXC
archives on the fly; other input displays unchanged.
\f[B]\-\-dict\f[R] supplies the dictionary of archives that need one
(see \f[B]\-D\f[R]).
The \f[B]ZXC\f[R] environment variable names the \f[B]zxc\f[R] binary to
use.
.PP
\f[B]zxcgrep\f[R] [\f[B]\-\-dict=\f[R]\f[I]FILE\f[R]]
[\f[I]GREP\-OPTIONS\f[R]] [\f[B]\-e\f[R]] \f[I]PATTERN\f[R]
[\f[I]FILE\f[R]...]
runs \f[B]grep\f[R](1) on files, or standard input, decompressing ZXC
archives on the fly; other input is searched unchanged.
\f[B]\-\-dict\f[R] supplies the dictionary of archives that need one
(see \f[B]\-D\f[R]).
Options go before the pattern; those that select files (\f[B]\-r\f[R],
\f[B]\-d\f[R], \f[B]\-D\f[R], \f[B]\-\-include\f[R],
\f[B]\-\-exclude\f[R]) and \f[B]\-f \-\f[R] are not supported.
The exit status is grep\(aqs: 0 if a line matched, 1 if none did, 2 on
any error, including a damaged archive.
\f[B]ZXC\f[R] and \f[B]GREP\f[R] name the binaries to use; grep must
support \f[B]\-\-label\f[R] (GNU and BSD grep do).
.PP
\f[B]unzxc\f[R] and \f[B]zxccat\f[R] are symlinks to \f[B]zxc\f[R], and
\f[B]zxcless\f[R] is a shell script; all three are installed on POSIX
systems only.
\f[B]zxcless\f[R] and \f[B]zxcgrep\f[R] are shell scripts; all four are
installed on POSIX systems only.
.SH DESCRIPTION
\f[B]zxc\f[R] is a command\-line interface for the ZXC compression
library, a high\-performance lossless compression algorithm optimized
Expand Down Expand Up @@ -284,6 +302,9 @@ zxccat part1.zxc part2.zxc | grep ERROR
.PP
\f[B]Read a compressed log page by page:\f[R] zxcless app.log.zxc
.PP
\f[B]Search compressed logs, with file names and line numbers:\f[R]
zxcgrep \-n ERROR logs\-*.zxc
.PP
\f[B]Compress multiple files independently:\f[R] zxc \-m file1.txt
file2.txt file3.txt
.PP
Expand Down
11 changes: 8 additions & 3 deletions src/cli/zxc.1.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# zxc(1)

## NAME
**zxc**, **unzxc**, **zxccat**, **zxcless** - Compress or decompress .zxc files
**zxc**, **unzxc**, **zxccat**, **zxcless**, **zxcgrep** - Compress or decompress .zxc files

## SYNOPSIS
**zxc** [*OPTIONS*] [*INPUT-FILE*] [*OUTPUT-FILE*]
Expand All @@ -10,9 +10,11 @@

**zxccat** is equivalent to **zxc -dcf** applied to every *INPUT-FILE* in turn, writing them all to standard output; input that does not start like a ZXC archive (see **-f**) comes out unchanged.

**zxcless** [*LESS-OPTIONS*] [*FILE*...] shows files, or standard input, in **less**(1), decompressing ZXC archives on the fly; other input displays unchanged. The **ZXC** environment variable names the **zxc** binary to use.
**zxcless** [**--dict=***FILE*] [*LESS-OPTIONS*] [*FILE*...] shows files, or standard input, in **less**(1), decompressing ZXC archives on the fly; other input displays unchanged. **--dict** supplies the dictionary of archives that need one (see **-D**). The **ZXC** environment variable names the **zxc** binary to use.

**unzxc** and **zxccat** are symlinks to **zxc**, and **zxcless** is a shell script; all three are installed on POSIX systems only.
**zxcgrep** [**--dict=***FILE*] [*GREP-OPTIONS*] [**-e**] *PATTERN* [*FILE*...] runs **grep**(1) on files, or standard input, decompressing ZXC archives on the fly; other input is searched unchanged. **--dict** supplies the dictionary of archives that need one (see **-D**). Options go before the pattern; those that select files (**-r**, **-d**, **-D**, **--include**, **--exclude**) and **-f -** are not supported. The exit status is grep's: 0 if a line matched, 1 if none did, 2 on any error, including a damaged archive. **ZXC** and **GREP** name the binaries to use; grep must support **--label** (GNU and BSD grep do).

**unzxc** and **zxccat** are symlinks to **zxc**, and **zxcless** and **zxcgrep** are shell scripts; all four are installed on POSIX systems only.

## DESCRIPTION
**zxc** is a command-line interface for the ZXC compression library, a high-performance lossless compression algorithm optimized for maximum decompression throughput.
Expand Down Expand Up @@ -143,6 +145,9 @@ Archives can be concatenated (`cat a.zxc b.zxc > ab.zxc`): **-d** decodes them o
**Read a compressed log page by page:**
zxcless app.log.zxc

**Search compressed logs, with file names and line numbers:**
zxcgrep -n ERROR logs-*.zxc

**Compress multiple files independently:**
zxc -m file1.txt file2.txt file3.txt

Expand Down
133 changes: 133 additions & 0 deletions src/cli/zxcgrep
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
# ZXC - High-performance lossless compression
#
# Copyright (c) Bertrand Lebonnois and contributors.
#
# zxcgrep: grep(1) on .zxc files, decompressed on the fly, other files as is.
# grep's options go before the pattern; those that pick files are refused.
# --dict=FILE names a zxc dictionary.

zxc=${ZXC:-zxc}
grep=${GREP:-grep}

usage() {
echo "Usage: zxcgrep [GREP-OPTIONS] [-e] PATTERN [FILE...]"
}

die() {
echo "zxcgrep: $1" >&2
exit 2
}

# qv = $1 quoted for eval
q() {
qs=$1 qv=''
while :; do
case $qs in
*\'*) qv="$qv${qs%%\'*}'\\''"; qs=${qs#*\'} ;;
*) qv="'$qv$qs'"; return ;;
esac
done
}

add() {
q "$1"
opts="$opts $qv"
}

# grep's stdin is the data
nostdin='-f -: patterns from standard input not supported'

opts='' pat='' no_name='' quiet='' silent='' dict=''
while [ "$#" -gt 0 ]; do
case $1 in
--) shift; break ;;
--help) usage; exit 0 ;;
--recursive | --dereference-recursive | --directories* | --devices* | \
--include* | --exclude*) die "${1%%=*}: not supported" ;;
--file=-) die "$nostdin" ;;
--dict=*) dict=${1#--dict=}; shift; continue ;;
--dict)
[ "$#" -ge 2 ] || die "$1: missing argument"
dict=$2; shift 2; continue ;;
--regexp=* | --file=*) pat=1 ;;
--regexp | --file)
pat=1
[ "$#" -ge 2 ] || die "$1: missing argument"
[ "$1 $2" != "--file -" ] || die "$nostdin"
add "$1"; shift ;;
--after-context | --before-context | --context | --max-count | --label | \
--binary-files | --group-separator)
[ "$#" -ge 2 ] || die "$1: missing argument"
add "$1"; shift ;;
--quiet | --silent) quiet=1 ;;
--no-messages) silent=1 ;;
--no-filename) no_name=1 ;;
--*) ;;
-?*)
# -inA 3, -ie PAT: a letter taking a value ends the cluster
o=${1#-}
while [ -n "$o" ]; do
c=${o%"${o#?}"}; o=${o#?}
case $c in
[rRdD]) die "-$c: not supported" ;;
[ef]) pat=1 ;;
h) no_name=1 ;;
q) quiet=1 ;;
s) silent=1 ;;
esac
case $c in
[ABCefm])
if [ -z "$o" ]; then
[ "$#" -ge 2 ] || die "-$c: missing argument"
o=$2; add "$1"; shift
fi
[ "$c$o" != f- ] || die "$nostdin"
break ;;
esac
done ;;
*) break ;;
esac
add "$1"; shift
done

if [ -z "$pat" ]; then
[ "$#" -gt 0 ] || { usage >&2; exit 2; }
add -e; add "$1"; shift
fi
[ "$#" -gt 0 ] || set -- -
[ "$#" -gt 1 ] && [ -z "$no_name" ] && opts="-H$opts"

sigpipe() { [ "$1" -gt 128 ] && [ "$(kill -l "$1" 2>/dev/null)" = PIPE ]; }

unzxc() {
if [ -n "$dict" ]; then "$zxc" -dcf -D "$dict" -- "$1"; else "$zxc" -dcf -- "$1"; fi
}

# Status as grep: 0 match, 1 none, 2 error. -s silences zxc too.
if [ -n "$silent" ]; then exec 5>/dev/null; else exec 5>&2; fi
exec 3>&1
st=1
for f do
label=''
if [ "$f" != - ]; then q "--label=$f"; label=$qv; fi
# Status: grep's from the pipeline, zxc's on fd 4
z=$(
exec 4>&1
eval "set -- $label $opts -"
{ unzxc "$f" 2>&5 4>&-; echo "$?" >&4; } | "$grep" "$@" 4>&- >&3
)
g=$?
# grep's errors (bad pattern, output closed) hold for every file
[ "$g" -gt 1 ] && exit "$g"
# -q: the first match settles it
[ "$g" -eq 0 ] && [ -n "$quiet" ] && exit 0
# SIGPIPE on zxc: grep stopped reading (-l, -m...)
if [ "$z" -ne 0 ] && ! sigpipe "$z"; then
st=2
elif [ "$g" -eq 0 ] && [ "$st" -eq 1 ]; then
st=0
fi
done
exit "$st"
25 changes: 21 additions & 4 deletions src/cli/zxcless
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,35 @@
# Copyright (c) Bertrand Lebonnois and contributors.
#
# zxcless: less(1) on .zxc files, decompressed on the fly by this script rerun as
# the LESSOPEN filter. Needs less 451+.
# the LESSOPEN filter. Needs less 451+. --dict=FILE names a zxc dictionary.

zxc=${ZXC:-zxc}

if [ "$#" -eq 2 ] && [ "$1" = --zxcless-filter ]; then
f=$2
set -- -dcq
[ -z "$ZXCLESS_DICT" ] || set -- "$@" -D "$ZXCLESS_DICT"
# -f for stdin only: a named file zxc can't decode, less opens itself.
if [ "$2" = - ]; then
exec "$zxc" -dcfq -
if [ "$f" = - ]; then
exec "$zxc" -f "$@" -
fi
exec "$zxc" -dcq -- "$2"
exec "$zxc" "$@" -- "$f"
fi

# --dict is ours, the rest is less's: the filter reads it from the environment
want=''
for a do
shift
if [ -n "$want" ]; then ZXCLESS_DICT=$a want=''; continue; fi
case $a in
--dict=*) ZXCLESS_DICT=${a#--dict=}; continue ;;
--dict) want=1; continue ;;
esac
set -- "$@" "$a"
done
[ -z "$want" ] || { echo "zxcless: --dict: missing argument" >&2; exit 1; }
export ZXCLESS_DICT

# "||": empty output with status 0 (an empty archive) shows empty; "-": stdin too.
export LESSOPEN="||-sh \"$0\" --zxcless-filter %s"
unset LESSCLOSE
Expand Down
Loading
Loading