系列中的上一篇
当前教程
遍历文件树
系列中的下一篇

系列中的上一篇: 列出目录的内容

系列中的下一篇: 监视目录更改

遍历文件树

您是否需要创建一个应用程序,该应用程序将递归访问文件树中的所有文件?也许您需要删除树中的所有 .class 文件,或者找到所有在过去一年中未访问过的文件。您可以使用 FileVisitor 接口来实现。

 

FileVisitor 接口

要遍历文件树,您首先需要实现一个 FileVisitor。一个 FileVisitor 指定了在遍历过程中的关键点所需的行為:访问文件时,访问目录之前,访问目录之后,或发生错误时。该接口有四个方法对应于这些情况

  • preVisitDirectory() – 在访问目录的条目之前调用。
  • postVisitDirectory() – 在访问目录中的所有条目之后调用。如果遇到任何错误,则将特定异常传递给该方法。
  • visitFile() – 在访问的文件上调用。该文件的 BasicFileAttributes 被传递给该方法,或者您可以使用文件属性包来读取特定的一组属性。例如,您可以选择读取文件的 DosFileAttributeView 来确定该文件是否设置了“隐藏”位。
  • visitFileFailed() – 当无法访问文件时调用。特定异常被传递给该方法。您可以选择是抛出异常,将其打印到控制台或日志文件,等等。

如果您不需要实现所有四个 FileVisitor 方法,那么您可以扩展 SimpleFileVisitor 类,而不是实现 FileVisitor 接口。此类是一个适配器,它实现了 FileVisitor 接口,遍历树中的所有文件,并在遇到错误时抛出一个 IOError。您可以扩展此类并仅覆盖您需要的那些方法。

以下是一个扩展 SimpleFileVisitor 以打印文件树中的所有条目的示例。它打印条目,无论条目是普通文件、符号链接、目录还是其他“未指定”类型的文件。它还打印每个文件的以字节为单位的大小。遇到的任何异常都将打印到控制台。

以下代码显示了 FileVisitor 方法

import static java.nio.file.FileVisitResult.*;

public static class PrintFiles
    extends SimpleFileVisitor<Path> {

    // Print information about
    // each type of file.
    @Override // from FileVisitor
    public FileVisitResult visitFile(Path file,
                                   BasicFileAttributes attr) {
        if (attr.isSymbolicLink()) {
            System.out.format("Symbolic link: %s ", file);
        } else if (attr.isRegularFile()) {
            System.out.format("Regular file: %s ", file);
        } else {
            System.out.format("Other: %s ", file);
        }
        System.out.println("(" + attr.size() + "bytes)");
        return CONTINUE;
    }

    // Print each directory visited.
    @Override // from FileVisitor
    public FileVisitResult postVisitDirectory(Path dir,
                                          IOException exc) {
        System.out.format("Directory: %s%n", dir);
        return CONTINUE;
    }

    // If there is some error accessing
    // the file, let the user know.
    // If you don't override this method
    // and an error occurs, an IOException
    // is thrown.
    @Override // from FileVisitor
    public FileVisitResult visitFileFailed(Path file,
                                       IOException exc) {
        System.err.println(exc);
        return CONTINUE;
    }
}

 

启动进程

实现 FileVisitor 后,如何启动文件遍历?Files 类中有两个 walkFileTree() 方法。

第一个方法只需要一个起点和您的 FileVisitor 的实例。您可以按如下方式调用 PrintFiles 文件访问器

Path startingDir = ...;
PrintFiles pf = new PrintFiles();
Files.walkFileTree(startingDir, pf);

第二个 walkFileTree() 方法使您能够另外指定访问级别的限制和一组 FileVisitOption 枚举。如果您想确保此方法遍历整个文件树,您可以为最大深度参数指定 Integer.MAX_VALUE

您可以指定 FileVisitOption 枚举,FOLLOW_LINKS,它表示应遵循符号链接。

此代码片段显示了如何调用四个参数方法

import static java.nio.file.FileVisitResult.*;

Path startingDir = ...;

EnumSet<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);

Finder finder = new Finder(pattern);
Files.walkFileTree(startingDir, opts, Integer.MAX_VALUE, finder);

 

创建 FileVisitor 时需要考虑的事项

文件树是深度优先遍历的,但您不能对子目录的迭代顺序做出任何假设。

如果您的程序将更改文件系统,您需要仔细考虑如何实现您的 FileVisitor

例如,如果您正在编写递归删除,您首先删除目录中的文件,然后再删除目录本身。在这种情况下,您在 postVisitDirectory() 中删除目录。

如果您正在编写递归复制,您在尝试将文件复制到它(在 visitFiles() 中)之前,在 preVisitDirectory() 中创建新目录。如果您想保留源目录的属性(类似于 UNIX cp -p 命令),您需要在文件复制完成后在 postVisitDirectory() 中执行此操作。 Copy 示例展示了如何执行此操作。

如果您正在编写文件搜索,您将在 visitFile() 方法中执行比较。此方法找到与您的条件匹配的所有文件,但它不会找到目录。如果您想找到文件和目录,您还必须在 preVisitDirectory()postVisitDirectory() 方法中执行比较。 Find 示例展示了如何执行此操作。

您需要决定是否要遵循符号链接。例如,如果您正在删除文件,则可能不建议遵循符号链接。如果您正在复制文件树,您可能希望允许它。默认情况下,walkFileTree() 不会遵循符号链接。

visitFile() 方法针对文件调用。如果您指定了 FOLLOW_LINKS 选项,并且您的文件树具有到父目录的循环链接,则循环目录将在 visitFileFailed() 方法中使用 FileSystemLoopException 报告。以下代码片段展示了如何捕获循环链接,它来自 Copy 示例: visitFile() 方法针对文件调用。如果您指定了 FOLLOW_LINKS 选项,并且您的文件树具有到父目录的循环链接,则循环目录将在 visitFileFailed() 方法中使用 FileSystemLoopException 报告。以下代码片段展示了如何捕获循环链接,它来自 Copy 示例

@Override
public FileVisitResult
    visitFileFailed(Path file,
        IOException exc) {
    if (exc instanceof FileSystemLoopException) {
        System.err.println("cycle detected: " + file);
    } else {
        System.err.format("Unable to copy:" + " %s: %s%n", file, exc);
    }
    return CONTINUE;
}

这种情况仅在程序遵循符号链接时才会发生。

 

控制流程

也许您想遍历文件树以查找特定目录,并在找到该目录时,您希望该过程终止。也许您想跳过特定目录。

FileVisitor 方法返回一个 FileVisitResult 值。您可以通过在 FileVisitor 方法中返回的值来中止文件遍历过程或控制是否访问目录。

  • CONTINUE – 表示文件遍历应继续。如果 preVisitDirectory() 方法返回 CONTINUE,则访问该目录。
  • TERMINATE – 立即中止文件遍历。返回此值后,不会再调用任何其他文件遍历方法。
  • SKIP_SUBTREE – 当 preVisitDirectory() 返回此值时,将跳过指定的目录及其子目录。此分支将从树中“剪除”。
  • SKIP_SIBLINGS – 当 preVisitDirectory() 返回此值时,不会访问指定的目录,不会调用 postVisitDirectory(),并且不会访问任何其他未访问的同级目录。如果从 postVisitDirectory() 方法返回,则不会访问任何其他同级目录。本质上,在指定的目录中不会发生任何其他事情。

在此代码段中,将跳过任何名为 SCCS 的目录。

public FileVisitResult
     preVisitDirectory(Path dir,
         BasicFileAttributes attrs) {
    (if (dir.getFileName().toString().equals("SCCS")) {
         return SKIP_SUBTREE;
    }
    return CONTINUE;
}

在此代码段中,一旦找到特定文件,就会将文件名打印到标准输出,并且文件遍历将终止。

import static java.nio.file.FileVisitResult.*;

// The file we are looking for.
Path lookingFor = ...;

public FileVisitResult
    visitFile(Path file,
        BasicFileAttributes attr) {
    if (file.getFileName().equals(lookingFor)) {
        System.out.println("Located file: " + file);
        return TERMINATE;
    }
    return CONTINUE;
}

 

查找文件

如果您曾经使用过 shell 脚本,那么您很可能使用过模式匹配来查找文件。事实上,您可能已经广泛使用过它。如果您没有使用过它,模式匹配使用特殊字符来创建模式,然后可以将文件名与该模式进行比较。例如,在大多数 shell 脚本中,星号 * 匹配任意数量的字符。例如,以下命令列出当前目录中以 .html 结尾的所有文件

$ ls *.html

java.nio.file 包为此有用功能提供了编程支持。每个文件系统实现都提供一个 PathMatcher。您可以使用 FileSystem 类中的 getPathMatcher(String) 方法检索文件系统的 PathMatcher。以下代码段获取默认文件系统的路径匹配器

String pattern = ...;
PathMatcher matcher =
    FileSystems.getDefault().getPathMatcher("glob:" + pattern);

传递给 getPathMatcher(String) 的字符串参数指定语法风格和要匹配的模式。此示例指定 glob 语法。如果您不熟悉 glob 语法,请参阅部分 什么是 Glob

glob 语法易于使用且灵活,但如果您愿意,也可以使用正则表达式或 regex 语法。有关 regex 的更多信息,请参阅部分 正则表达式。一些文件系统实现可能支持其他语法。

如果您想使用其他形式的基于字符串的模式匹配,您可以创建自己的 PathMatcher 类。此页面中的示例使用 glob 语法。

创建 PathMatcher 实例后,您就可以开始使用它来匹配文件。PathMatcher 接口有一个方法 matches(),它接受一个 Path 参数并返回一个 boolean 值:它要么匹配模式,要么不匹配。以下代码段查找以 .java.class 结尾的文件,并将这些文件打印到标准输出

PathMatcher matcher =
    FileSystems.getDefault().getPathMatcher("glob:*.{java,class}");

Path filename = ...;
if (matcher.matches(filename)) {
    System.out.println(filename);
}

递归模式匹配

搜索与特定模式匹配的文件与遍历文件树密切相关。您知道文件在文件系统中的某个位置,但您知道在哪里吗?或者您可能需要查找文件树中所有具有特定文件扩展名的文件。

Find 示例正是这样做的。Find 类似于 UNIX find 实用程序,但功能已精简。您可以扩展此示例以包含其他功能。例如,find 实用程序支持 -prune 标志来排除整个子树。您可以通过在 preVisitDirectory() 方法中返回 SKIP_SUBTREE 来实现该功能。要实现 -L 选项(它遵循符号链接),您可以使用四个参数的 walkFileTree(Path, Set, int, FileVisitor) 方法并传入 FOLLOW_LINKS 枚举(但请确保在 visitFile() 方法中测试循环链接)。

要运行 Find 应用程序,请使用以下格式

$ java Find <path> -name "<glob_pattern>"

模式放在引号内,因此 shell 不会解释任何通配符。例如

$ java Find . -name "*.html"

 

Find 示例

以下是 Find 示例的源代码

/**
 * Sample code that finds files that match the specified glob pattern.
 * For more information on what constitutes a glob pattern, see
 * https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob
 *
 * The file or directories that match the pattern are printed to
 * standard out.  The number of matches is also printed.
 *
 * When executing this application, you must put the glob pattern
 * in quotes, so the shell will not expand any wild cards:
 *              java Find . -name "*.java"
 */

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import static java.nio.file.FileVisitResult.*;
import static java.nio.file.FileVisitOption.*;
import java.util.*;


public class Find {

    public static class Finder
        extends SimpleFileVisitor<Path> {

        private final PathMatcher matcher;
        private int numMatches = 0;

        Finder(String pattern) {
            matcher = FileSystems.getDefault()
                    .getPathMatcher("glob:" + pattern);
        }

        // Compares the glob pattern against
        // the file or directory name.
        void find(Path file) {
            Path name = file.getFileName();
            if (name != null && matcher.matches(name)) {
                numMatches++;
                System.out.println(file);
            }
        }

        // Prints the total number of
        // matches to standard out.
        void done() {
            System.out.println("Matched: "
                + numMatches);
        }

        // Invoke the pattern matching
        // method on each file.
        @Override
        public FileVisitResult visitFile(Path file,
                BasicFileAttributes attrs) {
            find(file);
            return CONTINUE;
        }

        // Invoke the pattern matching
        // method on each directory.
        @Override
        public FileVisitResult preVisitDirectory(Path dir,
                BasicFileAttributes attrs) {
            find(dir);
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file,
                IOException exc) {
            System.err.println(exc);
            return CONTINUE;
        }
    }

    static void usage() {
        System.err.println("java Find <path>" +
            " -name \"<glob_pattern>\"");
        System.exit(-1);
    }

    public static void main(String[] args)
        throws IOException {

        if (args.length < 3 || !args[1].equals("-name"))
            usage();

        Path startingDir = Paths.get(args[0]);
        String pattern = args[2];

        Finder finder = new Finder(pattern);
        Files.walkFileTree(startingDir, finder);
        finder.done();
    }
}

 

复制示例

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.EnumSet;
import java.util.stream.Stream;

import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

/**
 * Sample code that copies files recursively 
 * from a source directory to a destination folder.
 * The maximum number of directory levels to copy 
 * is specified after -depth.
 * The number of files copied is printed
 * to standard out.
 * You can execute the application using:
 * @code java Copy . new -depth 4
 */

public class Copy {

    /**
     * A {@code FileVisitor} that finds
     * all files that match the
     * specified pattern.
     */
    public static class Replicator
            extends SimpleFileVisitor<Path> {

        Path source;
        Path destination;


        public Replicator(Path source, Path destination) {
            this.source = source;
            this.destination = destination;
        }

        // Prints the total number of
        // files copied to standard out.
        void done() throws IOException {
            try (Stream<Path> path = Files.list(Paths.get(destination.toUri()))) {
                System.out.println("Number of files copied: "
                        + path.filter(p -> p.toFile().isFile()).count());
            }

        }

        // Copy a file in destination
        @Override
        public FileVisitResult visitFile(Path file,
                                         BasicFileAttributes attrs) {
            System.out.println("Copy file: " + file);
            Path newFile = destination.resolve(source.relativize(file));
            try{
                Files.copy(file,newFile);
            }
            catch (IOException ioException){
                //log it and move
            }
            return CONTINUE;
        }

        // Invoke copy of a directory.
        @Override
        public FileVisitResult preVisitDirectory(Path dir,
                                                 BasicFileAttributes attrs) {
            System.out.println("Copy directory: " + dir);
            Path targetDir = destination.resolve(source.relativize(dir));
            try {
                Files.copy(dir, targetDir, REPLACE_EXISTING, COPY_ATTRIBUTES);
            } catch (IOException e) {
                System.err.println("Unable to create " + targetDir + " [" + e + "]");
                return SKIP_SUBTREE;
            }

            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            if (exc == null) {
                Path destination = this.destination.resolve(source.relativize(dir));
                try {
                    FileTime time = Files.getLastModifiedTime(dir);
                    Files.setLastModifiedTime(destination, time);
                } catch (IOException e) {
                    System.err.println("Unable to copy all attributes to: " + destination + " [" + e + "]");
                }
            } else {
                throw exc;
            }

            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file,
                                               IOException exc) {
            if (exc instanceof FileSystemLoopException) {
                System.err.println("cycle detected: " + file);
            } else {
                System.err.format("Unable to copy:" + " %s: %s%n", 
                        file, exc);
            }
            return CONTINUE;
        }
    }

    static void usage() {
        System.err.println("java Copy <source> <destination>" +
                " -depth \"<max_level_dir>\"");
        System.exit(-1);
    }

    public static void main(String[] args)
            throws IOException {

        if (args.length < 4 || !args[2].equals("-depth"))
            usage();

        Path source = Paths.get(args[0]);
        Path destination = Paths.get(args[1]);
        int depth = Integer.parseInt(args[3]);

        Replicator walk = new Replicator(source, destination);
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        Files.walkFileTree(source, opts, depth, walk);
        walk.done();
    }
}

 

Chmod 示例

import java.nio.file.*;
import java.nio.file.attribute.*;
import static java.nio.file.attribute.PosixFilePermission.*;
import static java.nio.file.FileVisitResult.*;
import java.io.IOException;
import java.util.*;

/**
 * Sample code that changes the permissions of files in a similar manner to the
 * chmod(1) program.
 */

public class Chmod {

    /**
     * Compiles a list of one or more <em>symbolic mode expressions</em> that
     * may be used to change a set of file permissions. This method is
     * intended for use where file permissions are required to be changed in
     * a manner similar to the UNIX <i>chmod</i> program.
     *
     * <p> The {@code exprs} parameter is a comma separated list of expressions
     * where each takes the form:
     * <blockquote>
     * <i>who operator</i> [<i>permissions</i>]
     * </blockquote>
     * where <i>who</i> is one or more of the characters {@code 'u'}, {@code 'g'},
     * {@code 'o'}, or {@code 'a'} meaning the owner (user), group, others, or
     * all (owner, group, and others) respectively.
     *
     * <p> <i>operator</i> is the character {@code '+'}, {@code '-'}, or {@code
     * '='} signifying how permissions are to be changed. {@code '+'} means the
     * permissions are added, {@code '-'} means the permissions are removed, and
     * {@code '='} means the permissions are assigned absolutely.
     *
     * <p> <i>permissions</i> is a sequence of zero or more of the following:
     * {@code 'r'} for read permission, {@code 'w'} for write permission, and
     * {@code 'x'} for execute permission. If <i>permissions</i> is omitted
     * when assigned absolutely, then the permissions are cleared for
     * the owner, group, or others as identified by <i>who</i>. When omitted
     * when adding or removing then the expression is ignored.
     *
     * <p> The following examples demonstrate possible values for the {@code
     * exprs} parameter:
     *
     * <table border="0">
     * <tr>
     *   <td> {@code u=rw} </td>
     *   <td> Sets the owner permissions to be read and write. </td>
     * </tr>
     * <tr>
     *   <td> {@code ug+w} </td>
     *   <td> Sets the owner write and group write permissions. </td>
     * </tr>
     * <tr>
     *   <td> {@code u+w,o-rwx} </td>
     *   <td> Sets the owner write, and removes the others read, others write
     *     and others execute permissions. </td>
     * </tr>
     * <tr>
     *   <td> {@code o=} </td>
     *   <td> Sets the others permission to none (others read, others write and
     *     others execute permissions are removed if set) </td>
     * </tr>
     * </table>
     *
     * @param   exprs
     *          List of one or more <em>symbolic mode expressions</em>
     *
     * @return  A {@code Changer} that may be used to changer a set of
     *          file permissions
     *
     * @throws  IllegalArgumentException
     *          If the value of the {@code exprs} parameter is invalid
     */
    public static Changer compile(String exprs) {
        // minimum is who and operator (u= for example)
        if (exprs.length() < 2)
            throw new IllegalArgumentException("Invalid mode");

        // permissions that the changer will add or remove
        final Set<PosixFilePermission> toAdd = new HashSet<PosixFilePermission>();
        final Set<PosixFilePermission> toRemove = new HashSet<PosixFilePermission>();

        // iterate over each of expression modes
        for (String expr: exprs.split(",")) {
            // minimum of who and operator
            if (expr.length() < 2)
                throw new IllegalArgumentException("Invalid mode");

            int pos = 0;

            // who
            boolean u = false;
            boolean g = false;
            boolean o = false;
            boolean done = false;
            for (;;) {
                switch (expr.charAt(pos)) {
                    case 'u' : u = true; break;
                    case 'g' : g = true; break;
                    case 'o' : o = true; break;
                    case 'a' : u = true; g = true; o = true; break;
                    default : done = true;
                }
                if (done)
                    break;
                pos++;
            }
            if (!u && !g && !o)
                throw new IllegalArgumentException("Invalid mode");

            // get operator and permissions
            char op = expr.charAt(pos++);
            String mask = (expr.length() == pos) ? "" : expr.substring(pos);

            // operator
            boolean add = (op == '+');
            boolean remove = (op == '-');
            boolean assign = (op == '=');
            if (!add && !remove && !assign)
                throw new IllegalArgumentException("Invalid mode");

            // who= means remove all
            if (assign && mask.length() == 0) {
                assign = false;
                remove = true;
                mask = "rwx";
            }

            // permissions
            boolean r = false;
            boolean w = false;
            boolean x = false;
            for (int i=0; i<mask.length(); i++) {
                switch (mask.charAt(i)) {
                    case 'r' : r = true; break;
                    case 'w' : w = true; break;
                    case 'x' : x = true; break;
                    default:
                        throw new IllegalArgumentException("Invalid mode");
                }
            }

            // update permissions set
            if (add) {
                if (u) {
                    if (r) toAdd.add(OWNER_READ);
                    if (w) toAdd.add(OWNER_WRITE);
                    if (x) toAdd.add(OWNER_EXECUTE);
                }
                if (g) {
                    if (r) toAdd.add(GROUP_READ);
                    if (w) toAdd.add(GROUP_WRITE);
                    if (x) toAdd.add(GROUP_EXECUTE);
                }
                if (o) {
                    if (r) toAdd.add(OTHERS_READ);
                    if (w) toAdd.add(OTHERS_WRITE);
                    if (x) toAdd.add(OTHERS_EXECUTE);
                }
            }
            if (remove) {
                if (u) {
                    if (r) toRemove.add(OWNER_READ);
                    if (w) toRemove.add(OWNER_WRITE);
                    if (x) toRemove.add(OWNER_EXECUTE);
                }
                if (g) {
                    if (r) toRemove.add(GROUP_READ);
                    if (w) toRemove.add(GROUP_WRITE);
                    if (x) toRemove.add(GROUP_EXECUTE);
                }
                if (o) {
                    if (r) toRemove.add(OTHERS_READ);
                    if (w) toRemove.add(OTHERS_WRITE);
                    if (x) toRemove.add(OTHERS_EXECUTE);
                }
            }
            if (assign) {
                if (u) {
                    if (r) toAdd.add(OWNER_READ);
                      else toRemove.add(OWNER_READ);
                    if (w) toAdd.add(OWNER_WRITE);
                      else toRemove.add(OWNER_WRITE);
                    if (x) toAdd.add(OWNER_EXECUTE);
                      else toRemove.add(OWNER_EXECUTE);
                }
                if (g) {
                    if (r) toAdd.add(GROUP_READ);
                      else toRemove.add(GROUP_READ);
                    if (w) toAdd.add(GROUP_WRITE);
                      else toRemove.add(GROUP_WRITE);
                    if (x) toAdd.add(GROUP_EXECUTE);
                      else toRemove.add(GROUP_EXECUTE);
                }
                if (o) {
                    if (r) toAdd.add(OTHERS_READ);
                      else toRemove.add(OTHERS_READ);
                    if (w) toAdd.add(OTHERS_WRITE);
                      else toRemove.add(OTHERS_WRITE);
                    if (x) toAdd.add(OTHERS_EXECUTE);
                      else toRemove.add(OTHERS_EXECUTE);
                }
            }
        }

        // return changer
        return new Changer() {
            @Override
            public Set<PosixFilePermission> change(Set<PosixFilePermission> perms) {
                perms.addAll(toAdd);
                perms.removeAll(toRemove);
                return perms;
            }
        };
    }

    /**
     * A task that <i>changes</i> a set of {@link PosixFilePermission} elements.
     */
    public interface Changer {
        /**
         * Applies the changes to the given set of permissions.
         *
         * @param   perms
         *          The set of permissions to change
         *
         * @return  The {@code perms} parameter
         */
        Set<PosixFilePermission> change(Set<PosixFilePermission> perms);
    }

    /**
     * Changes the permissions of the file using the given Changer.
     */
    static void chmod(Path file, Changer changer) {
        try {
            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
            Files.setPosixFilePermissions(file, changer.change(perms));
        } catch (IOException x) {
            System.err.println(x);
        }
    }

    /**
     * Changes the permission of each file and directory visited
     */
    static class TreeVisitor implements FileVisitor<Path> {
        private final Changer changer;

        TreeVisitor(Changer changer) {
            this.changer = changer;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
            chmod(dir, changer);
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            chmod(file, changer);
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
            if (exc != null)
                System.err.println("WARNING: " + exc);
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            System.err.println("WARNING: " + exc);
            return CONTINUE;
        }
    }

    static void usage() {
        System.err.println("java Chmod [-R] symbolic-mode-list file...");
        System.exit(-1);
    }

    public static void main(String[] args) throws IOException {
        if (args.length < 2)
            usage();
        int argi = 0;
        int maxDepth = 0;
        if (args[argi].equals("-R")) {
            if (args.length < 3)
                usage();
            argi++;
            maxDepth = Integer.MAX_VALUE;
        }

        // compile the symbolic mode expressions
        Changer changer = compile(args[argi++]);
        TreeVisitor visitor = new TreeVisitor(changer);

        Set<FileVisitOption> opts = Collections.emptySet();
        while (argi < args.length) {
            Path file = Paths.get(args[argi]);
            Files.walkFileTree(file, opts, maxDepth, visitor);
            argi++;
        }
    }
}

上次更新: 2024 年 1 月 4 日


系列中的上一篇
当前教程
遍历文件树
系列中的下一篇

系列中的上一篇: 列出目录的内容

系列中的下一篇: 监视目录更改