aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2019-05-24 16:01:47 -0700
committerRob Landley <rob@landley.net>2019-05-25 21:28:57 -0500
commit1dbd86ec825ba92be12e4aafad49804faaa86ec6 (patch)
tree49d8e3be0b1a50fb42d7f571a97ce16d268df986
parent2f9d9096aac148a26254801014feaff057b64fa0 (diff)
downloadtoybox-1dbd86ec825ba92be12e4aafad49804faaa86ec6.tar.gz
find: add -printf support.
This only implements the format specifiers that I've seen used in the wild (which is actually a significant fraction of the total supported by findutils' find). The most obvious gap is in the time support. I'm happy to add more, but didn't want to add stuff "just because". I'd say %A@, %C@, and -- for SELinux users -- %Z are probably the most plausibly useful formats still missing. I don't think the human-readable date formatting is particularly useful unless someone's seen it actually used in the wild. The %T+ "full ISO" format being the most likely exception to that. Anyway, this is enough for me get started building AOSP with toybox find.
-rw-r--r--lib/lib.c2
-rw-r--r--toys/posix/find.c83
2 files changed, 80 insertions, 5 deletions
diff --git a/lib/lib.c b/lib/lib.c
index fe15c990..b5825fd2 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -1046,7 +1046,7 @@ char *getdirname(char *name)
{
char *s = xstrdup(name), *ss = strrchr(s, '/');
- while (*ss && *ss == '/' && s != ss) *ss-- = 0;
+ while (ss && *ss && *ss == '/' && s != ss) *ss-- = 0;
return s;
}
diff --git a/toys/posix/find.c b/toys/posix/find.c
index 5cefbf15..8b757b8e 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -46,10 +46,23 @@ config FIND
-print Print match with newline -print0 Print match with null
-exec Run command with path -execdir Run command in file's dir
-ok Ask before exec -okdir Ask before execdir
- -delete Remove matching file/dir
+ -delete Remove matching file/dir
+ -printf FORMAT Print using format string
Commands substitute "{}" with matched file. End with ";" to run each file,
or "+" (next argument after "{}") to collect and run with multiple files.
+
+ FORMAT characters are \ escapes and:
+ %f basename
+ %g textual gid %G numeric gid
+ %i decimal inode
+ %l target of symlink (or empty)
+ %m octal mode, no leading 0 %M type and mode in ls format
+ %p filename %P filename without root
+ %s size in bytes
+ %T@ last modification unix time with fraction
+ %u textual uid %U numeric uid
+ %Z security context
*/
#define FOR_find
@@ -61,6 +74,7 @@ GLOBALS(
int topdir, xdev, depth;
time_t now;
long max_bytes;
+ char *start;
)
struct execdir_data {
@@ -535,6 +549,65 @@ static int do_find(struct dirtree *new)
// Argument consumed, skip the check.
goto cont;
+ } else if (!strcmp(s, "printf")) {
+ char *fmt = ss[1], *path, *lnk, *start, mode_str[11], ch;
+
+ print++;
+ if (check) {
+ for (; *fmt; fmt++) {
+ if (*fmt == '\\') {
+ if (!(ch = unescape(fmt[1])))
+ error_exit("bad \\ escape: %c", fmt[1]);
+ fmt++;
+ putchar(ch);
+ } else if (*fmt == '%') {
+ switch (*++fmt) {
+ case '%': putchar('%'); break;
+ case 'f': printf("%s", new->name); break;
+ case 'G': printf("%d", new->st.st_gid); break;
+ case 'g': printf("%s", getgroupname(new->st.st_gid)); break;
+ case 'i': printf("%lld", (long long) new->st.st_ino); break;
+ case 'l':
+ path = dirtree_path(new, 0);
+ lnk = xreadlink(path);
+ printf("%s", lnk ? lnk : "");
+ free(lnk);
+ free(path);
+ break;
+ case 'M':
+ mode_to_string(new->st.st_mode, mode_str);
+ printf("%s", mode_str);
+ break;
+ case 'm': printf("%o", new->st.st_mode & ~S_IFMT); break;
+ case 'P':
+ start = getdirname(TT.start);
+ path = dirtree_path(new, 0);
+ printf("%s", path + 1+strlen(start));
+ free(path);
+ free(start);
+ break;
+ case 'p':
+ path = dirtree_path(new, 0);
+ printf("%s", path);
+ free(path);
+ break;
+ case 's': printf("%lld", (long long) new->st.st_size); break;
+ case 'T':
+ switch (*++fmt) {
+ case '@':
+ printf("%ld.%ld", new->st.st_mtim.tv_sec,
+ new->st.st_mtim.tv_nsec);
+ break;
+ default: error_exit("bad %%T variant: %%T%c", *fmt);
+ }
+ break;
+ case 'U': printf("%d", new->st.st_uid); break;
+ case 'u': printf("%s", getusername(new->st.st_uid)); break;
+ default: error_exit("bad %% specifier: %c", *fmt);
+ }
+ } else putchar(*fmt);
+ }
+ }
} else goto error;
// This test can go at the end because we do a syntax checking
@@ -586,9 +659,11 @@ void find_main(void)
do_find(0);
// Loop through paths
- for (i = 0; i < len; i++)
- dirtree_flagread(ss[i], DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)),
- do_find);
+ for (i = 0; i < len; i++) {
+ TT.start = ss[i];
+ dirtree_flagread(TT.start,
+ DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)), do_find);
+ }
execdir(0, 1);