1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
From 59d7a2237500c9439b758910e951bcd5dc48c657 Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Thu, 4 Apr 2013 08:43:05 -0700
Subject: [PATCH] Add --matchdirs and --caseinsensitive to matching
The new --matchdirs option causes pattern matching to include
the full contents of any directories that match the pattern,
including sub-directories.
The --caseinsensitive option simply makes pattern matching case
insensitive.
---
CHANGES | 5 ++++
README | 4 ++++
doc/tree.1 | 17 +++++++++++++-
tree.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------
4 files changed, 91 insertions(+), 14 deletions(-)
diff --git a/CHANGES b/CHANGES
index 10f2e20..004b86a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+Version 1.6.1
+ - Added --matchdirs flag so that patterns match against directory names and
+ then subsequently descend into sub-directories.
+ - Added --caseinsensitive flag so patterns match without regards to case.
+
Version 1.6.0
- Re-org of code into multiple files, split HTML and Unix listdir() into
separate functions, various code cleanups and optimizations.
diff --git a/README b/README
index ab58d82..58767e8 100644
--- a/README
+++ b/README
@@ -141,6 +141,10 @@ Ujjwal Kumar
- Suggested that tree backslash spaces like ls does for script use. Made
output more like ls.
+Jason A. Donenfeld
+ - Added --matchdirs flag for matching patterns against directories.
+ - Added --caseinsensitive flag for case insensitive pattern matching.
+
And many others whom I've failed to keep track of. I should have started
this list years ago.
diff --git a/doc/tree.1 b/doc/tree.1
index 4b80852..41d0815 100644
--- a/doc/tree.1
+++ b/doc/tree.1
@@ -21,7 +21,7 @@
.SH NAME
tree \- list contents of directories in a tree-like format.
.SH SYNOPSIS
-\fBtree\fP [\fB-acdfghilnpqrstuvxACDFQNSUX\fP] [\fB-L\fP \fIlevel\fP [\fB-R\fP]] [\fB-H\fP \fIbaseHREF\fP] [\fB-T\fP \fItitle\fP] [\fB-o\fP \fIfilename\fP] [\fB--nolinks\fP] [\fB-P\fP \fIpattern\fP] [\fB-I\fP \fIpattern\fP] [\fB--inodes\fP] [\fB--device\fP] [\fB--noreport\fP] [\fB--dirsfirst\fP] [\fB--version\fP] [\fB--help\fP] [\fB--filelimit\fP \fI#\fP] [\fB--si\fP] [\fB--prune\fP] [\fB--du\fP] [\fB--timefmt\fP \fIformat\fP] [\fIdirectory\fP ...]
+\fBtree\fP [\fB-acdfghilnpqrstuvxACDFQNSUX\fP] [\fB-L\fP \fIlevel\fP [\fB-R\fP]] [\fB-H\fP \fIbaseHREF\fP] [\fB-T\fP \fItitle\fP] [\fB-o\fP \fIfilename\fP] [\fB--nolinks\fP] [\fB-P\fP \fIpattern\fP] [\fB-I\fP \fIpattern\fP] [\fB--inodes\fP] [\fB--device\fP] [\fB--noreport\fP] [\fB--dirsfirst\fP] [\fB--version\fP] [\fB--help\fP] [\fB--filelimit\fP \fI#\fP] [\fB--si\fP] [\fB--prune\fP] [\fB--du\fP] [\fB--timefmt\fP \fIformat\fP] [\fB--caseinsensitive\fP] [\fB--matchdirs\fP] [\fIdirectory\fP ...]
.br
.SH DESCRIPTION
\fITree\fP is a recursive directory listing program that produces a depth
@@ -123,6 +123,19 @@ Prints (implies -D) and formats the date according to the format string
which uses the \fBstrftime\fP(3) syntax.
.PP
.TP
+.B --caseinsensitive
+If a match pattern is specified by the -P option, this will cause the pattern
+to match without regards to the case of each letter.
+.PP
+.TP
+.B --matchdirs
+If a match pattern is specified by the -P option, this will cause the pattern
+to be applied to directory names (in addition to filenames). In the event of a
+match on the directory name, matching is disabled for the directory's
+contents. If the --prune option is used, empty folders that match the pattern
+will not be pruned.
+.PP
+.TP
.B -o \fIfilename\fP
Send output to \fIfilename\fP.
.PP
@@ -314,6 +327,8 @@ Steve Baker (ice@mama.indstate.edu)
HTML output hacked by Francesc Rocher (rocher@econ.udg.es)
.br
Charsets and OS/2 support by Kyosuke Tokoro (NBG01720@nifty.ne.jp)
+.br
+Directory and case matching by Jason A. Donenfeld (Jason@zx2c4.com)
.SH BUGS AND NOTES
Tree does not prune "empty" directories when the -P and -I options are used by
diff --git a/tree.c b/tree.c
index 19cf368..388aae5 100644
--- a/tree.c
+++ b/tree.c
@@ -19,16 +19,17 @@
#include "tree.h"
-static char *version ="$Version: $ tree v1.6.0 (c) 1996 - 2011 by Steve Baker, Thomas Moore, Francesc Rocher, Kyosuke Tokoro $";
-static char *hversion="\t\t tree v1.6.0 %s 1996 - 2011 by Steve Baker and Thomas Moore <br>\n"
+static char *version ="$Version: $ tree v1.6.0 (c) 1996 - 2014 by Steve Baker, Thomas Moore, Francesc Rocher, Kyosuke Tokoro, Jason A. Donenfeld $";
+static char *hversion="\t\t tree v1.6.0 %s 1996 - 2014 by Steve Baker and Thomas Moore <br>\n"
"\t\t HTML output hacked and copyleft %s 1998 by Francesc Rocher <br>\n"
- "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro\n";
+ "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro <br>\n"
+ "\t\t Directory and case matching %s 2014 by Jason A. Donenfeld\n";
/* Globals */
bool dflag, lflag, pflag, sflag, Fflag, aflag, fflag, uflag, gflag;
bool qflag, Nflag, Qflag, Dflag, inodeflag, devflag, hflag, Rflag;
bool Hflag, siflag, cflag, Xflag, duflag, pruneflag;
-bool noindent, force_color, nocolor, xdev, noreport, nolinks, flimit, dirsfirst, nosort;
+bool noindent, force_color, nocolor, xdev, noreport, nolinks, flimit, dirsfirst, nosort, matchdirs, caseinsensitive;
char *pattern = NULL, *ipattern = NULL, *host = NULL, *title = "Directory Tree", *sp = " ";
char *timefmt = NULL;
const char *charset = NULL;
@@ -75,12 +76,13 @@ int main(int argc, char **argv)
char sizebuf[64];
off_t size = 0;
mode_t mt;
+ bool needfulltree;
q = p = dtotal = ftotal = 0;
aflag = dflag = fflag = lflag = pflag = sflag = Fflag = uflag = gflag = FALSE;
Dflag = qflag = Nflag = Qflag = Rflag = hflag = Hflag = siflag = cflag = FALSE;
noindent = force_color = nocolor = xdev = noreport = nolinks = FALSE;
- dirsfirst = nosort = inodeflag = devflag = Xflag = FALSE;
+ caseinsensitive = matchdirs = dirsfirst = nosort = inodeflag = devflag = Xflag = FALSE;
duflag = pruneflag = FALSE;
flimit = 0;
dirs = xmalloc(sizeof(int) * (maxdirs=4096));
@@ -350,6 +352,16 @@ int main(int argc, char **argv)
Dflag = TRUE;
break;
}
+ if (!strncmp("--matchdirs",argv[i],11)) {
+ j = strlen(argv[i])-1;
+ matchdirs = TRUE;
+ break;
+ }
+ if (!strncmp("--caseinsensitive",argv[i],17)) {
+ j = strlen(argv[i])-1;
+ caseinsensitive = TRUE;
+ break;
+ }
}
default:
fprintf(stderr,"tree: Invalid argument -`%c'.\n",argv[i][j]);
@@ -387,16 +399,17 @@ int main(int argc, char **argv)
parse_dir_colors();
initlinedraw(0);
+ needfulltree = duflag || pruneflag || matchdirs;
/* Set our listdir function and sanity check options. */
if (Hflag) {
- listdir = (duflag || pruneflag)? html_rlistdir : html_listdir;
+ listdir = needfulltree ? html_rlistdir : html_listdir;
Xflag = FALSE;
} else if (Xflag) {
- listdir = (duflag || pruneflag)? xml_rlistdir : xml_listdir;
+ listdir = needfulltree ? xml_rlistdir : xml_listdir;
colorize = FALSE;
colored = FALSE; /* Do people want colored XML output? */
} else {
- listdir = (duflag || pruneflag)? unix_rlistdir : unix_listdir;
+ listdir = needfulltree ? unix_rlistdir : unix_listdir;
}
if (dflag) pruneflag = FALSE; /* You'll just get nothing otherwise. */
@@ -517,7 +530,8 @@ void usage(int n)
"usage: tree [-acdfghilnpqrstuvxACDFQNSUX] [-H baseHREF] [-T title ] [-L level [-R]]\n"
"\t[-P pattern] [-I pattern] [-o filename] [--version] [--help] [--inodes]\n"
"\t[--device] [--noreport] [--nolinks] [--dirsfirst] [--charset charset]\n"
- "\t[--filelimit[=]#] [--si] [--timefmt[=]<f>] [<directory list>]\n");
+ "\t[--filelimit[=]#] [--si] [--timefmt[=]<f>] [--matchdirs]\n"
+ "\t[--caseinsensitive] [<directory list>]\n");
if (n < 2) exit(0);
fprintf(stderr,
" ------- Listing options -------\n"
@@ -534,6 +548,8 @@ void usage(int n)
" --charset X Use charset X for terminal/HTML and indentation line output.\n"
" --filelimit # Do not descend dirs with more than # files in them.\n"
" --timefmt <f> Print and format time according to the format <f>.\n"
+ " --matchdirs Include directory names in -P pattern matching.\n"
+ " --caseinsensitive Match files without regards to case in -P pattern matching.\n"
" -o filename Output to file instead of stdout.\n"
" -------- File options ---------\n"
" -q Print non-printable characters as '?'.\n"
@@ -689,6 +705,8 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e
struct _info **dir, **sav, **p, *sp;
struct stat sb;
int n;
+ u_long lev_tmp;
+ char *tmp_pattern = NULL, *start_rel_path;
*err = NULL;
if (Level >= 0 && lev > Level) return NULL;
@@ -696,7 +714,29 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e
stat(d,&sb);
dev = sb.st_dev;
}
+
+ // if the directory name matches, turn off pattern matching for contents
+ if (matchdirs && pattern) {
+ lev_tmp = lev;
+ for (start_rel_path = d + strlen(d); start_rel_path != d; --start_rel_path) {
+ if (*start_rel_path == '/')
+ --lev_tmp;
+ if (lev_tmp <= 0) {
+ if (*start_rel_path)
+ ++start_rel_path;
+ break;
+ }
+ }
+ if (patmatch(start_rel_path,pattern) == 1) {
+ tmp_pattern = pattern;
+ pattern = NULL;
+ }
+ }
sav = dir = read_dir(d,&n);
+ if (tmp_pattern) {
+ pattern = tmp_pattern;
+ tmp_pattern = NULL;
+ }
if (dir == NULL) {
*err = scopy("error opening dir");
return NULL;
@@ -745,7 +785,9 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e
saveino((*dir)->inode, (*dir)->dev);
(*dir)->child = getfulltree(path,lev+1,dev,&((*dir)->size),&((*dir)->err));
}
- if (pruneflag && (*dir)->child == NULL) {
+ // prune empty folders, unless they match the requested pattern
+ if (pruneflag && (*dir)->child == NULL &&
+ !(matchdirs && pattern && patmatch((*dir)->name,pattern) == 1)) {
sp = *dir;
for(p=dir;*p;p++) *p = *(p+1);
n--;
@@ -869,8 +911,19 @@ char *gnu_getcwd()
}
/*
+ * Returns a lower case version of the argument if case insensitive
+ * mode is enabled. Note that this is static and inline so that we
+ * do not incur penalty when this is running in a tight loop.
+ */
+static inline char cond_lower(char c)
+{
+ return caseinsensitive ? tolower(c) : c;
+}
+
+/*
* Patmatch() code courtesy of Thomas Moore (dark@mama.indstate.edu)
* '|' support added by David MacMahon (davidm@astron.Berkeley.EDU)
+ * Case insensitive support added by Jason A. Donenfeld (Jason@zx2c4.com)
* returns:
* 1 on a match
* 0 on a mismatch
@@ -918,11 +971,11 @@ int patmatch(char *buf, char *pat)
pat += 2;
if(*pat == '\\' && *pat)
pat++;
- if(*buf >= m && *buf <= *pat)
+ if(cond_lower(*buf) >= cond_lower(m) && cond_lower(*buf) <= cond_lower(*pat))
match = n;
if(!*pat)
pat--;
- } else if(*buf == *pat) match = n;
+ } else if(cond_lower(*buf) == cond_lower(*pat)) match = n;
pat++;
}
buf++;
@@ -940,7 +993,7 @@ int patmatch(char *buf, char *pat)
if(*pat)
pat++;
default:
- match = (*buf++ == *pat);
+ match = (cond_lower(*buf++) == cond_lower(*pat));
break;
}
pat++;
--
1.9.2
|