From a58d45a7d0afd1667dd1e5c8e318944a02aeab28 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 29 May 2019 16:02:16 -0500 Subject: Teach find -printf about %.Ns patterns, tweak help text, add tests. --- tests/find.test | 28 +++++------ toys/posix/find.c | 137 ++++++++++++++++++++++++++---------------------------- 2 files changed, 79 insertions(+), 86 deletions(-) diff --git a/tests/find.test b/tests/find.test index 6b0b0a8a..26ad1626 100755 --- a/tests/find.test +++ b/tests/find.test @@ -81,15 +81,11 @@ testing "-exec {} +" \ "find dir -type f -exec ls {} +" "dir/file\n" "" "" # `find . -iname` was segfaulting -testing "-name file" \ - "find dir -name file" "dir/file\n" "" "" -testing "-name FILE" \ - "find dir -name FILE" "" "" "" +testing "-name file" "find dir -name file" "dir/file\n" "" "" +testing "-name FILE" "find dir -name FILE" "" "" "" -testing "-iname file" \ - "find dir -iname FILE" "dir/file\n" "" "" -testing "-iname FILE" \ - "find dir -iname FILE" "dir/file\n" "" "" +testing "-iname file" "find dir -iname FILE" "dir/file\n" "" "" +testing "-iname FILE" "find dir -iname FILE" "dir/file\n" "" "" testing "-name (no arguments)" \ @@ -100,13 +96,13 @@ testing "-iname (no arguments)" \ testing "" "find dir \( -iname file -o -iname missing \) -exec echo {} \;" \ "dir/file\n" "" "" -testing "-path glob" \ - "find dir -path 'dir*e'" "dir/file\n" "" "" -testing "-wholename glob" \ - "find dir -wholename 'dir*e'" "dir/file\n" "" "" -testing "-ipath glob" \ - "find dir -ipath 'dIr*E'" "dir/file\n" "" "" -testing "-iwholename glob" \ - "find dir -iwholename 'dIr*E'" "dir/file\n" "" "" +testing "-path glob" "find dir -path 'dir*e'" "dir/file\n" "" "" +testing "-wholename glob" "find dir -wholename 'dir*e'" "dir/file\n" "" "" +testing "-ipath glob" "find dir -ipath 'dIr*E'" "dir/file\n" "" "" +testing "-iwholename glob" "find dir -iwholename 'dIr*E'" "dir/file\n" "" "" +testing "-printf" "find dir -name file -printf '%f %p %P %s'" \ + "file dir/file file 0" "" "" +testing "-printf .N" "find dir -name file -printf %.2f" "fi" "" "" + rm -rf dir diff --git a/toys/posix/find.c b/toys/posix/find.c index 8b757b8e..3f24c0dc 100644 --- a/toys/posix/find.c +++ b/toys/posix/find.c @@ -52,17 +52,13 @@ config FIND 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 + -printf FORMAT characters are \ escapes and: + %b 512 byte blocks used + %f basename %g textual gid %G numeric gid + %i decimal inode %l target of symlink %m octal mode + %M ls format type/mode %p path to file %P path to file minus DIR + %s size in bytes %T@ mod time as unixtime + %u username %U numeric uid %Z security context */ #define FOR_find @@ -222,7 +218,7 @@ static int do_find(struct dirtree *new) if (new->parent) { if (!dirtree_notdotdot(new)) return 0; if (TT.xdev && new->st.st_dev != new->parent->st.st_dev) recurse = 0; - } + } else TT.start = new->name; if (S_ISDIR(new->st.st_mode)) { // Descending into new directory @@ -550,62 +546,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; + char *fmt, *ff, next[32], buf[64], ch; + long ll; + int len; 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, + if (check) for (fmt = ss[1]; *fmt; fmt++) { + // Print the parts that aren't escapes + if (*fmt == '\\') { + if (!(ch = unescape(*++fmt))) error_exit("bad \\%c", *fmt); + putchar(ch); + } else if (*fmt != '%') putchar(*fmt); + else if (*++fmt == '%') putchar('%'); + else { + fmt = next_printf(ff = fmt-1, 0); + if ((len = fmt-ff)>28) error_exit("bad %.*s", len+1, ff); + memcpy(next, ff, len); + ff = 0; + ch = *fmt; + + // long long is its own stack size on LP64, so handle seperately + if (ch == 'i' || ch == 's') { + strcpy(next+len, "lld"); + printf(next, (ch == 'i') ? (long long)new->st.st_ino + : (long long)new->st.st_size); + } else { + + // LP64 says these are all a single "long" argument to printf + strcpy(next+len, "s"); + if (ch == 'G') next[len] = 'd', ll = new->st.st_gid; + else if (ch == 'm') next[len] = 'o', ll = new->st.st_mode&~S_IFMT; + else if (ch == 'U') next[len] = 'd', ll = new->st.st_uid; + else if (ch == 'f') ll = (long)new->name; + else if (ch == 'g') ll = (long)getgroupname(new->st.st_gid); + else if (ch == 'u') ll = (long)getusername(new->st.st_uid); + else if (ch == 'l') { + char *path = dirtree_path(new, 0); + + ll = (long)(ff = xreadlink(path)); + free(path); + if (!ll) ll = (long)""; + } else if (ch == 'M') { + mode_to_string(new->st.st_mode, buf); + ll = (long)buf; + } else if (ch == 'P') { + ch = *TT.start; + *TT.start = 0; + ll = (long)(ff = dirtree_path(new, 0)); + *TT.start = ch; + } else if (ch == 'p') ll = (long)(ff = dirtree_path(new, 0)); + else if (ch == 'T') { + if (*++fmt!='@') error_exit("bad -printf %%T: %%T%c", *fmt); + sprintf(buf, "%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); + ll = (long)buf; + } else error_exit("bad -printf %%%c", ch); + + printf(next, ll); + free(ff); + } } } } else goto error; @@ -659,11 +658,9 @@ void find_main(void) do_find(0); // Loop through paths - for (i = 0; i < len; i++) { - TT.start = ss[i]; - dirtree_flagread(TT.start, - DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)), do_find); - } + for (i = 0; i < len; i++) + dirtree_flagread(ss[i], DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)), + do_find); execdir(0, 1); -- cgit v1.2.3