All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unkrig.zz.find.Main Maven / Gradle / Ivy

There is a newer version: 1.3.10
Show newest version

/*
 * de.unkrig.find - An advanced version of the UNIX FIND utility
 *
 * Copyright (c) 2011, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. The name of the author may not be used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package de.unkrig.zz.find;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.Provider;
import java.security.Provider.Service;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

import de.unkrig.commons.file.org.apache.commons.compress.archivers.ArchiveFormatFactory;
import de.unkrig.commons.file.org.apache.commons.compress.compressors.CompressionFormatFactory;
import de.unkrig.commons.lang.AssertionUtil;
import de.unkrig.commons.lang.protocol.ConsumerWhichThrows;
import de.unkrig.commons.lang.protocol.ProducerUtil;
import de.unkrig.commons.text.LevelFilteredPrinter;
import de.unkrig.commons.text.Printers;
import de.unkrig.commons.text.pattern.Glob;
import de.unkrig.commons.text.pattern.Pattern2;
import de.unkrig.commons.util.CommandLineOptionException;
import de.unkrig.commons.util.CommandLineOptions;
import de.unkrig.commons.util.annotation.CommandLineOption;
import de.unkrig.commons.util.annotation.RegexFlags;
import de.unkrig.commons.util.logging.SimpleLogging;

/**
 * A FIND utility that can recurse into directories, archive files and compressed files.
 */
public final
class Main {

    static { AssertionUtil.enableAssertionsForThisClass(); }

    private Main() {}

    private final Find find    = new Find();

    private final LevelFilteredPrinter levelFilteredPrinter = new LevelFilteredPrinter(Printers.get());

    /**
     * A command line utility to find files, directories and archive entries by various criteria, and optionally
     * execute some actions.
     * 

Usage

*
*
{@code zzfind} [ option ] ... file-or-dir ... [ expression ]
*
* Apply "expression" to "file-or-dir ..." and all nested directory members, * archive entries and compressed files. *
*
*

* File name "-" stands for STDIN. *

* *

Options

* *
* {@main.commandLineOptions} *
* *

Expressions

* * Expressions are either tests or actions. Both evaluate to a boolean value. * *

Tests

*
*
{@code -name} glob
*
* Name matches "glob" (see below). *
*
{@code -path} glob
*
* The full path (e.g. "{@code dir/file.zip!dir/file.zip!dir/file}" or "{@code dir/file}") matches * "glob" (see below). *
*
{@code -type} glob
*
* Whether the type matches the glob. See the "type" property, described below. * If you are unsure about types, run {@code zzfind} with -echo "@{type} @{path}" first. *
*
{@code -readable}
*
* Whether this file is readable. *
*
{@code -writable}
*
* Whether this file is writable. *
*
{@code -executable}
*
* Whether this file is executable. *
*
{@code -size} N
*
{@code -size -}N
*
{@code -size +}N
*
* Whether the size is exactly/less than/more than N (e.g. "{@code 100}", "{@code -1K}", * "{@code +10M}"). *
*
{@code -mtime} N
*
{@code -mtime +}N
*
{@code -mtime -}N
*
* Whether this file/archive entry was last modified (exactly/more than/less than) N days ago * (N=0: 0...24h, N=1: 24...48h, ...). *
*
{@code -mmin} N
*
{@code -mmin +}N
*
{@code -mmin -}N
*
* Whether this file/archive entry was last modified (exactly/more than/less than) N minutes ago * (N=0: 0...59sec, N=1: 60...119sec, ...). *
*
exp1 {@code -a} exp2
*
exp1 exp2
*
* Whether both exp1 and exp2 are true. exp2 is not evaluated if * exp1 is false. *
*
exp1 {@code -o} exp2
*
* Whether exp1 or exp2 is true. exp2 is not evaluated if exp1 * is true. *
*
exp1 {@code ,} exp2
*
* Evaluates both expressions and returns the result of exp2. *
*
{@code -not} exp
*
{@code !} exp
*
* Whether exp is false. *
*
{@code (} exp {@code )}
*
* Whether exp is true. *
*
* *

Actions

*
*
{@code -print}
*
* Print file path and return true. *
*
{@code -echo} message
*
* Print the message and return true. * All occurrences of "@{property-name}" in the message are replaced with * the value of the property. For the list of supported properties, see section "Properties of files and * archive entries", below. *
*
{@code -ls}
*
* Print file type, readability, writability, executability, size, modification time and path, and return true. *
*
{@code -exec} word{@code ... ;}
*
* Execute "word{@code ...}" as an external command; "{@code {}}" is replaced with the current * file's path (which may contain "{@code !}" and would then NOT denote a physical file in the file system). *
*
{@code -pipe} word{@code ... ;}
*
* Copy the file contents to the standard input of an external command "word{@code ...}"; * "{@code {}}" is replaced with the current file's path (which may contain "{@code !}" and would then * NOT denote a physical file in the file system). *
*
{@code -cat}
*
* Print file contents and return true. *
*
{@code -copy} tofile
*
* Copy file contents to the named file. * All occurrences of "@{property-name}" in the tofile are replaced with * the value of the property. For the list of supported properties, see section "Properties of files and * archive entries", below. *
*
{@code -disassemble} [ {@code -hideLines} ] [ {@code -hideVars} ]
*
* Disassembles a Java class file. *
*
{@code -digest} algorithm
*
* Calculate a "message digest" of the contents, print it and return true. *
* Algorithms available in this environment are: ${digest.providers} *
*
{@code -checksum} CRC32|ADLER32
*
* Calculate a checksum of the contents, print it and return true. *
*
*
{@code -true}
*
* Return {@code true}. *
*
{@code -false}
*
* Return {@code false}. *
*
*

* If no action is given, then "{@code -print}" is implicitly added. *

* *

Properties of files and archive entries

*

* Various tests and actions (e.g. "{@code -echo}") have access to a set of "properties" of the current file * or archive entry. *

*
*
{@code "name"}:
*
* The last component of the path. *
*
{@code "path"}:
*
* The path to the resources as it was found. "!" indicates an archive, "%" a compressed file. *
*
{@code "depth"}:
*
* Zero for the files/directories specified on the command line, +1 for files/directories in a subdirectory, * +1 for compressed content, and +1 for the entries of archive files. *
*
{@code "type"}:
*
* The "type" of the current subject; the actual types are: *
*
{@code directory}
*
A directory
*
{@code file}
*
A (non-archive, not-compressed) file
*
{@code archive-file}
*
An archive file
*
{@code compressed-file}
*
A compressed file
*
{@code archive}
*
A nested archive
*
{@code normal-contents}
*
Normal (non-archive, not-compressed) content
*
{@code directory-entry}
*
A "directory entry" in an archive.
*
*
*
{@code "absolutePath"}:
*
* The absolute path of the file or directory, typically by resolving relative paths against the current user * directory. For all other types: That of the enclosing file. *
*
{@code "canonicalPath"}:
*
* The absolute path of the file or directory, typically by resolving relative paths against the current user * directory and resolving all "." and ".." infixes. For all other types: That of the enclosing file. *
*
{@code "lastModifiedDate"}:
*
* The date and time of the last modification of the file, directory or archive entry, in the format "{@code * dow mon dd hh:mm:ss zzz yyyy}". *
*
{@code "size"}:
*
* The size, in bytes, for types "archive", "archive-file", "compressed-contents", "compressed-file", "file" or * "normal-contents", -1 for type "directory-entry", and 0 for type "directory". *
*
{@code "isDirectory"}:
*
* Whether the type is "directory" or "directory-entry". *
*
{@code "isFile"}:
*
* Whether the type is "archive", "archive-file", "compressed-contents", "compressed-file", "directory-entry", * "file" or "normal-contents". *
*
{@code "isHidden"}:
*
* Whether the file or directory is "hidden" in the file system; {@code false} for all other types. *
*
{@code "isReadable"}:
*
* Whether the file or directory is "readable" in the file system; {@code false} for all other types. *
*
{@code "isWritable"}:
*
* Whether the file or directory is "writable" in the file system; {@code false} for all other types. *
*
{@code "isExecutable"}:
*
* Whether the file or directory is "executable" in the file system; {@code false} for all other types. *
*
{@code "archiveFormat"}:
*
* For types "archive" and "archive-file": The format. *
* For types "normal-contents", "compressed-contents" and "directory-entry": The format of the immediately * enclosing archive. *
* For all other types: Empty. *
*
{@code "compressionFormat"}:
*
* For types "compressed-contents" and "compressed-file": The format. *
* For type "normal-contents": The format of the immediately enclosing compressed contents. *
* For all other types: Empty. *
*
*

* Some archive entries have additional properties: *

*
*
Archive format "ar":
*
* "groupId", "mode", "userId" *
*
Archive format "arj":
*
* "hostOs", "method", "mode", "unixMode", "isHostOsUnix" *
*
Archive format "cpio":
*
* "alignmentBoundary", "chksum", "dataPadCount", "device", "format", "gID", "headerPadCount", "headerSize", * "inode", "mode", "numberOfLinks", "remoteDevice", "uID", "isBlockDevice", "isNetwork", "isPipe", * "isRegularFile", "isSocket", "isSymbolicLink" *
*
Archive format "dump":
*
* "accessTime", "creationTime", "entrySize", "generation", "groupId", "headerCount", "headerHoles", * "headerType", "ino", "mode", "nlink", "offset", "originalName", "permissions", "simpleName", userId", * "volume", "isBlkDev", "isChrDev", isDeleted", "isFifo", isSocket" *
*
Archive format "7z":
*
* "accessDate", "crcValue", "creationDate", hasAccessDate", "hasCrc", hasCreationDate", "hasLastModifiedDate", * "hasWindowsAttributes", "windowsAttributes", "isAntiItem" *
*
Archive format "tar":
*
* "devMajor", "devMinor", "groupId", "groupName", "linkName", "mode", "modTime", "realSize", "userId", * "userName", "isBlockDevice", "isCharacterDevice", "isExtended", "isFIFO", isGlobalPaxHeader", * "isGNULongLinkEntry", "isGNULongNameEntry", "isGNUSparse", "isLink", "isPaxHeader", "isSymbolicLink" *
*
Archive format "zip":
*
* "externalAttributes", "internalAttributes", "method", "platform", "unixMode", "isUnixSymlink" *
*
* *

Example globs

* *
*
{@code dir/file}
*
* File "{@code file}" in directory "{@code dir}" *
*
{@code file.gz%}
*
* Compressed file "{@code file.gz}" *
*
{@code file.zip!dir/file}
*
* Entry "{@code dir/file}" in archive file "{@code dir/file.zip}" *
*
{@code file.tar.gz%!dir/file}
*
* Entry "{@code dir/file}" in the compressed archive file "{@code file.tar.gz}" *
*
*/x
*
* File "{@code x}" in an immediate subdirectory *
*
**/x
*
* File "{@code x}" in any subdirectory *
*
***/x
*
* File "{@code x}" in any subdirectory, or any entry "**/x" in any archive file in any * subdirectory *
*
{@code a,dir/file.7z!dir/b}
*
* File "{@code a}" and entry "{@code dir/b}" in archive file "{@code dir/file.7z}" *
*
{@code ~*.c}
*
* Files that don't end with "{@code .c}" *
*
{@code ~*.c~*.h}
*
* Files that don't end with "{@code .c}" or "{@code .h}" *
*
{@code ~*.c~*.h,foo.c}
*
* "{@code foo.c}" plus all files that don't end with "{@code .c}" or "{@code .h}" *
*
*/ public static void main(final String[] args) { final Main main = new Main(); Printers.withPrinter(main.levelFilteredPrinter, new Runnable() { @Override public void run() { main.main2(args); } }); } private void main2(String[] args) { try { args = CommandLineOptions.parse(args, this); } catch (CommandLineOptionException cloe) { Printers.error(cloe.getMessage() + ", try \"--help\"."); System.exit(1); } // Parse the file names on the command line. int i = 0; List files = new ArrayList(); for (; i < args.length; i++) { String arg = args[i]; if ((arg.startsWith("-") && !"-".equals(arg)) || "(".equals(arg) || "!".equals(arg)) break; files.add(arg); } // Parse the FIND expression. // Notice: Even ZERO tokens are a valid FIND expression. try { Parser parser = new Parser(ProducerUtil.fromArray(args, i, args.length), System.out); this.find.setExpression(parser.parse()); } catch (Exception e) { Printers.error("Parsing predicates", e); System.exit(1); } final boolean[] hadExceptions = new boolean[1]; ConsumerWhichThrows exceptionHandler = new ConsumerWhichThrows() { @Override public void consume(IOException ioe) { Printers.error(ioe.toString()); hadExceptions[0] = true; } }; this.find.setExceptionHandler(exceptionHandler); // Execute the search. try { for (String file : files) { try { if ("-".equals(file)) { this.find.findInStream(System.in); } else { this.find.findInFile(new File(file)); } } catch (IOException ioe) { exceptionHandler.consume(ioe); } } if (hadExceptions[0]) System.exit(2); } catch (Exception e) { Printers.error(null, e); System.exit(2); } } /** * Print this text and terminate. */ @CommandLineOption public static void help() throws IOException { List algorithms = new ArrayList(); for (Provider p : Security.getProviders()) { for (Service s : p.getServices()) { if ("MessageDigest".equals(s.getType())) { algorithms.add(s.getAlgorithm()); } } } System.setProperty("archive.formats", ArchiveFormatFactory.allFormats().toString()); System.setProperty("compression.formats", CompressionFormatFactory.allFormats().toString()); System.setProperty("digest.providers", algorithms.toString()); CommandLineOptions.printResource(Main.class, "main(String[]).txt", Charset.forName("UTF-8"), System.out); System.exit(0); } /** * Look into compressed and archive contents if the format and the path match the globs. * The default is to look into any recognised archive or compressed contents. *
* Supported archive formats in this runtime configuration are: *
* {@code ${archive.formats}} *
* Supported compression formats in this runtime configuration are: *
* {@code ${compression.formats}} * * @param fomatAndPath format-glob{@code :}path-glob */ @CommandLineOption public void setLookInto(@RegexFlags(Pattern2.WILDCARD | Glob.INCLUDES_EXCLUDES) Glob fomatAndPath) { this.find.setLookIntoFormat(fomatAndPath); } /** * Process each directory's contents before the directory itself, and each archive's entries before the archive * itself, and each compressed contents before the enclosing file or archive entry. */ @CommandLineOption public void setDepth() { this.find.setDepth(true); } /** * Do not apply any tests or actions at levels less than levels. E.g. "1" means "process all files * except the top level files and directories". */ @CommandLineOption public void setMinDepth(int levels) { this.find.setMinDepth(levels); } /** * Descend at most levels of directories below the top level files and directories. "0" means "only * apply the tests and actions to the top level files and directories". *

* If in doubt, try: *

*
zzfind ... -print -echo @{depth}
*/ @CommandLineOption public void setMaxDepth(int levels) { this.find.setMaxDepth(levels); } /** * Suppress all messages except errors. */ @CommandLineOption public void setNowarn() { this.levelFilteredPrinter.setNoWarn(); } /** * Suppress normal output. */ @CommandLineOption public void setQuiet() { this.levelFilteredPrinter.setQuiet(); } /** * Print verbose messages. */ @CommandLineOption public void setVerbose() { this.levelFilteredPrinter.setVerbose(); } /** * Print verbose and debug messages. */ @CommandLineOption public void setDebug() { this.levelFilteredPrinter.setDebug(); } /** * Add logging at level {@code FINE} on logger "{@code de.unkrig}" to STDERR using the "{@code FormatFormatter}" * and "{@code SIMPLE}" format, or the given arguments, which are all optional. * * @param spec level{@code :}logger{@code :}handler{@code * :}formatter{@code :}format */ @CommandLineOption(cardinality = CommandLineOption.Cardinality.ANY) public static void addLog(String spec) { SimpleLogging.configureLoggers(spec); } /** * (These are no longer supported; use "--look-into" instead.) */ @CommandLineOption(name = { "-zip", "-zz", "-nested-zip", "-z" }) public static void noLongerSupported() { System.err.println("Command line option is no longer supported - try \"--help\"."); System.exit(1); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy