added walk(1)
This commit is contained in:
parent
41d4cecf6d
commit
6da89d6266
122
sys/man/1/walk
Normal file
122
sys/man/1/walk
Normal file
|
@ -0,0 +1,122 @@
|
|||
.TH WALK 1
|
||||
.SH NAME
|
||||
walk \- walk a path
|
||||
.SH SYNOPSIS
|
||||
.B walk
|
||||
[
|
||||
.B -dftxu
|
||||
] [
|
||||
.B -n
|
||||
.I mind,maxd
|
||||
] [
|
||||
.B -e
|
||||
.I ststr
|
||||
] [
|
||||
.I name ...
|
||||
]
|
||||
.SH DESCRIPTION
|
||||
.I Walk
|
||||
recursively descends any directory arguments,
|
||||
printing the name of each file on a separate line.
|
||||
When no arguments are given, the current directory
|
||||
is assumed.
|
||||
Non-directory arguments are checked for existence,
|
||||
then printed, if so.
|
||||
.PP
|
||||
Options are:
|
||||
.TP
|
||||
.B -d
|
||||
Print only directories.
|
||||
.TP
|
||||
.B -f
|
||||
Print only non-directories.
|
||||
.TP
|
||||
.B -t
|
||||
Print a file only if it has the temporary flag set.
|
||||
.TP
|
||||
.B -x
|
||||
Print a file only if it has any executable bits set.
|
||||
.TP
|
||||
.B -u
|
||||
Unbuffered output.
|
||||
.TP
|
||||
.B -n min,max
|
||||
Set the inclusive range of depths for filtering in results.
|
||||
Both
|
||||
.I min
|
||||
and
|
||||
.I max
|
||||
are optional.
|
||||
.TP
|
||||
.B -e statfmt
|
||||
Setting the statfmt string allows specifying the data
|
||||
.B walk
|
||||
should print.
|
||||
It takes a string of characters, each corresponding
|
||||
to some piece of information about the file being
|
||||
traversed, and prints them separated by spaces.
|
||||
.PP
|
||||
The statfmt characters are as follows:
|
||||
.TF .
|
||||
.TP
|
||||
.B U
|
||||
owner name (uid)
|
||||
.TP
|
||||
.B G
|
||||
group name (gid)
|
||||
.TP
|
||||
.B M
|
||||
name of last user to modify (muid)
|
||||
.TP
|
||||
.B a
|
||||
last access time (atime)
|
||||
.TP
|
||||
.B m
|
||||
last modification time (mtime)
|
||||
.TP
|
||||
.B n
|
||||
final path element (name)
|
||||
.TP
|
||||
.B p
|
||||
path
|
||||
.TP
|
||||
.B q
|
||||
qid path.version.type (see
|
||||
.IR stat (2))
|
||||
.TP
|
||||
.B s
|
||||
size in bytes
|
||||
.TP
|
||||
.B x
|
||||
permissions
|
||||
.PD
|
||||
.PP
|
||||
The default statfmt is simply,
|
||||
.IR p .
|
||||
.SH EXAMPLES
|
||||
List files in a directory, sorted by modification time.
|
||||
.IP
|
||||
.EX
|
||||
walk -femp catpics | sort -n | sed 's/^[^ ]+ //'
|
||||
.EE
|
||||
.PP
|
||||
Print the size and path of files (excluding dirs)
|
||||
in the working directory.
|
||||
.IP
|
||||
.EX
|
||||
walk -fn1 -esp
|
||||
.EE
|
||||
.PD
|
||||
.SH SOURCE
|
||||
.B /sys/src/cmd/walk.c
|
||||
.SH SEE ALSO
|
||||
.IR ls (1),
|
||||
.IR du (1)
|
||||
.SH BUGS
|
||||
Statfmt character `x' displays permissions as an integer.
|
||||
.PP
|
||||
Manipulating ifs is a nuisance.
|
||||
.PP
|
||||
File names are assumed to not contain newlines.
|
||||
.PP
|
||||
Correct invocation requires too much thought.
|
323
sys/src/cmd/walk.c
Normal file
323
sys/src/cmd/walk.c
Normal file
|
@ -0,0 +1,323 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <bio.h>
|
||||
#include <String.h>
|
||||
|
||||
int Cflag = 0;
|
||||
int uflag = 0;
|
||||
String *stfmt;
|
||||
|
||||
/* should turn these flags into a mask */
|
||||
int dflag = 1;
|
||||
int fflag = 1;
|
||||
int tflag = 0;
|
||||
int xflag = 0;
|
||||
long maxdepth = ~(1<<31);
|
||||
long mindepth = 0;
|
||||
|
||||
char *dotpath = ".";
|
||||
Dir *dotdir = nil;
|
||||
|
||||
Biobuf *bout;
|
||||
|
||||
int seen(Dir*);
|
||||
|
||||
void
|
||||
warn(char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
char buf[1024]; /* arbitrary */
|
||||
int n;
|
||||
|
||||
if((n = snprint(buf, sizeof(buf), "%s: ", argv0)) < 0)
|
||||
sysfatal("snprint: %r");
|
||||
va_start(arg, fmt);
|
||||
vseprint(buf+n, buf+sizeof(buf), fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
Bflush(bout);
|
||||
fprint(2, "%s\n", buf);
|
||||
}
|
||||
|
||||
void
|
||||
dofile(char *path, Dir *f, int pathonly, long depth)
|
||||
{
|
||||
char *p;
|
||||
|
||||
USED(depth);
|
||||
|
||||
if(
|
||||
(f == dotdir)
|
||||
|| (tflag && ! (f->qid.type & QTTMP))
|
||||
|| (xflag && ! (f->mode & DMEXEC))
|
||||
)
|
||||
return;
|
||||
|
||||
for(p = s_to_c(stfmt); *p != '\0'; p++){
|
||||
switch(*p){
|
||||
case 'U': Bwrite(bout, f->uid, strlen(f->uid)); break;
|
||||
case 'G': Bwrite(bout, f->gid, strlen(f->gid)); break;
|
||||
case 'M': Bwrite(bout, f->muid, strlen(f->muid)); break;
|
||||
case 'a': Bprint(bout, "%uld", f->atime); break;
|
||||
case 'm': Bprint(bout, "%uld", f->mtime); break;
|
||||
case 'n': Bwrite(bout, f->name, strlen(f->name)); break;
|
||||
case 'p':
|
||||
if(path != dotpath)
|
||||
Bwrite(bout, path, strlen(path));
|
||||
if(! (f->qid.type & QTDIR) && !pathonly){
|
||||
if(path != dotpath)
|
||||
Bputc(bout, '/');
|
||||
Bwrite(bout, f->name, strlen(f->name));
|
||||
}
|
||||
break;
|
||||
case 'q': Bprint(bout, "%ullx.%uld.%.2uhhx", f->qid.path, f->qid.vers, f->qid.type); break;
|
||||
case 's': Bprint(bout, "%lld", f->length); break;
|
||||
case 'x': Bprint(bout, "%ulo", f->mode); break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
if(*(p+1) != '\0')
|
||||
Bputc(bout, ' ');
|
||||
}
|
||||
|
||||
Bputc(bout, '\n');
|
||||
|
||||
if(uflag)
|
||||
Bflush(bout);
|
||||
}
|
||||
|
||||
void
|
||||
walk(char *path, Dir *cf, long depth)
|
||||
{
|
||||
String *file;
|
||||
Dir *dirs, *f, *fe;
|
||||
int fd;
|
||||
long n;
|
||||
|
||||
if(cf == nil){
|
||||
warn("path: %s: %r", path);
|
||||
return;
|
||||
}
|
||||
|
||||
if(depth >= maxdepth)
|
||||
goto nodescend;
|
||||
|
||||
if((fd = open(path, OREAD)) < 0){
|
||||
warn("couldn't open %s: %r", path);
|
||||
return;
|
||||
}
|
||||
|
||||
while((n = dirread(fd, &dirs)) > 0){
|
||||
fe = dirs+n;
|
||||
for(f = dirs; f < fe; f++){
|
||||
if(seen(f))
|
||||
continue;
|
||||
if(! (f->qid.type & QTDIR)){
|
||||
if(fflag && depth >= mindepth)
|
||||
dofile(path, f, 0, depth);
|
||||
} else if(strcmp(f->name, ".") == 0 || strcmp(f->name, "..") == 0){
|
||||
warn(". or .. named file: %s/%s", path, f->name);
|
||||
} else{
|
||||
if(depth+1 > maxdepth){
|
||||
dofile(path, f, 0, depth);
|
||||
continue;
|
||||
} else if(path == dotpath){
|
||||
if((file = s_new()) == nil)
|
||||
sysfatal("s_new: %r");
|
||||
} else{
|
||||
if((file = s_copy(path)) == nil)
|
||||
sysfatal("s_copy: %r");
|
||||
if(s_len(file) != 1 || *s_to_c(file) != '/')
|
||||
s_putc(file, '/');
|
||||
}
|
||||
s_append(file, f->name);
|
||||
|
||||
walk(s_to_c(file), f, depth+1);
|
||||
s_free(file);
|
||||
}
|
||||
}
|
||||
free(dirs);
|
||||
}
|
||||
close(fd);
|
||||
if(n < 0)
|
||||
warn("%s: %r", path);
|
||||
|
||||
nodescend:
|
||||
depth--;
|
||||
if(dflag && depth >= mindepth)
|
||||
dofile(path, cf, 0, depth);
|
||||
}
|
||||
|
||||
char*
|
||||
slashslash(char *s)
|
||||
{
|
||||
char *p, *q;
|
||||
|
||||
for(p=q=s; *q; q++){
|
||||
if(q>s && *q=='/' && *(q-1)=='/')
|
||||
continue;
|
||||
if(p != q)
|
||||
*p = *q;
|
||||
p++;
|
||||
}
|
||||
do{
|
||||
*p-- = '\0';
|
||||
} while(p>s && *p=='/');
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
long
|
||||
estrtol(char *as, char **aas, int base)
|
||||
{
|
||||
long n;
|
||||
char *p;
|
||||
|
||||
n = strtol(as, &p, base);
|
||||
if(p == as || *p != '\0')
|
||||
sysfatal("estrtol: bad input '%s'", as);
|
||||
else if(aas != nil)
|
||||
*aas = p;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
elimdepth(char *p){
|
||||
char *q;
|
||||
|
||||
if(strlen(p) == 0)
|
||||
sysfatal("empty depth argument");
|
||||
|
||||
if(q = strchr(p, ',')){
|
||||
*q = '\0';
|
||||
if(p != q)
|
||||
mindepth = estrtol(p, nil, 0);
|
||||
p = q+1;
|
||||
if(*p == '\0')
|
||||
return;
|
||||
}
|
||||
|
||||
maxdepth = estrtol(p, nil, 0);
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-udftx] [-n mind,maxd] [-e statfmt] [file ...]\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
/*
|
||||
Last I checked (commit 3dd6a31881535615389c24ab9a139af2798c462c),
|
||||
libString calls sysfatal when things go wrong; in my local
|
||||
copy of libString, failed calls return nil and errstr is set.
|
||||
|
||||
There are various nil checks in this code when calling libString
|
||||
functions, but since they are a no-op and libString needs
|
||||
a rework, I left them in - BurnZeZ
|
||||
*/
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
long i;
|
||||
Dir *d;
|
||||
|
||||
stfmt = nil;
|
||||
ARGBEGIN{
|
||||
case 'C': Cflag++; break; /* undocumented; do not cleanname() the args */
|
||||
case 'u': uflag++; break; /* unbuffered output */
|
||||
|
||||
case 'd': dflag++; fflag = 0; break; /* only dirs */
|
||||
case 'f': fflag++; dflag = 0; break; /* only non-dirs */
|
||||
case 't': tflag++; break; /* only tmp files */
|
||||
case 'x': xflag++; break; /* only executable permission */
|
||||
|
||||
case 'n': elimdepth(EARGF(usage())); break;
|
||||
case 'e':
|
||||
if((stfmt = s_reset(stfmt)) == nil)
|
||||
sysfatal("s_reset: %r");
|
||||
s_append(stfmt, EARGF(usage()));
|
||||
i = strspn(s_to_c(stfmt), "UGMamnpqsx");
|
||||
if(i != s_len(stfmt))
|
||||
sysfatal("bad stfmt: %s\n", s_to_c(stfmt));
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}ARGEND;
|
||||
|
||||
if((bout = Bfdopen(1, OWRITE)) == nil)
|
||||
sysfatal("Bfdopen: %r");
|
||||
Blethal(bout, nil);
|
||||
if(stfmt == nil){
|
||||
if((stfmt = s_new()) == nil)
|
||||
sysfatal("s_new: %r");
|
||||
s_putc(stfmt, 'p');
|
||||
s_terminate(stfmt);
|
||||
}
|
||||
if(maxdepth != ~(1<<31))
|
||||
maxdepth++;
|
||||
if(argc == 0){
|
||||
dotdir = dirstat(".");
|
||||
walk(dotpath, dotdir, 1);
|
||||
} else for(i=0; i<argc; i++){
|
||||
if(strncmp(argv[i], "#/", 2) == 0)
|
||||
slashslash(argv[i]+2);
|
||||
else{
|
||||
if(!Cflag)
|
||||
cleanname(argv[i]);
|
||||
slashslash(argv[i]);
|
||||
}
|
||||
if((d = dirstat(argv[i])) != nil && ! (d->qid.type & QTDIR)){
|
||||
if(fflag && !seen(d) && mindepth < 1)
|
||||
dofile(argv[i], d, 1, 0);
|
||||
} else
|
||||
walk(argv[i], d, 1);
|
||||
free(d);
|
||||
}
|
||||
Bterm(bout);
|
||||
|
||||
exits(nil);
|
||||
}
|
||||
|
||||
/* below pilfered from /sys/src/cmd/du.c
|
||||
* NOTE: I did not check for bugs */
|
||||
|
||||
#define NCACHE 256 /* must be power of two */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Dir* cache;
|
||||
int n;
|
||||
int max;
|
||||
} Cache;
|
||||
Cache cache[NCACHE];
|
||||
|
||||
int
|
||||
seen(Dir *dir)
|
||||
{
|
||||
Dir *dp;
|
||||
int i;
|
||||
Cache *c;
|
||||
|
||||
c = &cache[dir->qid.path&(NCACHE-1)];
|
||||
dp = c->cache;
|
||||
for(i=0; i<c->n; i++, dp++)
|
||||
if(dir->qid.path == dp->qid.path &&
|
||||
dir->type == dp->type &&
|
||||
dir->dev == dp->dev)
|
||||
return 1;
|
||||
if(c->n == c->max){
|
||||
if (c->max == 0)
|
||||
c->max = 8;
|
||||
else
|
||||
c->max += c->max/2;
|
||||
c->cache = realloc(c->cache, c->max*sizeof(Dir));
|
||||
if(c->cache == nil)
|
||||
sysfatal("realloc: %r");
|
||||
}
|
||||
c->cache[c->n++] = *dir;
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue