diff options
-rw-r--r-- | ChangeLog | 8 | ||||
-rw-r--r-- | INSTALL | 9 | ||||
-rw-r--r-- | Makefile.in | 91 | ||||
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | README | 3 | ||||
-rw-r--r-- | arg_parser.cc | 193 | ||||
-rw-r--r-- | arg_parser.h | 95 | ||||
-rwxr-xr-x | configure | 12 | ||||
-rw-r--r-- | doc/zcat.1 | 4 | ||||
-rw-r--r-- | doc/zdiff.1 | 4 | ||||
-rw-r--r-- | doc/zgrep.1 | 7 | ||||
-rw-r--r-- | doc/zutils.1 | 46 | ||||
-rw-r--r-- | doc/zutils.info | bin | 5554 -> 5792 bytes | |||
-rw-r--r-- | doc/zutils.texinfo | 14 | ||||
-rw-r--r-- | main.cc | 688 | ||||
-rwxr-xr-x | testsuite/check.sh | 21 | ||||
-rw-r--r-- | zcat.in | 40 | ||||
-rw-r--r-- | zcmp.in | 3 | ||||
-rw-r--r-- | zdiff.in | 58 | ||||
-rw-r--r-- | zegrep.in | 3 | ||||
-rw-r--r-- | zfgrep.in | 3 | ||||
-rw-r--r-- | zgrep.in | 51 |
22 files changed, 1234 insertions, 125 deletions
@@ -1,4 +1,10 @@ -2009-08-?? Antonio Diaz Diaz <ant_diaz@teleline.es> +2009-09-17 Antonio Diaz Diaz <ant_diaz@teleline.es> + + * Version 0.4 released. + * Added two new utilities; zegrep and zfgrep. + * Added zutils executable which recognizes file formats. + +2009-08-28 Antonio Diaz Diaz <ant_diaz@teleline.es> * Version 0.3 released. * Removed default compressor. @@ -1,7 +1,12 @@ Requirements ------------ -You will need a Posix compatible shell command interpreter like Bash or -Dash. +You will need a C++ compiler. +I use gcc 4.3.4 and 3.3.6, but the code should compile with any +standards compliant compiler. +Gcc is available at http://gcc.gnu.org. + +You will also need a Posix compatible shell command interpreter like +Bash or Dash. Bash is available at http://gnu.org/software/bash/bash.html. Dash is available at http://packages.debian.org/stable/dash/. diff --git a/Makefile.in b/Makefile.in index c93cf1a..23330c1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,5 +1,5 @@ -DISTNAME = $(pkgname)-$(pkgversion) +DISTNAME = $(progname)-$(progversion) INSTALL = install INSTALL_PROGRAM = $(INSTALL) -p -m 755 INSTALL_SCRIPT = $(INSTALL) -p -m 755 @@ -7,17 +7,24 @@ INSTALL_DATA = $(INSTALL) -p -m 644 INSTALL_DIR = $(INSTALL) -d -m 755 SHELL = /bin/sh -scripts = zcat zcmp zdiff zgrep +objs = arg_parser.o main.o +scripts = zcat zcmp zdiff zegrep zfgrep zgrep .PHONY : all install install-info install-man install-strip \ uninstall uninstall-info uninstall-man \ doc info man check dist clean distclean -all : $(scripts) +all : $(progname) $(scripts) + +$(progname) : $(objs) + $(CXX) $(LDFLAGS) -o $(progname) $(objs) + +$(progname)_profiled : $(objs) + $(CXX) $(LDFLAGS) -pg -o $(progname)_profiled $(objs) zcat : zcat.in - sed -e 's|VERSION|$(pkgversion)|g' $(VPATH)/zcat.in > zcat + sed -e 's,VERSION,$(progversion),g' $(VPATH)/zcat.in > zcat chmod a+x zcat zcmp : zcmp.in @@ -25,23 +32,44 @@ zcmp : zcmp.in chmod a+x zcmp zdiff : zdiff.in - sed -e 's|VERSION|$(pkgversion)|g' $(VPATH)/zdiff.in > zdiff + sed -e 's,VERSION,$(progversion),g' $(VPATH)/zdiff.in > zdiff chmod a+x zdiff +zegrep : zegrep.in + cat $(VPATH)/zegrep.in > zegrep + chmod a+x zegrep + +zfgrep : zfgrep.in + cat $(VPATH)/zfgrep.in > zfgrep + chmod a+x zfgrep + zgrep : zgrep.in - sed -e 's|VERSION|$(pkgversion)|g' $(VPATH)/zgrep.in > zgrep + sed -e 's,VERSION,$(progversion),g' $(VPATH)/zgrep.in > zgrep chmod a+x zgrep -$(scripts) : Makefile +main.o : main.cc + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROGVERSION=\"$(progversion)\" -c -o $@ $< + +%.o : %.cc + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +$(objs) : Makefile +$(scripts) : Makefile +arg_parser.o : arg_parser.h +main.o : arg_parser.h doc : info man -info : $(VPATH)/doc/$(pkgname).info +info : $(VPATH)/doc/$(progname).info -$(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texinfo - cd $(VPATH)/doc && makeinfo $(pkgname).texinfo +$(VPATH)/doc/$(progname).info : $(VPATH)/doc/$(progname).texinfo + cd $(VPATH)/doc && makeinfo $(progname).texinfo -man : $(VPATH)/doc/zcat.1 $(VPATH)/doc/zdiff.1 $(VPATH)/doc/zgrep.1 +man : $(VPATH)/doc/$(progname).1 $(VPATH)/doc/zcat.1 $(VPATH)/doc/zdiff.1 \ + $(VPATH)/doc/zgrep.1 + +$(VPATH)/doc/$(progname).1 : $(progname) + help2man -o $(VPATH)/doc/$(progname).1 ./$(progname) $(VPATH)/doc/zcat.1 : zcat help2man -o $(VPATH)/doc/zcat.1 --no-info ./zcat @@ -60,38 +88,55 @@ check : all $(VPATH)/testsuite/check.sh install : all install-info install-man if test ! -d $(DESTDIR)$(bindir) ; then $(INSTALL_DIR) $(DESTDIR)$(bindir) ; fi + $(INSTALL_PROGRAM) ./$(progname) $(DESTDIR)$(bindir)/$(progname) $(INSTALL_SCRIPT) zcat $(DESTDIR)$(bindir)/zcat $(INSTALL_SCRIPT) zcmp $(DESTDIR)$(bindir)/zcmp $(INSTALL_SCRIPT) zdiff $(DESTDIR)$(bindir)/zdiff + $(INSTALL_SCRIPT) zegrep $(DESTDIR)$(bindir)/zegrep + $(INSTALL_SCRIPT) zfgrep $(DESTDIR)$(bindir)/zfgrep $(INSTALL_SCRIPT) zgrep $(DESTDIR)$(bindir)/zgrep install-info : if test ! -d $(DESTDIR)$(infodir) ; then $(INSTALL_DIR) $(DESTDIR)$(infodir) ; fi - $(INSTALL_DATA) $(VPATH)/doc/$(pkgname).info $(DESTDIR)$(infodir)/$(pkgname).info - -install-info --info-dir=$(DESTDIR)$(infodir) $(DESTDIR)$(infodir)/$(pkgname).info + $(INSTALL_DATA) $(VPATH)/doc/$(progname).info $(DESTDIR)$(infodir)/$(progname).info + -install-info --info-dir=$(DESTDIR)$(infodir) $(DESTDIR)$(infodir)/$(progname).info install-man : if test ! -d $(DESTDIR)$(mandir)/man1 ; then $(INSTALL_DIR) $(DESTDIR)$(mandir)/man1 ; fi + $(INSTALL_DATA) $(VPATH)/doc/$(progname).1 $(DESTDIR)$(mandir)/man1/$(progname).1 $(INSTALL_DATA) $(VPATH)/doc/zcat.1 $(DESTDIR)$(mandir)/man1/zcat.1 -rm -f $(DESTDIR)$(mandir)/man1/zcmp.1 cd $(DESTDIR)$(mandir)/man1 && ln -s zdiff.1 zcmp.1 $(INSTALL_DATA) $(VPATH)/doc/zdiff.1 $(DESTDIR)$(mandir)/man1/zdiff.1 $(INSTALL_DATA) $(VPATH)/doc/zgrep.1 $(DESTDIR)$(mandir)/man1/zgrep.1 + -rm -f $(DESTDIR)$(mandir)/man1/zegrep.1 + -rm -f $(DESTDIR)$(mandir)/man1/zfgrep.1 + cd $(DESTDIR)$(mandir)/man1 && ln -s zgrep.1 zegrep.1 + cd $(DESTDIR)$(mandir)/man1 && ln -s zgrep.1 zfgrep.1 + +install-strip : all + $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install uninstall : uninstall-info uninstall-man + -rm -f $(DESTDIR)$(bindir)/$(progname) -rm -f $(DESTDIR)$(bindir)/zcat -rm -f $(DESTDIR)$(bindir)/zcmp -rm -f $(DESTDIR)$(bindir)/zdiff + -rm -f $(DESTDIR)$(bindir)/zegrep + -rm -f $(DESTDIR)$(bindir)/zfgrep -rm -f $(DESTDIR)$(bindir)/zgrep uninstall-info : - -install-info --info-dir=$(DESTDIR)$(infodir) --remove $(DESTDIR)$(infodir)/$(pkgname).info - -rm -f $(DESTDIR)$(infodir)/$(pkgname).info + -install-info --info-dir=$(DESTDIR)$(infodir) --remove $(DESTDIR)$(infodir)/$(progname).info + -rm -f $(DESTDIR)$(infodir)/$(progname).info uninstall-man : + -rm -f $(DESTDIR)$(mandir)/man1/$(progname).1 -rm -f $(DESTDIR)$(mandir)/man1/zcat.1 -rm -f $(DESTDIR)$(mandir)/man1/zcmp.1 -rm -f $(DESTDIR)$(mandir)/man1/zdiff.1 + -rm -f $(DESTDIR)$(mandir)/man1/zegrep.1 + -rm -f $(DESTDIR)$(mandir)/man1/zfgrep.1 -rm -f $(DESTDIR)$(mandir)/man1/zgrep.1 dist : @@ -105,20 +150,18 @@ dist : $(DISTNAME)/NEWS \ $(DISTNAME)/README \ $(DISTNAME)/configure \ - $(DISTNAME)/doc/zcat.1 \ - $(DISTNAME)/doc/zdiff.1 \ - $(DISTNAME)/doc/zgrep.1 \ - $(DISTNAME)/doc/$(pkgname).info \ - $(DISTNAME)/doc/$(pkgname).texinfo \ + $(DISTNAME)/doc/*.1 \ + $(DISTNAME)/doc/$(progname).info \ + $(DISTNAME)/doc/$(progname).texinfo \ $(DISTNAME)/testsuite/check.sh \ - $(DISTNAME)/zcat.in \ - $(DISTNAME)/zcmp.in \ - $(DISTNAME)/zdiff.in \ - $(DISTNAME)/zgrep.in + $(DISTNAME)/*.h \ + $(DISTNAME)/*.cc \ + $(DISTNAME)/z*.in rm -f $(DISTNAME) lzip -v -9 $(DISTNAME).tar clean : + -rm -f $(progname) $(progname)_profiled $(objs) -rm -f $(scripts) distclean : clean @@ -1,3 +1,5 @@ -Changes in version 0.2: +Changes in version 0.4: -Support for the xz compressor has been added. +Two new utilities have been added; zegrep and zfgrep. + +Zutils now recognizes file formats independently of filename extensions. @@ -4,7 +4,8 @@ Zutils is a collection of utilities for dealing with any combination of compressed and non-compressed files transparently. The supported compressors are gzip, bzip2, lzip and xz. -The currently provided utilities are zcat, zcmp, zdiff and zgrep. +The currently provided utilities are zcat, zcmp, zdiff, zegrep, zfgrep +and zgrep. Copyright (C) 2009 Antonio Diaz Diaz. diff --git a/arg_parser.cc b/arg_parser.cc new file mode 100644 index 0000000..b889e1a --- /dev/null +++ b/arg_parser.cc @@ -0,0 +1,193 @@ +/* Arg_parser - A POSIX/GNU command line argument parser. + Copyright (C) 2006, 2007, 2008, 2009 Antonio Diaz Diaz. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cstring> +#include <string> +#include <vector> + +#include "arg_parser.h" + + +bool Arg_parser::parse_long_option( const char * const opt, const char * const arg, + const Option options[], int & argind ) + { + unsigned int len; + int index = -1; + bool exact = false, ambig = false; + + for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ; + + // Test all long options for either exact match or abbreviated matches. + for( int i = 0; options[i].code != 0; ++i ) + if( options[i].name && !std::strncmp( options[i].name, &opt[2], len ) ) + { + if( std::strlen( options[i].name ) == len ) // Exact match found + { index = i; exact = true; break; } + else if( index < 0 ) index = i; // First nonexact match found + else if( options[index].code != options[i].code || + options[index].has_arg != options[i].has_arg ) + ambig = true; // Second or later nonexact match found + } + + if( ambig && !exact ) + { + error_ = "option `"; error_ += opt; error_ += "' is ambiguous"; + return false; + } + + if( index < 0 ) // nothing found + { + error_ = "unrecognized option `"; error_ += opt; error_ += '\''; + return false; + } + + ++argind; + data.push_back( Record( options[index].code ) ); + + if( opt[len+2] ) // `--<long_option>=<argument>' syntax + { + if( options[index].has_arg == no ) + { + error_ = "option `--"; error_ += options[index].name; + error_ += "' doesn't allow an argument"; + return false; + } + if( options[index].has_arg == yes && !opt[len+3] ) + { + error_ = "option `--"; error_ += options[index].name; + error_ += "' requires an argument"; + return false; + } + data.back().argument = &opt[len+3]; + return true; + } + + if( options[index].has_arg == yes ) + { + if( !arg || !arg[0] ) + { + error_ = "option `--"; error_ += options[index].name; + error_ += "' requires an argument"; + return false; + } + ++argind; data.back().argument = arg; + return true; + } + + return true; + } + + +bool Arg_parser::parse_short_option( const char * const opt, const char * const arg, + const Option options[], int & argind ) + { + int cind = 1; // character index in opt + + while( cind > 0 ) + { + int index = -1; + const unsigned char c = opt[cind]; + + if( c != 0 ) + for( int i = 0; options[i].code; ++i ) + if( c == options[i].code ) + { index = i; break; } + + if( index < 0 ) + { + error_ = "invalid option -- "; error_ += c; + return false; + } + + data.push_back( Record( c ) ); + if( opt[++cind] == 0 ) { ++argind; cind = 0; } // opt finished + + if( options[index].has_arg != no && cind > 0 && opt[cind] ) + { + data.back().argument = &opt[cind]; ++argind; cind = 0; + } + else if( options[index].has_arg == yes ) + { + if( !arg || !arg[0] ) + { + error_ = "option requires an argument -- "; error_ += c; + return false; + } + data.back().argument = arg; ++argind; cind = 0; + } + } + return true; + } + + +Arg_parser::Arg_parser( const int argc, const char * const argv[], + const Option options[], const bool in_order ) + { + if( argc < 2 || !argv || !options ) return; + + std::vector< std::string > non_options; // skipped non-options + int argind = 1; // index in argv + + while( argind < argc ) + { + const unsigned char ch1 = argv[argind][0]; + const unsigned char ch2 = ( ch1 ? argv[argind][1] : 0 ); + + if( ch1 == '-' && ch2 ) // we found an option + { + const char * const opt = argv[argind]; + const char * const arg = (argind + 1 < argc) ? argv[argind+1] : 0; + if( ch2 == '-' ) + { + if( !argv[argind][2] ) { ++argind; break; } // we found "--" + else if( !parse_long_option( opt, arg, options, argind ) ) break; + } + else if( !parse_short_option( opt, arg, options, argind ) ) break; + } + else + { + if( !in_order ) non_options.push_back( argv[argind++] ); + else { data.push_back( Record() ); data.back().argument = argv[argind++]; } + } + } + if( error_.size() ) data.clear(); + else + { + for( unsigned int i = 0; i < non_options.size(); ++i ) + { data.push_back( Record() ); data.back().argument.swap( non_options[i] ); } + while( argind < argc ) + { data.push_back( Record() ); data.back().argument = argv[argind++]; } + } + } + + +Arg_parser::Arg_parser( const char * const opt, const char * const arg, + const Option options[] ) + { + if( !opt || !opt[0] || !options ) return; + + if( opt[0] == '-' && opt[1] ) // we found an option + { + int argind = 1; // dummy + if( opt[1] == '-' ) + { if( opt[2] ) parse_long_option( opt, arg, options, argind ); } + else + parse_short_option( opt, arg, options, argind ); + if( error_.size() ) data.clear(); + } + else { data.push_back( Record() ); data.back().argument = opt; } + } diff --git a/arg_parser.h b/arg_parser.h new file mode 100644 index 0000000..fa6161b --- /dev/null +++ b/arg_parser.h @@ -0,0 +1,95 @@ +/* Arg_parser - A POSIX/GNU command line argument parser. + Copyright (C) 2006, 2007, 2008, 2009 Antonio Diaz Diaz. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Arg_parser reads the arguments in `argv' and creates a number of + option codes, option arguments and non-option arguments. + + In case of error, `error' returns a non-empty error message. + + `options' is an array of `struct Option' terminated by an element + containing a code which is zero. A null name means a short-only + option. A code value outside the unsigned char range means a + long-only option. + + Arg_parser normally makes it appear as if all the option arguments + were specified before all the non-option arguments for the purposes + of parsing, even if the user of your program intermixed option and + non-option arguments. If you want the arguments in the exact order + the user typed them, call `Arg_parser' with `in_order' = true. + + The argument `--' terminates all options; any following arguments are + treated as non-option arguments, even if they begin with a hyphen. + + The syntax for optional option arguments is `-<short_option><argument>' + (without whitespace), or `--<long_option>=<argument>'. +*/ + +class Arg_parser + { +public: + enum Has_arg { no, yes, maybe }; + + struct Option + { + int code; // Short option letter or code ( code != 0 ) + const char * name; // Long option name (maybe null) + Has_arg has_arg; + }; + +private: + struct Record + { + int code; + std::string argument; + Record( const int c = 0 ) : code( c ) {} + }; + + std::string error_; + std::vector< Record > data; + + bool parse_long_option( const char * const opt, const char * const arg, + const Option options[], int & argind ); + bool parse_short_option( const char * const opt, const char * const arg, + const Option options[], int & argind ); + +public: + Arg_parser( const int argc, const char * const argv[], + const Option options[], const bool in_order = false ); + + // Restricted constructor. Parses a single token and argument (if any) + Arg_parser( const char * const opt, const char * const arg, + const Option options[] ); + + const std::string & error() const throw() { return error_; } + + // The number of arguments parsed (may be different from argc) + int arguments() const throw() { return data.size(); } + + // If code( i ) is 0, argument( i ) is a non-option. + // Else argument( i ) is the option's argument (or empty). + int code( const int i ) const throw() + { + if( i >= 0 && i < arguments() ) return data[i].code; + else return 0; + } + + const std::string & argument( const int i ) const throw() + { + if( i >= 0 && i < arguments() ) return data[i].argument; + else return error_; + } + }; @@ -5,12 +5,12 @@ # This configure script is free software: you have unlimited permission # to copy, distribute and modify it. # -# Date of this version: 2009-08-13 +# Date of this version: 2009-09-17 args= no_create= -pkgname=zutils -pkgversion=0.3 +progname=zutils +progversion=0.4 srctrigger=zdiff.in # clear some things potentially inherited from environment. @@ -71,7 +71,7 @@ while [ x"$1" != x ] ; do echo exit 0 ;; --version | --ve* | -V) - echo "Configure script for ${pkgname} version ${pkgversion}" + echo "Configure script for ${progname} version ${progversion}" exit 0 ;; --srcdir* | --sr*) srcdir=`echo ${optarg} | sed -e 's,/$,,'` ;; @@ -191,8 +191,8 @@ cat > Makefile << EOF # This Makefile is free software: you have unlimited permission # to copy, distribute and modify it. -pkgname = ${pkgname} -pkgversion = ${pkgversion} +progname = ${progname} +progversion = ${progversion} VPATH = ${srcdir} prefix = ${prefix} exec_prefix = ${exec_prefix} @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. -.TH ZCAT "1" "August 2009" "Zcat 0.3" "User Commands" +.TH ZCAT "1" "September 2009" "Zcat 0.4" "User Commands" .SH NAME -Zcat \- manual page for Zcat 0.3 +Zcat \- manual page for Zcat 0.4 .SH SYNOPSIS .B zcat [\fIOPTIONS\fR] [\fICAT_OPTIONS\fR] [\fIFILES\fR] diff --git a/doc/zdiff.1 b/doc/zdiff.1 index c7ecbfa..cbada53 100644 --- a/doc/zdiff.1 +++ b/doc/zdiff.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. -.TH ZDIFF "1" "August 2009" "Zdiff 0.3" "User Commands" +.TH ZDIFF "1" "September 2009" "Zdiff 0.4" "User Commands" .SH NAME -Zdiff \- manual page for Zdiff 0.3 +Zdiff \- manual page for Zdiff 0.4 .SH SYNOPSIS .B zdiff [\fIOPTIONS\fR] [\fIDIFF_OPTIONS\fR] \fIFILE1 \fR[\fIFILE2\fR] diff --git a/doc/zgrep.1 b/doc/zgrep.1 index f03b530..fadbbc0 100644 --- a/doc/zgrep.1 +++ b/doc/zgrep.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. -.TH ZGREP "1" "August 2009" "Zgrep 0.3" "User Commands" +.TH ZGREP "1" "September 2009" "Zgrep 0.4" "User Commands" .SH NAME -Zgrep \- manual page for Zgrep 0.3 +Zgrep \- manual page for Zgrep 0.4 .SH SYNOPSIS .B zgrep [\fIOPTIONS\fR] [\fIGREP_OPTIONS\fR] \fIPATTERN \fR[\fIFILES\fR] @@ -15,6 +15,9 @@ used. If a given file does not exist, zgrep tries the compressed file names corresponding to the supported compressors. The supported compressors are gzip, bzip2, lzip and xz. .PP +Zegrep is a shortcut for "zgrep \fB\-E\fR" +Zfgrep is a shortcut for "zgrep \fB\-F\fR" +.PP GREP_OPTIONS are passed directly to grep. The exit status from grep is preserved. .SH OPTIONS diff --git a/doc/zutils.1 b/doc/zutils.1 new file mode 100644 index 0000000..14c33b7 --- /dev/null +++ b/doc/zutils.1 @@ -0,0 +1,46 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. +.TH ZUTILS "1" "September 2009" "Zutils 0.4" "User Commands" +.SH NAME +Zutils \- manual page for Zutils 0.4 +.SH SYNOPSIS +.B zutils +[\fIoptions\fR] [\fIfiles\fR] +.SH DESCRIPTION +Zutils \- A file format detector for compressed files. +.SH OPTIONS +.TP +\fB\-h\fR, \fB\-\-help\fR +display this help and exit +.TP +\fB\-V\fR, \fB\-\-version\fR +output version information and exit +.TP +\fB\-q\fR, \fB\-\-quiet\fR +suppress all messages +.TP +\fB\-t\fR, \fB\-\-test\fR +test compressed file type +.TP +\fB\-v\fR, \fB\-\-verbose\fR +be verbose (a 2nd \fB\-v\fR gives more) +.SH "REPORTING BUGS" +Report bugs to zutils\-bug@nongnu.org +Zutils home page: http://www.nongnu.org/zutils/zutils.html +.SH COPYRIGHT +Copyright \(co 2009 Antonio Diaz Diaz. +License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> +.br +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +.SH "SEE ALSO" +The full documentation for +.B Zutils +is maintained as a Texinfo manual. If the +.B info +and +.B Zutils +programs are properly installed at your site, the command +.IP +.B info Zutils +.PP +should give you access to the complete manual. diff --git a/doc/zutils.info b/doc/zutils.info Binary files differindex 5ba19ac..caa2371 100644 --- a/doc/zutils.info +++ b/doc/zutils.info diff --git a/doc/zutils.texinfo b/doc/zutils.texinfo index ce69ab1..ccadf05 100644 --- a/doc/zutils.texinfo +++ b/doc/zutils.texinfo @@ -5,8 +5,8 @@ @finalout @c %**end of header -@set UPDATED 13 August 2009 -@set VERSION 0.2 +@set UPDATED 17 September 2009 +@set VERSION 0.4 @dircategory Data Compression @direntry @@ -55,7 +55,8 @@ Zutils is a collection of utilities for dealing with any combination of compressed and non-compressed files transparently. The supported compressors are gzip, bzip2, lzip and xz. -The currently provided utilities are zcat, zcmp, zdiff and zgrep. +The currently provided utilities are zcat, zcmp, zdiff, zegrep, zfgrep +and zgrep. @node Zcat @@ -147,6 +148,8 @@ refer to temporary filenames instead of those specified. @node Zgrep @chapter Zgrep +@cindex zegrep +@cindex zfgrep @cindex zgrep Zgrep is a wrapper script around the grep command that allows @@ -156,6 +159,11 @@ used. If a given file does not exist, zgrep tries the compressed file names corresponding to the supported compressors. The supported compressors are gzip, bzip2, lzip and xz. +@noindent +Zegrep is a shortcut for @samp{zgrep -E}@* +Zfgrep is a shortcut for @samp{zgrep -F} + + The format for running zgrep is: @example @@ -0,0 +1,688 @@ +/* Zutils - A file format detector for compressed files + Copyright (C) 2009 Antonio Diaz Diaz. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/* + Return values: 0 for a normal exit, 1 for environmental problems + (file not found, invalid flags, I/O errors, etc), 2 to indicate a + corrupt or invalid input file, 3 for an internal consistency error + (eg, bug) which caused zutils to panic. +*/ + +#define _FILE_OFFSET_BITS 64 + +#include <algorithm> +#include <cerrno> +#include <climits> +#include <csignal> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <string> +#include <vector> +#include <fcntl.h> +#include <stdint.h> +#include <unistd.h> +#include <utime.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include "arg_parser.h" + +#ifndef LLONG_MAX +#define LLONG_MAX 0x7FFFFFFFFFFFFFFFLL +#endif +#ifndef LLONG_MIN +#define LLONG_MIN (-LLONG_MAX - 1LL) +#endif +#ifndef ULLONG_MAX +#define ULLONG_MAX 0xFFFFFFFFFFFFFFFFULL +#endif + + +namespace { + +const char * invocation_name = 0; +const char * const Program_name = "Zutils"; +const char * const program_name = "zutils"; +const char * const program_year = "2009"; + +struct { const char * from; const char * to; } const known_extensions[] = { + { ".zf", "" }, + { 0, 0 } }; + +enum Mode { m_compress = 0, m_decompress, m_test }; + +std::string output_filename; +int outhandle = -1; +int verbosity = 0; +bool delete_output_on_interrupt = false; + + +class Pretty_print + { + const char * const stdin_name; + const unsigned int stdin_name_len; + unsigned int longest_name; + std::string name_; + mutable bool first_post; + +public: + Pretty_print( const std::vector< std::string > & filenames ) + : stdin_name( "(stdin)" ), stdin_name_len( std::strlen( stdin_name ) ), + longest_name( 0 ), first_post( false ) + { + for( unsigned int i = 0; i < filenames.size(); ++i ) + { + const std::string & s = filenames[i]; + const unsigned int len = ( ( s == "-" ) ? stdin_name_len : s.size() ); + if( len > longest_name ) longest_name = len; + } + if( longest_name == 0 ) longest_name = stdin_name_len; + } + + void set_name( const std::string & filename ) + { + if( filename.size() && filename != "-" ) name_ = filename; + else name_ = stdin_name; + first_post = true; + } + + void reset() const throw() { if( name_.size() ) first_post = true; } + const char * name() const throw() { return name_.c_str(); } + void operator()( const char * const msg = 0 ) const throw(); + }; + + +void Pretty_print::operator()( const char * const msg ) const throw() + { + if( verbosity >= 0 ) + { + if( first_post ) + { + first_post = false; + std::fprintf( stderr, " %s: ", name_.c_str() ); + for( unsigned int i = 0; i < longest_name - name_.size(); ++i ) + std::fprintf( stderr, " " ); + if( !msg ) std::fflush( stderr ); + } + if( msg ) std::fprintf( stderr, "%s.\n", msg ); + } + } + + +void show_help() throw() + { + std::printf( "%s - A file format detector for compressed files.\n", Program_name ); + std::printf( "\nUsage: %s [options] [files]\n", invocation_name ); + std::printf( "\nOptions:\n" ); + std::printf( " -h, --help display this help and exit\n" ); + std::printf( " -V, --version output version information and exit\n" ); +// std::printf( " -c, --stdout send output to standard output\n" ); +// std::printf( " -d, --decompress decompress\n" ); +// std::printf( " -f, --force overwrite existing output files\n" ); +// std::printf( " -k, --keep keep (don't delete) input files\n" ); +// std::printf( " -o, --output=<file> if reading stdin, place the output into <file>\n" ); + std::printf( " -q, --quiet suppress all messages\n" ); + std::printf( " -t, --test test compressed file type\n" ); + std::printf( " -v, --verbose be verbose (a 2nd -v gives more)\n" ); + std::printf( "\nReport bugs to zutils-bug@nongnu.org\n" ); + std::printf( "Zutils home page: http://www.nongnu.org/zutils/zutils.html\n" ); + } + + +void show_version() throw() + { + std::printf( "%s %s\n", Program_name, PROGVERSION ); + std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); + std::printf( "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n" ); + std::printf( "This is free software: you are free to change and redistribute it.\n" ); + std::printf( "There is NO WARRANTY, to the extent permitted by law.\n" ); + } + + +void show_error( const char * msg, const int errcode = 0, const bool help = false ) throw() + { + if( verbosity >= 0 ) + { + if( msg && msg[0] != 0 ) + { + std::fprintf( stderr, "%s: %s", program_name, msg ); + if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) ); + std::fprintf( stderr, "\n" ); + } + if( help && invocation_name && invocation_name[0] != 0 ) + std::fprintf( stderr, "Try `%s --help' for more information.\n", invocation_name ); + } + } + + +void internal_error( const char * msg ) + { + std::string s( "internal error: " ); s += msg; + show_error( s.c_str() ); + std::exit( 3 ); + } + + +const char * format_num( long long num, long long limit = 9999, + const int set_prefix = 0 ) throw() + { + const char * const si_prefix[8] = + { "k", "M", "G", "T", "P", "E", "Z", "Y" }; + const char * const binary_prefix[8] = + { "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" }; + static bool si = false; + static char buf[16]; + + if( set_prefix ) si = ( set_prefix > 0 ); + const int factor = ( si ) ? 1000 : 1024; + const char * const *prefix = ( si ) ? si_prefix : binary_prefix; + const char *p = ""; + limit = std::max( 999LL, std::min( 999999LL, limit ) ); + + for( int i = 0; i < 8 && ( llabs( num ) > limit || + ( llabs( num ) >= factor && num % factor == 0 ) ); ++i ) + { num /= factor; p = prefix[i]; } + snprintf( buf, sizeof buf, "%lld %s", num, p ); + return buf; + } + + +long long getnum( const char * ptr, const int bs, + const long long llimit = LLONG_MIN + 1, + const long long ulimit = LLONG_MAX ) throw() + { + errno = 0; + char *tail; + long long result = strtoll( ptr, &tail, 0 ); + if( tail == ptr ) + { + show_error( "bad or missing numerical argument", 0, true ); + std::exit( 1 ); + } + + if( !errno && tail[0] ) + { + int factor = ( tail[1] == 'i' ) ? 1024 : 1000; + int exponent = 0; + bool bad_multiplier = false; + switch( tail[0] ) + { + case ' ': break; + case 'b': if( bs > 0 ) { factor = bs; exponent = 1; } + else bad_multiplier = true; + break; + case 'Y': exponent = 8; break; + case 'Z': exponent = 7; break; + case 'E': exponent = 6; break; + case 'P': exponent = 5; break; + case 'T': exponent = 4; break; + case 'G': exponent = 3; break; + case 'M': exponent = 2; break; + case 'K': if( factor == 1024 ) exponent = 1; else bad_multiplier = true; + break; + case 'k': if( factor == 1000 ) exponent = 1; else bad_multiplier = true; + break; + default: bad_multiplier = true; + } + if( bad_multiplier ) + { + show_error( "bad multiplier in numerical argument", 0, true ); + std::exit( 1 ); + } + for( int i = 0; i < exponent; ++i ) + { + if( LLONG_MAX / factor >= llabs( result ) ) result *= factor; + else { errno = ERANGE; break; } + } + } + if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE; + if( errno ) + { + show_error( "numerical argument out of limits" ); + std::exit( 1 ); + } + return result; + } + + +int extension_index( const std::string & name ) throw() + { + for( int i = 0; known_extensions[i].from; ++i ) + { + const std::string ext( known_extensions[i].from ); + if( name.size() > ext.size() && + name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 ) + return i; + } + return -1; + } + + +int open_instream( const std::string & name, struct stat * in_statsp, + const Mode program_mode, const int eindex, + const bool force, const bool to_stdout ) throw() + { + int inhandle = -1; + if( program_mode == m_compress && !force && eindex >= 0 ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: input file `%s' already has `%s' suffix.\n", + program_name, name.c_str(), + known_extensions[eindex].from ); + } + else + { + inhandle = open( name.c_str(), O_RDONLY ); + if( inhandle < 0 ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Can't open input file `%s': %s.\n", + program_name, name.c_str(), std::strerror( errno ) ); + } + else + { + const int i = fstat( inhandle, in_statsp ); + const mode_t & mode = in_statsp->st_mode; + if( i < 0 || !( S_ISREG( mode ) || ( to_stdout && + ( S_ISFIFO( mode ) || S_ISSOCK( mode ) || + S_ISBLK( mode ) || S_ISCHR( mode ) ) ) ) ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: input file `%s' is not a regular file%s.\n", + program_name, name.c_str(), + to_stdout ? "" : " and `--stdout' was not specified" ); + close( inhandle ); + inhandle = -1; + } + } + } + return inhandle; + } + + +void set_c_outname( const std::string & name ) throw() + { + output_filename = name; + output_filename += known_extensions[0].from; + } + + +void set_d_outname( const std::string & name, const int i ) throw() + { + if( i >= 0 ) + { + const std::string from( known_extensions[i].from ); + if( name.size() > from.size() ) + { + output_filename.assign( name, 0, name.size() - from.size() ); + output_filename += known_extensions[i].to; + return; + } + } + output_filename = name; output_filename += ".out"; + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: can't guess original name for `%s' -- using `%s'.\n", + program_name, name.c_str(), output_filename.c_str() ); + } + + +bool open_outstream( const bool /*force*/ ) throw() + { +/* if( force ) + outhandle = open( output_filename.c_str(), O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ); + else outhandle = open( output_filename.c_str(), O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ); + if( outhandle < 0 ) + { + if( errno == EEXIST ) outhandle = -2; else outhandle = -1; + if( verbosity >= 0 ) + { + if( outhandle == -2 ) + std::fprintf( stderr, "%s: Output file %s already exists, skipping.\n", + program_name, output_filename.c_str() ); + else + std::fprintf( stderr, "%s: Can't create output file `%s': %s.\n", + program_name, output_filename.c_str(), std::strerror( errno ) ); + } + }*/ + return ( outhandle >= 0 ); + } + + +bool check_tty( const int inhandle, const Mode program_mode ) throw() + { + if( program_mode == m_compress && isatty( outhandle ) ) + { + show_error( "I won't write compressed data to a terminal.", 0, true ); + return false; + } + if( ( program_mode == m_decompress || program_mode == m_test ) && + isatty( inhandle ) ) + { + show_error( "I won't read compressed data from a terminal.", 0, true ); + return false; + } + return true; + } + + +void cleanup_and_fail( const int retval ) throw() + { + if( delete_output_on_interrupt ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Deleting output file `%s', if it exists.\n", + program_name, output_filename.c_str() ); + if( outhandle >= 0 ) { close( outhandle ); outhandle = -1; } + if( std::remove( output_filename.c_str() ) != 0 ) + show_error( "WARNING: deletion of output file (apparently) failed." ); + } + std::exit( retval ); + } + + + // Set permissions, owner and times. +void close_and_set_permissions( const struct stat * in_statsp, int * retvalp ) + { + int tmp = 0; + if( in_statsp ) + { + if( fchmod( outhandle, in_statsp->st_mode ) != 0 ) tmp = 1; + if( !tmp ) (void)fchown( outhandle, in_statsp->st_uid, in_statsp->st_gid ); + // fchown will in many cases return with EPERM, which can be safely ignored. + } + if( close( outhandle ) == 0 ) outhandle = -1; + else cleanup_and_fail( 1 ); + delete_output_on_interrupt = false; + if( !in_statsp ) return; + if( !tmp ) + { + struct utimbuf t; + t.actime = in_statsp->st_atime; + t.modtime = in_statsp->st_mtime; + tmp = utime( output_filename.c_str(), &t ); + } + if( tmp ) + { + if( tmp > *retvalp ) *retvalp = tmp; + show_error( "I can't change output file attributes." ); + cleanup_and_fail( *retvalp ); + } + } + + +// Returns the number of bytes really read. +// If (returned value < size) and (errno == 0), means EOF was reached. +// +int readblock( const int fd, char * buf, const int size ) throw() + { + int rest = size; + errno = 0; + while( rest > 0 ) + { + errno = 0; + const int n = read( fd, buf + size - rest, rest ); + if( n > 0 ) rest -= n; + else if( n == 0 ) break; + else if( errno != EINTR && errno != EAGAIN ) break; + } + return ( rest > 0 ) ? size - rest : size; + } + + +// Returns the number of bytes really written. +// If (returned value < size), it is always an error. +// +int writeblock( const int fd, const char * buf, const int size ) throw() + { + int rest = size; + errno = 0; + while( rest > 0 ) + { + errno = 0; + const int n = write( fd, buf + size - rest, rest ); + if( n > 0 ) rest -= n; + else if( errno && errno != EINTR && errno != EAGAIN ) break; + } + return ( rest > 0 ) ? size - rest : size; + } + +/* +int compress( const int inhandle, const Pretty_print & pp, + const struct stat * in_statsp, int * retvalp ) + { + long long in_size = 0, out_size = 0; + if( verbosity >= 1 ) + { + pp(); + if( in_size <= 0 || out_size <= 0 ) + std::fprintf( stderr, "no data compressed.\n" ); + else + std::fprintf( stderr, "%6.3f:1, %6.3f bits/byte, " + "%5.2f%% saved, %lld in, %lld out.\n", + (double)in_size / out_size, + ( 8.0 * out_size ) / in_size, + 100.0 * ( 1.0 - ( (double)out_size / in_size ) ), + in_size, out_size ); + } + return 0; + } + + +int decompress( const int inhandle, const Pretty_print & pp ) + { + if( verbosity >= 1 ) + std::fprintf( stderr, "done\n" ); + return 0; + } +*/ + +int test_type( const int inhandle, const Pretty_print & pp ) + { + unsigned char buf[5]; + const char * msg = 0; + const int size = readblock( inhandle, (char *)buf, sizeof buf ); + if( size == (int)sizeof buf ) + { + if( buf[0] == 0x1F && buf[1] == 0x8B ) + msg = "gzip"; + else if( buf[0] == 'B' && buf[1] == 'Z' && buf[2] == 'h' ) + msg = "bzip2"; + else if( buf[0] == 'L' && buf[1] == 'Z' && buf[2] == 'I' && buf[3] == 'P' ) + msg = "lzip"; + else if( buf[1] == '7' && buf[2] == 'z' && buf[3] == 'X' && buf[4] == 'Z' ) + msg = "xz"; + } + const int retval = ( msg ? 0 : 1 ); + if( verbosity >= 0 ) + { + if( verbosity >= 1 ) pp(); + if( !msg ) msg = "unknown"; + std::printf( "%s\n", msg ); + } + return retval; + } + + +extern "C" void signal_handler( int ) throw() + { + show_error( "Control-C or similar caught, quitting." ); + cleanup_and_fail( 0 ); + } + + +void set_signals() throw() + { + signal( SIGHUP, signal_handler ); + signal( SIGINT, signal_handler ); + signal( SIGTERM, signal_handler ); + } + +} // end namespace + + +int main( const int argc, const char * argv[] ) + { + int inhandle = -1; + Mode program_mode = m_compress; + bool force = false; + bool keep_input_files = false; + bool to_stdout = false; + std::string input_filename; + std::string default_output_filename; + std::vector< std::string > filenames; + invocation_name = argv[0]; + + const Arg_parser::Option options[] = + { + { 'c', "stdout", Arg_parser::no }, + { 'd', "decompress", Arg_parser::no }, + { 'f', "force", Arg_parser::no }, + { 'h', "help", Arg_parser::no }, + { 'k', "keep", Arg_parser::no }, + { 'o', "output", Arg_parser::yes }, + { 'q', "quiet", Arg_parser::no }, + { 't', "test", Arg_parser::no }, + { 'v', "verbose", Arg_parser::no }, + { 'V', "version", Arg_parser::no }, + { 0 , 0, Arg_parser::no } }; + + Arg_parser parser( argc, argv, options ); + if( parser.error().size() ) // bad option + { show_error( parser.error().c_str(), 0, true ); return 1; } + + int argind = 0; + for( ; argind < parser.arguments(); ++argind ) + { + const int code = parser.code( argind ); + if( !code ) break; // no more options + const char * arg = parser.argument( argind ).c_str(); + switch( code ) + { + case 'c': to_stdout = true; break; + case 'd': program_mode = m_decompress; break; + case 'f': force = true; break; + case 'h': show_help(); return 0; + case 'k': keep_input_files = true; break; + case 'o': default_output_filename = arg; break; + case 'q': verbosity = -1; break; + case 't': program_mode = m_test; break; + case 'v': if( verbosity < 4 ) ++verbosity; break; + case 'V': show_version(); return 0; + default : internal_error( "uncaught option" ); + } + } + + bool filenames_given = false; + for( ; argind < parser.arguments(); ++argind ) + { + if( parser.argument( argind ) != "-" ) filenames_given = true; + filenames.push_back( parser.argument( argind ) ); + } + + if( filenames.empty() ) filenames.push_back("-"); + if( filenames_given ) set_signals(); + + Pretty_print pp( filenames ); + if( program_mode == m_test ) + outhandle = -1; + + int retval = 0; + for( unsigned int i = 0; i < filenames.size(); ++i ) + { + struct stat in_stats; + output_filename.clear(); + + if( !filenames[i].size() || filenames[i] == "-" ) + { + input_filename.clear(); + inhandle = STDIN_FILENO; + if( program_mode != m_test ) + { + if( to_stdout || !default_output_filename.size() ) + outhandle = STDOUT_FILENO; + else + { + if( program_mode == m_compress ) + set_c_outname( default_output_filename ); + else output_filename = default_output_filename; + if( !open_outstream( force ) ) + { + if( outhandle == -1 && retval < 1 ) retval = 1; + close( inhandle ); inhandle = -1; + continue; + } + } + } + } + else + { + input_filename = filenames[i]; + const int eindex = extension_index( input_filename ); + inhandle = open_instream( input_filename, &in_stats, program_mode, + eindex, force, to_stdout ); + if( inhandle < 0 ) { if( retval < 1 ) retval = 1; continue; } + if( program_mode != m_test ) + { + if( to_stdout ) outhandle = STDOUT_FILENO; + else + { + if( program_mode == m_compress ) + set_c_outname( input_filename ); + else set_d_outname( input_filename, eindex ); + if( !open_outstream( force ) ) + { + if( outhandle == -1 && retval < 1 ) retval = 1; + close( inhandle ); inhandle = -1; + continue; + } + } + } + } + + if( !check_tty( inhandle, program_mode ) ) return 1; + + if( output_filename.size() && !to_stdout && program_mode != m_test ) + delete_output_on_interrupt = true; +// const struct stat * in_statsp = input_filename.size() ? &in_stats : 0; + pp.set_name( input_filename ); + int tmp = 0; + switch( program_mode ) + { + case m_compress: + /*tmp = compress( inhandle, pp, in_statsp, &retval );*/ break; + case m_decompress: + /*tmp = decompress( inhandle, pp );*/ break; + case m_test: + tmp = test_type( inhandle, pp ); break; + } + if( tmp > retval ) retval = tmp; + if( tmp && program_mode != m_test ) cleanup_and_fail( retval ); +/* + if( delete_output_on_interrupt ) + close_and_set_permissions( in_statsp, &retval ); + if( input_filename.size() ) + { + close( inhandle ); inhandle = -1; + if( !keep_input_files && !to_stdout && program_mode != m_test ) + std::remove( input_filename.c_str() ); + }*/ + } + if( outhandle >= 0 ) close( outhandle ); + return retval; + } diff --git a/testsuite/check.sh b/testsuite/check.sh index dbbf076..7af54ac 100755 --- a/testsuite/check.sh +++ b/testsuite/check.sh @@ -10,8 +10,12 @@ export LC_ALL objdir=`pwd` testdir=`cd "$1" ; pwd` ZCAT="${objdir}"/zcat +ZCMP="${objdir}"/zcmp ZDIFF="${objdir}"/zdiff ZGREP="${objdir}"/zgrep +ZEGREP="${objdir}"/zegrep +ZFGREP="${objdir}"/zfgrep +ZUTILS="${objdir}"/zutils compressors="gzip bzip2 lzip" extensions="gz bz2 lz" framework_failure() { echo 'failure in testing framework'; exit 1; } @@ -54,11 +58,11 @@ echo -n . for i in ${extensions}; do - "${ZDIFF}" --cmp in.$i || fail=1 + "${ZCMP}" in.$i || fail=1 echo -n . - "${ZDIFF}" --cmp in in.$i || fail=1 + "${ZCMP}" in in.$i || fail=1 echo -n . - "${ZDIFF}" --cmp in.$i in || fail=1 + "${ZCMP}" in.$i in || fail=1 echo -n . done @@ -107,6 +111,17 @@ echo -n . echo -n . "${ZGREP}" License in in.gz in.bz2 in.lz -- -in- 2>&1 > /dev/null || fail=1 echo -n . +"${ZEGREP}" License in 2>&1 > /dev/null || fail=1 +echo -n . +"${ZFGREP}" License in 2>&1 > /dev/null || fail=1 +echo -n . + +if [ "gzip" != `"${ZUTILS}" -t in.gz` ]; then fail=1 ; fi +echo -n . +if [ "bzip2" != `"${ZUTILS}" -t in.bz2` ]; then fail=1 ; fi +echo -n . +if [ "lzip" != `"${ZUTILS}" -t in.lz` ]; then fail=1 ; fi +echo -n . echo @@ -59,28 +59,26 @@ retval=0 for i in "$@" ; do if [ "$i" = "--" ] && [ ${two_hyphens} = 0 ] ; then two_hyphens=1 else - if [ -f "$i" ]; then - case "$i" in - *.gz | *.tgz) - prog="gzip -cdfq" ;; - *.bz2 | *.tbz | *.tbz2) - prog="bzip2 -cdfq" ;; - *.lz | *.tlz) - prog="lzip -cdfq" ;; - *.xz | *.txz) - prog="xz -cdfq" ;; - *) - prog=cat ;; - esac - elif [ -f "$i.gz" ]; then i="$i.gz" ; prog="gzip -cdfq" - elif [ -f "$i.bz2" ]; then i="$i.bz2" ; prog="bzip2 -cdfq" - elif [ -f "$i.lz" ]; then i="$i.lz" ; prog="lzip -cdfq" - elif [ -f "$i.xz" ]; then i="$i.xz" ; prog="xz -cdfq" - else - echo "$0: File \"$i\" not found or not a regular file" 1>&2 - if [ ${retval} = 0 ]; then retval=1 ; fi - continue + if [ ! -f "$i" ]; then + if [ -f "$i.gz" ]; then i="$i.gz" + elif [ -f "$i.bz2" ]; then i="$i.bz2" + elif [ -f "$i.lz" ]; then i="$i.lz" + elif [ -f "$i.xz" ]; then i="$i.xz" + else + echo "$0: File \"$i\" not found or not a regular file" 1>&2 + if [ ${retval} = 0 ]; then retval=1 ; fi + continue + fi fi + bindir=`echo "$0" | sed -e 's,[^/]*$,,'` + prog_name=`"${bindir}"zutils -t -- "$i"` + case "${prog_name}" in + gzip) prog="gzip -cdfq" ;; + bzip2) prog="bzip2 -cdfq" ;; + lzip) prog="lzip -cdfq" ;; + xz) prog="xz -cdfq" ;; + *) prog=cat ;; + esac ${prog} -- "$i" | cat ${args} r=$? if [ $r != 0 ]; then retval=$r ; fi @@ -1,2 +1,3 @@ #! /bin/sh -exec zdiff --cmp "$@" +bindir=`echo "$0" | sed -e 's,[^/]*$,,'` +exec "${bindir}"zdiff --cmp "$@" @@ -95,24 +95,15 @@ fi if [ -z "${file2}" ]; then case "${file1}" in - *.gz) - file2=`printf "%s" "${file1}" | sed 's/.gz$//'` ;; - *.tgz) - file2=`printf "%s" "${file1}" | sed 's/tgz$/tar/'` ;; - *.bz2) - file2=`printf "%s" "${file1}" | sed 's/.bz2$//'` ;; - *.tbz) - file2=`printf "%s" "${file1}" | sed 's/tbz$/tar/'` ;; - *.tbz2) - file2=`printf "%s" "${file1}" | sed 's/tbz2$/tar/'` ;; - *.lz) - file2=`printf "%s" "${file1}" | sed 's/.lz$//'` ;; - *.tlz) - file2=`printf "%s" "${file1}" | sed 's/tlz$/tar/'` ;; - *.xz) - file2=`printf "%s" "${file1}" | sed 's/.xz$//'` ;; - *.txz) - file2=`printf "%s" "${file1}" | sed 's/txz$/tar/'` ;; + *.gz) file2=`printf "%s" "${file1}" | sed -e 's,.gz$,,'` ;; + *.tgz) file2=`printf "%s" "${file1}" | sed -e 's,tgz$,tar,'` ;; + *.bz2) file2=`printf "%s" "${file1}" | sed -e 's,.bz2$,,'` ;; + *.tbz) file2=`printf "%s" "${file1}" | sed -e 's,tbz$,tar,'` ;; + *.tbz2) file2=`printf "%s" "${file1}" | sed -e 's,tbz2$,tar,'` ;; + *.lz) file2=`printf "%s" "${file1}" | sed -e 's,.lz$,,'` ;; + *.tlz) file2=`printf "%s" "${file1}" | sed -e 's,tlz$,tar,'` ;; + *.xz) file2=`printf "%s" "${file1}" | sed -e 's,.xz$,,'` ;; + *.txz) file2=`printf "%s" "${file1}" | sed -e 's,txz$,tar,'` ;; *) if [ -f "${file1}.gz" ]; then file2="${file1}.gz" elif [ -f "${file1}.bz2" ]; then file2="${file1}.bz2" @@ -127,18 +118,25 @@ fi prog1= prog2= -case "${file1}" in -*.gz | *.tgz) prog1=gzip ;; -*.bz2 | *.tbz | *.tbz2) prog1=bzip2 ;; -*.lz | *.tlz) prog1=lzip ;; -*.xz | *.txz) prog1=xz ;; -esac -case "${file2}" in -*.gz | *.tgz) prog2=gzip ;; -*.bz2 | *.tbz | *.tbz2) prog2=bzip2 ;; -*.lz | *.tlz) prog2=lzip ;; -*.xz | *.txz) prog2=xz ;; -esac +bindir=`echo "$0" | sed -e 's,[^/]*$,,'` +if [ -f "${file1}" ]; then + prog_name=`"${bindir}"zutils -t -- "${file1}"` + case "${prog_name}" in + gzip) prog1=gzip ;; + bzip2) prog1=bzip2 ;; + lzip) prog1=lzip ;; + xz) prog1=xz ;; + esac +fi +if [ -f "${file2}" ]; then + prog_name=`"${bindir}"zutils -t -- "${file2}"` + case "${prog_name}" in + gzip) prog2=gzip ;; + bzip2) prog2=bzip2 ;; + lzip) prog2=lzip ;; + xz) prog2=xz ;; + esac +fi retval=0 if [ -n "${prog1}" ]; then diff --git a/zegrep.in b/zegrep.in new file mode 100644 index 0000000..0cac12e --- /dev/null +++ b/zegrep.in @@ -0,0 +1,3 @@ +#! /bin/sh +bindir=`echo "$0" | sed -e 's,[^/]*$,,'` +exec "${bindir}"zgrep -E "$@" diff --git a/zfgrep.in b/zfgrep.in new file mode 100644 index 0000000..c1a96d8 --- /dev/null +++ b/zfgrep.in @@ -0,0 +1,3 @@ +#! /bin/sh +bindir=`echo "$0" | sed -e 's,[^/]*$,,'` +exec "${bindir}"zgrep -F "$@" @@ -27,6 +27,9 @@ while [ x"$1" != x ] ; do echo "names corresponding to the supported compressors." echo "The supported compressors are gzip, bzip2, lzip and xz." echo + echo "Zegrep is a shortcut for \"zgrep -E\"" + echo "Zfgrep is a shortcut for \"zgrep -F\"" + echo echo "Usage: $0 [OPTIONS] [GREP_OPTIONS] PATTERN [FILES]" echo echo "GREP_OPTIONS are passed directly to grep." @@ -81,28 +84,26 @@ retval=0 for i in "$@" ; do if [ "$i" = "--" ] && [ ${two_hyphens} = 0 ] ; then two_hyphens=1 else - if [ -f "$i" ]; then - case "$i" in - *.gz | *.tgz) - prog="gzip -cdfq" ;; - *.bz2 | *.tbz | *.tbz2) - prog="bzip2 -cdfq" ;; - *.lz | *.tlz) - prog="lzip -cdfq" ;; - *.xz | *.txz) - prog="xz -cdfq" ;; - *) - prog=cat ;; - esac - elif [ -f "$i.gz" ]; then i="$i.gz" ; prog="gzip -cdfq" - elif [ -f "$i.bz2" ]; then i="$i.bz2" ; prog="bzip2 -cdfq" - elif [ -f "$i.lz" ]; then i="$i.lz" ; prog="lzip -cdfq" - elif [ -f "$i.xz" ]; then i="$i.xz" ; prog="xz -cdfq" - else - echo "$0: File \"$i\" not found or not a regular file" 1>&2 - if [ ${retval} = 0 ]; then retval=1 ; fi - continue + if [ ! -f "$i" ]; then + if [ -f "$i.gz" ]; then i="$i.gz" + elif [ -f "$i.bz2" ]; then i="$i.bz2" + elif [ -f "$i.lz" ]; then i="$i.lz" + elif [ -f "$i.xz" ]; then i="$i.xz" + else + echo "$0: File \"$i\" not found or not a regular file" 1>&2 + if [ ${retval} = 0 ]; then retval=1 ; fi + continue + fi fi + bindir=`echo "$0" | sed -e 's,[^/]*$,,'` + prog_name=`"${bindir}"zutils -t -- "$i"` + case "${prog_name}" in + gzip) prog="gzip -cdfq" ;; + bzip2) prog="bzip2 -cdfq" ;; + lzip) prog="lzip -cdfq" ;; + xz) prog="xz -cdfq" ;; + *) prog=cat ;; + esac if [ ${list} = 1 ]; then ${prog} -- "$i" | grep ${args} 2>&1 > /dev/null && echo "$i" r=$? @@ -110,11 +111,11 @@ for i in "$@" ; do ${prog} -- "$i" | grep ${args} r=$? else - j=`printf "%s" "$i" | sed 's/\\\\/\\\\\\\\/g'` - j=`printf "%s" "$j" | sed 's/|/\\\\|/g'` - j=`printf "%s" "$j" | sed 's/&/\\\\&/g'` + j=`printf "%s" "$i" | sed -e 's,\\\\,\\\\\\\\,g'` + j=`printf "%s" "$j" | sed -e 's,|,\\\\|,g'` + j=`printf "%s" "$j" | sed -e 's,&,\\\\&,g'` j=`printf "%s" "$j" | tr '\n' ' '` - ${prog} -- "$i" | grep ${args} | sed "s|^|${j}:|" + ${prog} -- "$i" | grep ${args} | sed -e "s,^,${j}:," r=$? fi [ $r != 0 ] && retval="$r" |