385. Java IO API - Chmod 示例:模拟 chmod 命令的文件权限更改
在这个示例中,我们将展示如何通过 Java 更改文件和目录的权限,类似于 UNIX 系统中的 chmod 命令。代码实现使用了 java.nio.file 包中的 API,特别是 PosixFilePermission 和 FileVisitor。
java
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++;
}
}
}
代码结构说明
-
compile 方法: 这个方法接受一个符号模式表达式(symbolic mode expression),并生成一个
Changer对象,用于更改文件权限。符号模式表达式的格式如下:who(谁):指定权限应用的对象,可以是u(用户)、g(组)、o(其他用户)或a(所有用户)。operator(操作符):+(添加权限)、-(移除权限)、=(设置绝对权限)。permissions(权限):r(读)、w(写)、x(执行)。
例如:
u=rw:设置用户(owner)具有读和写权限。ug+w:设置用户和组添加写权限。o-:移除其他用户的所有权限。
-
Changer 接口:
Changer接口的change方法接受当前权限的集合,并返回一个新的权限集合,应用了compile方法中定义的更改。 -
chmod 方法: 这个方法用来实际改变文件的权限。它首先获取当前文件的权限,然后调用
Changer对象的change方法来更改权限,最后将修改后的权限设置回文件。 -
TreeVisitor 类:
TreeVisitor实现了FileVisitor接口,它会遍历目录树中的每个文件,并对每个文件或目录应用权限更改。preVisitDirectory和visitFile方法会在访问目录或文件时调用chmod方法修改其权限。 -
main 方法:
main方法是程序的入口,它解析命令行参数,调用compile方法生成权限更改器,并使用FileVisitor遍历指定目录中的所有文件。
示例代码解释
java
public class Chmod {
public static Changer compile(String exprs) {
// 验证表达式的格式
// ...
// 解析权限更改的集合
// ...
// 返回权限更改器
return new Changer() {
@Override
public Set<PosixFilePermission> change(Set<PosixFilePermission> perms) {
perms.addAll(toAdd); // 添加权限
perms.removeAll(toRemove); // 移除权限
return perms;
}
};
}
static void chmod(Path file, Changer changer) {
try {
// 获取文件当前的权限
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
// 应用权限更改
Files.setPosixFilePermissions(file, changer.change(perms));
} catch (IOException e) {
System.err.println(e);
}
}
// 访问文件树时调用的文件访问器
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("警告: " + exc);
return CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.err.println("警告: " + exc);
return CONTINUE;
}
}
// 主函数,解析参数并启动程序
public static void main(String[] args) throws IOException {
if (args.length < 2) {
System.err.println("用法:java Chmod [-R] symbolic-mode-list file...");
System.exit(-1);
}
int argi = 0;
int maxDepth = 0;
if (args[argi].equals("-R")) { // 如果指定了 -R 参数,递归修改权限
if (args.length < 3)
System.exit(-1);
argi++;
maxDepth = Integer.MAX_VALUE;
}
// 编译符号模式表达式
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++;
}
}
}
使用示例
假设我们希望更改某个文件或目录的权限,类似于 UNIX 中的 chmod 命令:
java
java Chmod u=rw file.txt
这会将 file.txt 的所有者权限设置为可读和可写。
如果要递归地更改整个目录树的权限,可以使用 -R 参数:
java
java Chmod -R u+rwx,go-rwx /path/to/directory
这将递归地为 /path/to/directory 中的所有文件和子目录设置所有者(user)具有读、写和执行权限,同时移除组(group)和其他用户(others)的所有权限。
注意事项
- 递归操作:
-R参数可以让权限更改应用到目录中的所有文件和子目录。 - 权限管理: 使用
+、-或=操作符,可以灵活地控制权限的添加、删除或重置。