[ -f testing.sh ] && . testing.sh
#testing "name" "command" "result" "infile" "stdin"
-#set -x
-testing "printf TEXT" "printf toyTestText" "toyTestText" "" ""
-testing "printf MULTILINE_TEXT" \
- "printf 'Testing\nmultiline\ntext\nfrom\ntoybox\tcommand.\b'" \
- "Testing\nmultiline\ntext\nfrom\ntoybox\tcommand.\b" "" ""
+# Disable shell builtin
+PRINTF="$(which printf)"
+
+testing "printf text" "$PRINTF TEXT" "TEXT" "" ""
+testing "printf escapes" "$PRINTF 'one\ntwo\n\v\t\r\f\e\b\athree'" \
+ "one\ntwo\n\v\t\r\f\e\b\athree" "" ""
+testing "printf %b escapes" "$PRINTF %b 'one\ntwo\n\v\t\r\f\e\b\athree'" \
+ "one\ntwo\n\v\t\r\f\e\b\athree" "" ""
+testing "printf null" "$PRINTF 'x\0y' | od -An -tx1" ' 78 00 79\n' "" ""
+testing "printf trailing slash" "$PRINTF 'abc\'" 'abc\' "" ""
+testing "printf octal" "$PRINTF ' \1\002\429\045x'" ' \001\002"9%x' "" ""
+testing "printf not octal" "$PRINTF '\9'" '\9' "" ""
+testing "printf hex" "$PRINTF 'A\x1b\x2B\x3Q\xa' | od -An -tx1" \
+ ' 41 1b 2b 03 51 0a\n' "" ""
+testing "printf %x" "$PRINTF '%x\n' 0x2a" "2a\n" "" ""
+
+testing "printf %d 42" "$PRINTF %d 42" "42" "" ""
+testing "printf %d 0x2a" "$PRINTF %d 0x2a" "42" "" ""
+testing "printf %d 052" "$PRINTF %d 052" "42" "" ""
+
+testing "printf %s width precision" \
+ "$PRINTF '%3s,%.3s,%10s,%10.3s' abcde fghij klmno pqrst" \
+ "abcde,fgh, klmno, pqr" "" ""
+
+# posix: "The format operand shall be reused as often as necessary to satisfy
+# the argument operands."
+
+testing "printf extra args" "$PRINTF 'abc%s!%ddef\n' X 42 ARG 36" \
+ "abcX!42def\nabcARG!36def\n" "" ""
+
+testing "printf '%3c'" "$PRINTF '%3c' x" " x" "" ""
+testing "printf '%-3c'" "$PRINTF '%-3c' x" "x " "" ""
+testing "printf '%+d'" "$PRINTF '%+d' 5" "+5" "" ""
+
+
testing "printf '%5d%4d' 1 21 321 4321 54321" \
- "printf '%5d%4d' 1 21 321 4321 54321" " 1 21 321432154321 0" "" ""
-testing "printf '%c %c' 78 79" "printf '%c %c' 78 79" "7 7" "" ""
-testing "printf '%d %d' 78 79" "printf '%d %d' 78 79" "78 79" "" ""
-testing "printf '%f %f' 78 79" "printf '%f %f' 78 79" \
+ "$PRINTF '%5d%4d' 1 21 321 4321 54321" " 1 21 321432154321 0" "" ""
+testing "printf '%c %c' 78 79" "$PRINTF '%c %c' 78 79" "7 7" "" ""
+testing "printf '%d %d' 78 79" "$PRINTF '%d %d' 78 79" "78 79" "" ""
+testing "printf '%f %f' 78 79" "$PRINTF '%f %f' 78 79" \
"78.000000 79.000000" "" ""
-testing "printf 'f f' 78 79" "printf 'f f' 78 79" "f f" "" ""
-testing "printf '%i %i' 78 79" "printf '%i %i' 78 79" "78 79" "" ""
-testing "printf '%o %o' 78 79" "printf '%o %o' 78 79" "116 117" "" ""
-testing "printf '%u %u' 78 79" "printf '%u %u' 78 79" "78 79" "" ""
-testing "printf '%u %u' -1 -2" "printf '%u %u' -1 -2" \
+testing "printf 'f f' 78 79" "$PRINTF 'f f' 78 79" "f f" "" ""
+testing "printf '%i %i' 78 79" "$PRINTF '%i %i' 78 79" "78 79" "" ""
+testing "printf '%o %o' 78 79" "$PRINTF '%o %o' 78 79" "116 117" "" ""
+testing "printf '%u %u' 78 79" "$PRINTF '%u %u' 78 79" "78 79" "" ""
+testing "printf '%u %u' -1 -2" "$PRINTF '%u %u' -1 -2" \
"18446744073709551615 18446744073709551614" "" ""
-testing "printf '%x %X' 78 79" "printf '%x %X' 78 79" "4e 4F" "" ""
-testing "printf '%g %G' 78 79" "printf '%g %G' 78 79" "78 79" "" ""
-testing "printf '%s %s' 78 79" "printf '%s %s' 78 79" "78 79" "" ""
+testing "printf '%x %X' 78 79" "$PRINTF '%x %X' 78 79" "4e 4F" "" ""
+testing "printf '%g %G' 78 79" "$PRINTF '%g %G' 78 79" "78 79" "" ""
+testing "printf '%s %s' 78 79" "$PRINTF '%s %s' 78 79" "78 79" "" ""
+++ /dev/null
-/* printf.c - Format and Print the data.
- *
- * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
- * Copyright 2014 Kyungwan Han <asura321@gmail.com>
- *
- * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
-
-USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN))
-
-config PRINTF
- bool "printf"
- default n
- help
- usage: printf FORMAT [ARGUMENT...]
-
- Format and print ARGUMENT(s) according to FORMAT, using C printf syntax
- (percent escapes for cdeEfgGiosuxX, slash escapes for \abefnrtv0 or
- \0OCT or 0xHEX).
-*/
-
-#define FOR_printf
-#include "toys.h"
-
-GLOBALS(
- char *hv_w;
- char *hv_p;
- int encountered;
-)
-
-// Detect matching character (return true/valse) and advance pointer if match.
-static int eat(char **s, char c)
-{
- int x = (**s == c);
-
- if (x) ++*s;
-
- return x;
-}
-
-// Add ll and L to Interger and floating point formats respectively.
-static char *get_format(char *f)
-{
- int len = strlen(f);
- char last = f[--len], *post = "";
-
- f[len] = 0;
- if (strchr("diouxX", last)) post = "ll"; // add ll to integer modifier.
- else if (strchr("feEgG", last)) post = "L"; // add L to float modifier.
- return xmprintf("%s%s%c", f, post, last);
-}
-
-// Print arguments with corresponding conversion and width and precision.
-static void print(char *fmt, int w, int p)
-{
- char *ptr = fmt, *ep = 0, *format = 0, *arg = *toys.optargs;
-
- errno = 0;
- if (strchr("diouxX", *ptr)) {
- long long val = 0;
-
- if (arg) {
- if (*arg == '\'' || *arg == '"') val = arg[1];
- else {
- val = strtoll(arg, &ep, 0);
- if (errno || (ep && (*ep || ep == arg))) {
- perror_msg("Invalid num %s", arg);
- val = 0;
- }
- }
- }
- format = get_format(fmt);
- TT.hv_w ? (TT.hv_p ? printf(format, w, p, val) : printf(format, w, val))
- : (TT.hv_p ? printf(format, p, val) : printf(format, val));
- } else if (strchr("gGeEf", *ptr)) {
- long double dval = 0;
-
- if (arg) {
- dval = strtold(arg, &ep);
- if (errno || (ep && (*ep || ep == arg))) {
- perror_msg("Invalid num %s", arg);
- dval = 0;
- }
- }
- format = get_format(fmt);
- TT.hv_w ? (TT.hv_p ? printf(format, w, p, dval) : printf(format, w, dval))
- : (TT.hv_p ? printf(format, p, dval) : printf(format, dval));
- } else if (*ptr == 's') {
- char *str = arg;
-
- if (!str) str = "";
-
- TT.hv_w ? (TT.hv_p ? printf(fmt,w,p,str): printf(fmt, w, str))
- : (TT.hv_p ? printf(fmt, p, str) : printf(fmt, str));
- } else if (*ptr == 'c') printf(fmt, arg ? *arg : 0);
-
- if (format) free(format);
-}
-
-// Parse escape sequences.
-static int handle_slash(char **esc_val)
-{
- char *ptr = *esc_val;
- int len = 1, base = 0;
- unsigned result = 0;
-
- if (*ptr == 'c') xexit();
-
- // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
- if (eat(&ptr, 'x')) base = 16;
- else if (*ptr >= '0' && *ptr <= '8') base = 8;
- len += (base-8)/8;
-
- // Not a hex or octal escape? (This catches trailing \)
- if (!len) {
- if (!(result = unescape(*ptr))) result = '\\';
- else ++*esc_val;
-
- return result;
- }
-
- while (len) {
- unsigned num = tolower(*ptr)-'0';
-
- if (num > 10) num += '0'-'a'+10;
- if (num >= base) {
- // Don't parse invalid hex value ala "\xvd", print it verbatim
- if (base == 16 && len == 2) {
- ptr--;
- result = '\\';
- }
- break;
- }
- result = (result*base)+num;
- ptr++;
- len--;
- }
- *esc_val = ptr;
-
- return (char)result;
-}
-
-void printf_main(void)
-{
- char *format = *toys.optargs, **arg = toys.optargs+1, *f, *p;
-
- for (f = format; *f; f++) {
- if (eat(&f, '\\')) putchar(handle_slash(&f));
- else if (*f != '%' || *++f == '%') xputc(*f);
- else if (*f == 'b')
- for (p = *arg ? *(arg++) : ""; *p; p++)
- putchar(eat(&p, '\\') ? handle_slash(&p) : *p);
- else {
- char *start = f;
- int wp[2], i;
-
- // todo: we currently ignore these?
- if (strchr("-+# ", *f)) f++;
- memset(wp, 0, 8);
- for (i=0; i<2; i++) {
- if (eat(&f, '*')) {
- if (*arg) wp[i] = atolx(*(arg++));
- } else while (isdigit(*f)) f++;
- if (!eat(&f, '.')) break;
- }
- if (!(p = strchr("diouxXfeEgGcs", *f)))
- perror_exit("bad format@%ld", f-format);
- else {
- int len = f-start;
-
- TT.hv_p = strstr(start, ".*");
- TT.hv_w = strchr(start, '*');
- //pitfall: handle diff b/w * and .*
- if ((TT.hv_w-1) == TT.hv_p) TT.hv_w = NULL;
- memcpy((p = xzalloc(len+1)), start, len);
- print(p+len-1, wp[0], wp[1]);
- if (*arg) arg++;
- free(p);
- p = NULL;
- }
- TT.encountered = 1;
- }
- }
-}
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/chown.html
* See http://opengroup.org/onlinepubs/9699919799/utilities/chgrp.html
- *
- * TODO: group only one of [HLP]
-USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv", TOYFLAG_BIN))
-USE_CHGRP(OLDTOY(chown, chgrp, TOYFLAG_BIN))
+USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv[-HLP]", TOYFLAG_BIN))
+USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
config CHGRP
- bool "chgrp/chown"
+ bool "chgrp"
default y
help
- usage: chown [-RHLP] [-fvh] [owner][:group] file...
- usage: chgrp [-RHLP] [-fvh] group file...
+ usage: chgrp/chown [-RHLP] [-fvh] group file...
- Change ownership of one or more files.
+ Change group of one or more files.
-f suppress most error messages.
-h change symlinks instead of what they point to
-L with -R change target of symlink, follow all symlinks
-P with -R change symlink, do not follow symlinks (default)
-v verbose output.
+
+config CHOWN
+ bool "chown"
+ default y
+ help
+ see: chgrp
*/
#define FOR_chgrp
+#define FORCE_FLAGS
#include "toys.h"
GLOBALS(
if (CFG_TOYBOX_FREE && ischown) free(own);
}
+
+void chown_main()
+{
+ chgrp_main();
+}
--- /dev/null
+/* printf.c - Format and Print the data.
+ *
+ * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
+ * Copyright 2014 Kyungwan Han <asura321@gmail.com>
+ *
+ * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
+ *
+ * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
+
+USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+
+config PRINTF
+ bool "printf"
+ default y
+ help
+ usage: printf FORMAT [ARGUMENT...]
+
+ Format and print ARGUMENT(s) according to FORMAT, using C printf syntax
+ (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX).
+*/
+
+#define FOR_printf
+#include "toys.h"
+
+// Detect matching character (return true/valse) and advance pointer if match.
+static int eat(char **s, char c)
+{
+ int x = (**s == c);
+
+ if (x) ++*s;
+
+ return x;
+}
+
+// Parse escape sequences.
+static int handle_slash(char **esc_val)
+{
+ char *ptr = *esc_val;
+ int len, base = 0;
+ unsigned result = 0, num;
+
+ if (*ptr == 'c') xexit();
+
+ // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
+ if (eat(&ptr, 'x')) base = 16;
+ else if (*ptr >= '0' && *ptr <= '8') base = 8;
+ len = (char []){0,3,2}[base/8];
+
+ // Not a hex or octal escape? (This catches trailing \)
+ if (!len) {
+ if (!(result = unescape(*ptr))) result = '\\';
+ else ++*esc_val;
+
+ return result;
+ }
+
+ while (len) {
+ num = tolower(*ptr) - '0';
+ if (num >= 'a'-'0') num += '0'-'a'+10;
+ if (num >= base) {
+ // Don't parse invalid hex value ala "\xvd", print it verbatim
+ if (base == 16 && len == 2) {
+ ptr--;
+ result = '\\';
+ }
+ break;
+ }
+ result = (result*base)+num;
+ ptr++;
+ len--;
+ }
+ *esc_val = ptr;
+
+ return result;
+}
+
+void printf_main(void)
+{
+ char **arg = toys.optargs+1;
+
+ // Repeat format until arguments consumed
+ for (;;) {
+ int seen = 0;
+ char *f = *toys.optargs;
+
+ // Loop through characters in format
+ while (*f) {
+ if (eat(&f, '\\')) putchar(handle_slash(&f));
+ else if (!eat(&f, '%') || *f == '%') putchar(*f++);
+
+ // Handle %escape
+ else {
+ char c, *end = 0, *aa, *to = toybuf;
+ int wp[] = {0,-1}, i;
+
+ // Parse width.precision between % and type indicator.
+ *to++ = '%';
+ while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
+ for (i=0; i<2; i++) {
+ if (eat(&f, '*')) {
+ if (*arg) wp[i] = atolx(*arg++);
+ } else while (*f >= '0' && *f <= '9') {
+ if (wp[i]<0) wp[i] = 0;
+ wp[i] = (wp[i]*10)+(*f++)-'0';
+ }
+ if (!eat(&f, '.')) break;
+ }
+ c = *f++;
+ seen = sprintf(to, "*.*%c", c);;
+ errno = 0;
+ aa = *arg ? *arg++ : "";
+
+ // Output %esc using parsed format string
+ if (c == 'b') {
+ while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa) : *aa++);
+
+ continue;
+ } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
+ else if (c == 's') printf(toybuf, wp[0], wp[1], aa);
+ else if (strchr("diouxX", c)) {
+ long ll;
+
+ if (*aa == '\'' || *aa == '"') ll = aa[1];
+ else ll = strtoll(aa, &end, 0);
+
+ sprintf(to, "*.*ll%c", c);
+ printf(toybuf, wp[0], wp[1], ll);
+ } else if (strchr("feEgG", c)) {
+ long double ld = strtold(aa, &end);
+
+ sprintf(to, "*.*L%c", c);
+ printf(toybuf, wp[0], wp[1], ld);
+ } else error_exit("bad %%%c@%ld", c, f-*toys.optargs);
+
+ if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
+ }
+ }
+
+ // Posix says to keep looping through format until we consume all args.
+ // This only works if the format actually consumed at least one arg.
+ if (!seen || !*arg) break;
+ }
+}
if (line[len-1] == '\n') line[--len] = eol++;
TT.count++;
- logrus = TT.restart ? TT.restart : (void *)TT.pattern;
+ // The restart-1 is because we added one to make sure it wasn't NULL,
+ // otherwise N as last command would restart script
+ logrus = TT.restart ? ((struct step *)TT.restart)-1 : (void *)TT.pattern;
TT.restart = 0;
while (logrus) {
toybuf[off++] = '$';
emit(toybuf, off, 1);
} else if (c=='n') {
- TT.restart = logrus->next;
+ TT.restart = logrus->next+1;
break;
} else if (c=='N') {
// Can't just grab next line because we could have multiple N and
// we need to actually read ahead to get N;$p EOF detection right.
if (pline) {
- TT.restart = logrus->next;
+ TT.restart = logrus->next+1;
extend_string(&line, TT.nextline, len, -TT.nextlen);
free(TT.nextline);
TT.nextline = line;
to = delim = xmalloc(strlen(*pstr)+1);
while (mode || *from != d) {
- if (!*from) return 0;
+ if (!*from) {
+ *to = 0;
+
+ return 0;
+ }
// delimiter in regex character range doesn't count
if (*from == '[') {
static void jewel_of_judgement(char **pline, long len)
{
struct step *corwin = (void *)TT.pattern;
- char *line = *pline, *reg, c, *errstart = *pline;
+ char *line, *reg, c, *errstart;
int i;
+ line = errstart = pline ? *pline : "";
+
// Append additional line to pattern argument string?
// We temporarily repurpose "hit" to indicate line continuations
if (corwin && corwin->prev->hit) {
+ if (!*pline) error_exit("unfinished %c", corwin->prev->c);;
// Remove half-finished entry from list so remalloc() doesn't confuse it
TT.pattern = TT.pattern->prev;
corwin = dlist_pop(&TT.pattern);
- corwin->hit = 0;
c = corwin->c;
reg = (char *)corwin;
reg += corwin->arg1 + strlen(reg + corwin->arg1);
- // Resume parsing
- goto append;
+ // Resume parsing for 'a' or 's' command
+ if (corwin->hit < 256) goto resume_s;
+ else goto resume_a;
}
// Loop through commands in line
for (;;) {
if (corwin) dlist_add_nomalloc(&TT.pattern, (void *)corwin);
- while (isspace(*line) || *line == ';') line++;
- if (!*line || *line == '#') return;
+ for (;;) {
+ while (isspace(*line) || *line == ';') line++;
+ if (*line == '#') while (*line && *line != '\n') line++;
+ else break;
+ }
+ if (!*line) return;
errstart = line;
memset(toybuf, 0, sizeof(struct step));
else if (c == '}') {
if (!TT.nextlen--) break;
} else if (c == 's') {
- char *merlin, *fiona, delim = 0;
+ char *fiona, delim = 0;
// s/pattern/replacement/flags
+ // line continuations use arg1, so we fill out arg2 first (since the
+ // regex part can't be multiple lines) and swap them back later.
+
// get pattern (just record, we parse it later)
- corwin->arg1 = reg - (char *)corwin;
- if (!(merlin = unescape_delimited_string(&line, &delim, 1))) goto brand;
+ corwin->arg2 = reg - (char *)corwin;
+ if (!(TT.remember = unescape_delimited_string(&line, &delim, 1)))
+ goto brand;
+ reg += sizeof(regex_t);
+ corwin->arg1 = reg-(char *)corwin;
+ corwin->hit = delim;
+resume_s:
// get replacement - don't replace escapes because \1 and \& need
// processing later, after we replace \\ with \ we can't tell \\1 from \1
fiona = line;
- while (*line != delim) {
- if (!*line) goto brand;
- if (*line == '\\') {
- if (!line[1]) goto brand;
- line += 2;
- } else line++;
+ while (*fiona != corwin->hit) {
+ if (!*fiona) break;
+ if (*fiona++ == '\\') {
+ if (!*fiona || *fiona == '\n') {
+ fiona[-1] = '\n';
+ break;
+ }
+ fiona++;
+ }
}
- corwin->arg2 = corwin->arg1 + sizeof(regex_t);
- reg = extend_string((void *)&corwin, fiona, corwin->arg2, line-fiona)+1;
+ reg = extend_string((void *)&corwin, line, reg-(char *)corwin,fiona-line);
+ line = fiona;
+ // line continuation? (note: '\n' can't be a valid delim).
+ if (*line == corwin->hit) corwin->hit = 0;
+ else {
+ if (!*line) continue;
+ reg--;
+ line++;
+ goto resume_s;
+ }
+
+ // swap arg1/arg2 so they're back in order arguments occur.
+ i = corwin->arg1;
+ corwin->arg1 = corwin->arg2;
+ corwin->arg2 = i;
// get flags
for (line++; *line; line++) {
// We deferred actually parsing the regex until we had the s///i flag
// allocating the space was done by extend_string() above
- if (!*merlin) corwin->arg1 = 0;
- else xregcomp((void *)(corwin->arg1 + (char *)corwin), merlin,
+ if (!*TT.remember) corwin->arg1 = 0;
+ else xregcomp((void *)(corwin->arg1 + (char *)corwin), TT.remember,
((toys.optflags & FLAG_r)*REG_EXTENDED)|((corwin->sflags&1)*REG_ICASE));
- free(merlin);
+ free(TT.remember);
+ TT.remember = 0;
if (*line == 'w') {
line++;
goto writenow;
} else if (strchr("abcirtTw:", c)) {
int end;
- // Trim whitespace from "b ;" and ": blah " but only first space in "w x "
-
while (isspace(*line) && *line != '\n') line++;
-append:
+
+ // Resume logic differs from 's' case because we don't add a newline
+ // unless it's after something, so we add it on return instead.
+resume_a:
+ corwin->hit = 0;
+
+ // Trim whitespace from "b ;" and ": blah " but only first space in "w x "
if (!(end = strcspn(line, strchr("btT:", c) ? "; \t\r\n\v\f" : "\n"))) {
if (strchr("btT", c)) continue;
else if (!corwin->arg1) break;
// Line continuation? (Two slightly different input methods, -e with
// embedded newline vs -f line by line. Must parse both correctly.)
if (!strchr("btT:", c) && line[-1] == '\\') {
- // reg is next available space, so reg[-1] is the null terminator
- reg[-2] = 0;
- if (*line && line[1]) {
- reg -= 2;
- line++;
- goto append;
- } else corwin->hit++;
+ // backslash only matters if we have an odd number of them
+ for (i = 0; i<end; i++) if (line[-i-1] != '\\') break;
+ if (i&1) {
+ // reg is next available space, so reg[-1] is the null terminator
+ reg[-2] = 0;
+ if (*line && line[1]) {
+ reg -= 2;
+ line++;
+ goto resume_a;
+ } else corwin->hit = 256;
+ }
}
// Commands that take no arguments
jewel_of_judgement(&dworkin->arg, strlen(dworkin->arg));
for (dworkin = TT.f; dworkin; dworkin = dworkin->next)
do_lines(xopen(dworkin->arg, O_RDONLY), dworkin->arg, jewel_of_judgement);
+ jewel_of_judgement(0, 0);
dlist_terminate(TT.pattern);
if (TT.nextlen) error_exit("no }");