find命令详解

来源:互联网 发布:2016免费顶级域名注册 编辑:程序博客网 时间:2024/05/18 00:21

一 简介

find命令用法格式:

find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

其中 [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec]对于有些老的版本并不支持,比如busybox就不支持这些选项,这些选项必须放在path之前。

[-H] [-L] [-P]主要控制碰到符号链接时采取的行为:

-P:也就是默认行为, 从来不follow symbolic links.

-L: 总是follow symbolic links.

-H: 仅仅当作为命令行参数时,也就是这里的[path...],才会follow symbolic links.

[-Olevel]:指定优化等级,在不影响测试结果的前提下,通过reorder测试test,加快测试速度, 0优化等级最低,这是默认行为,3优化等级最高,耗时最少的放在最前面,耗时最多的放在最后面。

[-D help|tree|search|stat|rates|opt|exec]:打印诊断信息,当查找的结果跟预想的不一致时,可以使用该调试选项。

[path...]至少有一个path在[expression]之前,如果没有指定,则默认为当前路径

[expression]由operators, options, tests, and actions构成,其中:

operators选项有:

( EXPR ):   括号优先操作

! EXPR   -not EXPR: 非操作

EXPR1 -a EXPR2   EXPR1 -and EXPR2: 与操作

EXPR1 -o EXPR2   EXPR1 -or EXPR2 :或操作

EXPR1 ,  EXPR2:逗号表达式,EXPR1和EXPR2都会计算,但是EXPR1的值会被丢弃,整个逗号表达式的值等于EXPR2的值,逗号表达式对于搜索不同类型的文件特别有用,因为只需遍历整个目录一次,例如:

find / \( −perm −4000 −fprintf /root/suid.txt '%#m %u %p\n' \) , \ 
\( −size +100M −fprintf /root/big.txt '%−10s %p\n' \)

如果缺失,默认为与操作,即 EXPR1  EXPR2等价于 EXPR1 -a EXPR2


options选项有:

-d     A synonym for -depth, for compatibility with FreeBSD, NetBSD, MacOS X and OpenBSD.

-daystart
              Measure times (for -amin, -atime, -cmin, -ctime, -mmin, and -mtime) from the beginning of  today  rather  than
              from 24 hours ago.  This option only affects tests which appear later on the command line.

-depth Process each directory's contents before the directory itself.  The -delete action also implies -depth.

-follow
              Deprecated;  use  the  -L  option  instead.

 --help -maxdepth LEVELS -mindepth LEVELS -mount -noleaf
      --version -xdev -ignore_readdir_race -noignore_readdir_race

必须放在test和action之前,它们是全局性的,会影响到后面test的结果。


test选项有:

-amin N -anewer FILE -atime N -cmin N
      -cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group NAME
      -ilname PATTERN -iname PATTERN -inum N -iwholename PATTERN -iregex PATTERN
      -links N -lname PATTERN -mmin N -mtime N -name PATTERN -newer FILE
      -nouser -nogroup -path PATTERN -perm [+-]MODE -regex PATTERN
      -readable -writable -executable
      -wholename PATTERN -size N[bcwkMG] -true -type [bcdpflsD] -uid N
      -used N -user NAME -xtype [bcdpfls]

N can be +N or -N or N,返回结果true or false,如果单个test测试的结果为false,则同组中的后面test不会再进行测试,整组的测试结果为false


action选项有:

-delete -print0 -printf FORMAT -fprintf FILE FORMAT -print 
      -fprint0 FILE -fprint FILE -ls -fls FILE -prune -quit
      -exec COMMAND ; -exec COMMAND {} + -ok COMMAND ;
      -execdir COMMAND ; -execdir COMMAND {} + -okdir COMMAND

具有副作用,返回结果true or false,如果expression中除了prune外没有其他的action,则默认采用print.

find命令man手册:https://man.cx/find


二 源码实现

这里以busybox源码进行分析,其他版本实现可能存在差异。

  • 选项解析

参数解析代码主要为static action*** parse_params(char **argv)这个函数,选项并不一定以“-”开头,比如括号操作,非操作,选项之间以空白字符进行分割:


  • 与或非操作

部分代码如下所示:

/* Operators */
else if (parm == PARM_a IF_DESKTOP(|| parm == PARM_and)) {
dbg("%d", __LINE__);
/* no further special handling required */
}
else if (parm == PARM_o IF_DESKTOP(|| parm == PARM_or)) {
dbg("%d", __LINE__);
/* start new OR group */
cur_group++;
appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
/*appp[cur_group] = NULL; - already NULL */
appp[cur_group+1] = NULL;
cur_action = 0;
}
#if ENABLE_FEATURE_FIND_NOT
else if (parm == PARM_char_not IF_DESKTOP(|| parm == PARM_not)) {
/* also handles "find ! ! -name 'foo*'" */
invert_flag ^= 1;
dbg("invert_flag:%d", invert_flag);
}
#endif


红色部分为一个group,蓝色为另一个group,group之间为或关系,group内部为与关系,invert为非操作标志。整个测试过程为:遍历path指定的目录,对每个文件进行test测试,当一个group测试所有的test全部为true时,整个表达式为true,不必进行下一group的测试,当group中碰到test为false时,group测试结果为false,测试下一group,如果所有group测试均为false,则整个表达式结果为false,这部分代码为:

static int exec_actions(action ***appp, const char *fileName, const struct stat *statbuf)
{
int cur_group;
int cur_action;
int rc = 0;
action **app, *ap;

/* "action group" is a set of actions ANDed together.
* groups are ORed together.
* We simply evaluate each group until we find one in which all actions
* succeed. */


cur_group = -1;
while ((app = appp[++cur_group]) != NULL) {
rc &= ~TRUE; /* 'success' so far, clear TRUE bit */
cur_action = -1;
while (1) {
ap = app[++cur_action];
if (!ap) /* all actions in group were successful */
return rc ^ TRUE; /* restore TRUE bit */
rc |= TRUE ^ ap->f(fileName, statbuf, ap);
#if ENABLE_FEATURE_FIND_NOT
if (ap->invert) rc ^= TRUE;
#endif
dbg("grp %d action %d rc:0x%x", cur_group, cur_action, rc);
if (rc & TRUE) /* current group failed, try next */
break;
}
}
dbg("returning:0x%x", rc ^ TRUE);
return rc ^ TRUE; /* restore TRUE bit */
}

  • 括号操作

括号操作,代码实现部分如下:

else if (parm == PARM_char_brace) {
action_paren *ap;
char **endarg;
unsigned nested = 1;


dbg("%d", __LINE__);
endarg = argv;
while (1) {
if (!*++endarg)
bb_error_msg_and_die("unpaired '('");
if (LONE_CHAR(*endarg, '('))
nested++;
else if (LONE_CHAR(*endarg, ')') && !--nested) {
*endarg = NULL;
break;
}
}
ap = ALLOC_ACTION(paren);
ap->subexpr = parse_params(argv + 1);
*endarg = (char*) ")"; /* restore NULLed parameter */
argv = endarg;
}

通过代码可以看出,括号操作主要是通过嵌套递归操作实现的,括号操作数据结构如下,其中黄色部分为括号里的内容:


  • -depth选项
指定遍历顺序,加上-depth表明先进行目录下文件的测试,然后进行目录的测试,如果未指定默认为先进行目录测试。-delete选项同时暗示着-depth选项打开。
  • test和action区别

test和action之间有啥区别?

它们在表达式中出现的顺序有要求么,或者说test和action可以混合出现么?

每个group都需要action么,默认action print是指每个group还是整个表达式?

action的副作用是什么意思?

这是本人在阅读源代码之前心中的一些疑问,下面结合代码一一进行解答,首先贴出部分代码:

/* Actions */
else if (parm == PARM_print) {
dbg("%d", __LINE__);
G.need_print = 0;
(void) ALLOC_ACTION(print);
}

#if ENABLE_FEATURE_FIND_PRUNE
else if (parm == PARM_prune) {
dbg("%d", __LINE__);
(void) ALLOC_ACTION(prune);
}
#endif

else if (parm == PARM_name || parm == PARM_iname) {
action_name *ap;
dbg("%d", __LINE__);
ap = ALLOC_ACTION(name);
ap->pattern = arg1;
ap->iname = (parm == PARM_iname);
}

从上述代码可以看出,test和action没有本质的差别,都是一个action,只是action会设置G.need_print = 0而已,同时也可以回答第二个问题:test和action在表达式中的位置可以混合出现

关于第三个问题,通过上述代码片段其实也可以得到答案:既然test和action没有差别,那么每个group就不必非要有action。另外G.need_print 是全局的,只有一个,所有group共用,所以指的是整个表达式中,除了prune外没有指定其他的action,则默认为print:

fileAction()

{

............

r = exec_actions(G.actions, fileName, statbuf);
/* Had no explicit -print[0] or -exec? then print */
if ((r & TRUE) && G.need_print)
puts(fileName);

............

}

正是因为如此,find . −name afile −o −name bfile −print这条命令从来都不会打印afile文件名,即使在文件在当前目录下存在。

至于action的副作用,所以请看下面这条命令:

find ./  -name "*.o" -delete  -type f

假如delete前面的test都为true,那么执行delete删除文件,则delete之后的test会因为文件被删除而报错。

三 注意事项

  • 防止shell扩展
如果find的参数是从shell传出的,则对于shell特殊的字符需要用引号括起来或者前面加上"\"转义字符,防止shell进行拓展,例如:
find  ./ -name *svn      不正确,shell会拓展为find  ./  -name  svn  lsvn  mesvn
find ./ -name  "*svn"    正确
find ./  -name  \*svn     正确

  • 特殊文件名的处理
linux文件名可以包含除了'\0', '/' 的任何字符,如果文件名中含有空白字符(空格,制表符,换行符),反斜杠,引号等,有可能就会得到意想不到的结果,常用的方法是采用-print0选项:
ACTF(print0)
{
printf("%s%c", fileName, '\0');
return TRUE;
}
在打印文件结尾加上'\0', 然后和xargs -0配合使用,xargs -0指定‘\0’作为分割符,所以即使文件中中含有特殊字符,通过‘\0’分割也能得到正确文件名,因为文件名不能包含'\0'
find ./  -name "*.c" -print0 | xargs -0  ls -al
原创粉丝点击