Merge remote-tracking branch 'toybox/master' into HEAD am: bac04b4984 am: 3917661b78
am: 80060a8309

Change-Id: I1c95d91113a23a50ed5f2ae6c84b3f5948765d7e
diff --git a/generated/flags.h b/generated/flags.h
index 0101811..ee63832 100644
--- a/generated/flags.h
+++ b/generated/flags.h
@@ -403,15 +403,19 @@
 #undef FLAG_c
 #endif
 
-// cut b:|c:|f:|d:sn[!cbf] b:|c:|f:|d:sn[!cbf]
+// cut b*|c*|f*|F*|O(output-delimiter):d:sDn[!cbf] b*|c*|f*|F*|O(output-delimiter):d:sDn[!cbf]
 #undef OPTSTR_cut
-#define OPTSTR_cut "b:|c:|f:|d:sn[!cbf]"
+#define OPTSTR_cut "b*|c*|f*|F*|O(output-delimiter):d:sDn[!cbf]"
 #ifdef CLEANUP_cut
 #undef CLEANUP_cut
 #undef FOR_cut
 #undef FLAG_n
+#undef FLAG_D
 #undef FLAG_s
 #undef FLAG_d
+#undef FLAG_output_delimiter
+#undef FLAG_O
+#undef FLAG_F
 #undef FLAG_f
 #undef FLAG_c
 #undef FLAG_b
@@ -3570,11 +3574,15 @@
 #define TT this.cut
 #endif
 #define FLAG_n (1<<0)
-#define FLAG_s (1<<1)
-#define FLAG_d (1<<2)
-#define FLAG_f (1<<3)
-#define FLAG_c (1<<4)
-#define FLAG_b (1<<5)
+#define FLAG_D (1<<1)
+#define FLAG_s (1<<2)
+#define FLAG_d (1<<3)
+#define FLAG_output_delimiter (1<<4)
+#define FLAG_O (1<<4)
+#define FLAG_F (1<<5)
+#define FLAG_f (1<<6)
+#define FLAG_c (1<<7)
+#define FLAG_b (1<<8)
 #endif
 
 #ifdef FOR_date
diff --git a/generated/globals.h b/generated/globals.h
index 3462ffa..84a50d6 100644
--- a/generated/globals.h
+++ b/generated/globals.h
@@ -1027,14 +1027,12 @@
 // toys/posix/cut.c
 
 struct cut_data {
-  char *delim;
-  char *flist;
-  char *clist;
-  char *blist;
+  char *d;
+  char *O;
+  struct arg_list *select[4]; // we treat them the same, so loop through
 
-  void *slist_head;
-  unsigned nelem;
-  void (*do_cut)(int fd);
+  int pairs;
+  regex_t reg;
 };
 
 // toys/posix/date.c
diff --git a/generated/help.h b/generated/help.h
index c00251e..6ab10a3 100644
--- a/generated/help.h
+++ b/generated/help.h
@@ -302,10 +302,10 @@
 
 #define HELP_useradd "usage: useradd [-SDH] [-h DIR] [-s SHELL] [-G GRP] [-g NAME] [-u UID] USER [GROUP]\n\nCreate new user, or add USER to GROUP\n\n-D       Don't assign a password\n-g NAME  Real name\n-G GRP   Add user to existing group\n-h DIR   Home directory\n-H       Don't create home directory\n-s SHELL Login shell\n-S       Create a system user\n-u UID   User id\n\n"
 
-#define HELP_traceroute "usage: traceroute [-46FUIldnvr] [-f 1ST_TTL] [-m MAXTTL] [-p PORT] [-q PROBES]\n[-s SRC_IP] [-t TOS] [-w WAIT_SEC] [-g GATEWAY] [-i IFACE] [-z PAUSE_MSEC] HOST [BYTES]\n\ntraceroute6 [-dnrv] [-m MAXTTL] [-p PORT] [-q PROBES][-s SRC_IP] [-t TOS] [-w WAIT_SEC]\n  [-i IFACE] HOST [BYTES]\n\nTrace the route to HOST\n\n-4,-6 Force IP or IPv6 name resolution\n-F    Set the don't fragment bit (supports IPV4 only)\n-U    Use UDP datagrams instead of ICMP ECHO (supports IPV4 only)\n-I    Use ICMP ECHO instead of UDP datagrams (supports IPV4 only)\n-l    Display the TTL value of the returned packet (supports IPV4 only)\n-d    Set SO_DEBUG options to socket\n-n    Print numeric addresses\n-v    verbose\n-r    Bypass routing tables, send directly to HOST\n-m    Max time-to-live (max number of hops)(RANGE 1 to 255)\n-p    Base UDP port number used in probes(default 33434)(RANGE 1 to 65535)\n-q    Number of probes per TTL (default 3)(RANGE 1 to 255)\n-s    IP address to use as the source address\n-t    Type-of-service in probe packets (default 0)(RANGE 0 to 255)\n-w    Time in seconds to wait for a response (default 3)(RANGE 0 to 86400)\n-g    Loose source route gateway (8 max) (supports IPV4 only)\n-z    Pause Time in milisec (default 0)(RANGE 0 to 86400) (supports IPV4 only)\n-f    Start from the 1ST_TTL hop (instead from 1)(RANGE 1 to 255) (supports IPV4 only)\n-i    Specify a network interface to operate with\n\n"
-
 #define HELP_tr "usage: tr [-cds] SET1 [SET2]\n\nTranslate, squeeze, or delete characters from stdin, writing to stdout\n\n-c/-C  Take complement of SET1\n-d     Delete input characters coded SET1\n-s     Squeeze multiple output characters of SET2 into one character\n\n"
 
+#define HELP_traceroute "usage: traceroute [-46FUIldnvr] [-f 1ST_TTL] [-m MAXTTL] [-p PORT] [-q PROBES]\n[-s SRC_IP] [-t TOS] [-w WAIT_SEC] [-g GATEWAY] [-i IFACE] [-z PAUSE_MSEC] HOST [BYTES]\n\ntraceroute6 [-dnrv] [-m MAXTTL] [-p PORT] [-q PROBES][-s SRC_IP] [-t TOS] [-w WAIT_SEC]\n  [-i IFACE] HOST [BYTES]\n\nTrace the route to HOST\n\n-4,-6 Force IP or IPv6 name resolution\n-F    Set the don't fragment bit (supports IPV4 only)\n-U    Use UDP datagrams instead of ICMP ECHO (supports IPV4 only)\n-I    Use ICMP ECHO instead of UDP datagrams (supports IPV4 only)\n-l    Display the TTL value of the returned packet (supports IPV4 only)\n-d    Set SO_DEBUG options to socket\n-n    Print numeric addresses\n-v    verbose\n-r    Bypass routing tables, send directly to HOST\n-m    Max time-to-live (max number of hops)(RANGE 1 to 255)\n-p    Base UDP port number used in probes(default 33434)(RANGE 1 to 65535)\n-q    Number of probes per TTL (default 3)(RANGE 1 to 255)\n-s    IP address to use as the source address\n-t    Type-of-service in probe packets (default 0)(RANGE 0 to 255)\n-w    Time in seconds to wait for a response (default 3)(RANGE 0 to 86400)\n-g    Loose source route gateway (8 max) (supports IPV4 only)\n-z    Pause Time in milisec (default 0)(RANGE 0 to 86400) (supports IPV4 only)\n-f    Start from the 1ST_TTL hop (instead from 1)(RANGE 1 to 255) (supports IPV4 only)\n-i    Specify a network interface to operate with\n\n"
+
 #define HELP_tftpd "usage: tftpd [-cr] [-u USER] [DIR]\n\nTransfer file from/to tftp server.\n\n-r	read only\n-c	Allow file creation via upload\n-u	run as USER\n-l	Log to syslog (inetd mode requires this)\n\n"
 
 #define HELP_tftp "usage: tftp [OPTIONS] HOST [PORT]\n\nTransfer file from/to tftp server.\n\n-l FILE Local FILE\n-r FILE Remote FILE\n-g    Get file\n-p    Put file\n-b SIZE Transfer blocks of SIZE octets(8 <= SIZE <= 65464)\n\n"
@@ -406,10 +406,10 @@
 
 #define HELP_dhcpd "usage: dhcpd [-46fS] [-i IFACE] [-P N] [CONFFILE]\n\n -f    Run in foreground\n -i Interface to use\n -S    Log to syslog too\n -P N  Use port N (default ipv4 67, ipv6 547)\n -4, -6    Run as a DHCPv4 or DHCPv6 server\n\n"
 
-#define HELP_dhcp6 "usage: dhcp6 [-fbnqvR] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n\n      Configure network dynamicaly using DHCP.\n\n    -i Interface to use (default eth0)\n    -p Create pidfile\n    -s Run PROG at DHCP events\n    -t Send up to N Solicit packets\n    -T Pause between packets (default 3 seconds)\n    -A Wait N seconds after failure (default 20)\n    -f Run in foreground\n    -b Background if lease is not obtained\n    -n Exit if lease is not obtained\n    -q Exit after obtaining lease\n    -R Release IP on exit\n    -S Log to syslog too\n    -r Request this IP address\n    -v Verbose\n\n    Signals:\n    USR1  Renew current lease\n    USR2  Release current lease\n\n"
-
 #define HELP_dhcp "usage: dhcp [-fbnqvoCRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n            [-H HOSTNAME] [-V VENDOR] [-x OPT:VAL] [-O OPT]\n\n     Configure network dynamicaly using DHCP.\n\n   -i Interface to use (default eth0)\n   -p Create pidfile\n   -s Run PROG at DHCP events (default /usr/share/dhcp/default.script)\n   -B Request broadcast replies\n   -t Send up to N discover packets\n   -T Pause between packets (default 3 seconds)\n   -A Wait N seconds after failure (default 20)\n   -f Run in foreground\n   -b Background if lease is not obtained\n   -n Exit if lease is not obtained\n   -q Exit after obtaining lease\n   -R Release IP on exit\n   -S Log to syslog too\n   -a Use arping to validate offered address\n   -O Request option OPT from server (cumulative)\n   -o Don't request any options (unless -O is given)\n   -r Request this IP address\n   -x OPT:VAL  Include option OPT in sent packets (cumulative)\n   -F Ask server to update DNS mapping for NAME\n   -H Send NAME as client hostname (default none)\n   -V VENDOR Vendor identifier (default 'toybox VERSION')\n   -C Don't send MAC as client identifier\n   -v Verbose\n\n   Signals:\n   USR1  Renew current lease\n   USR2  Release current lease\n\n\n"
 
+#define HELP_dhcp6 "usage: dhcp6 [-fbnqvR] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n\n      Configure network dynamicaly using DHCP.\n\n    -i Interface to use (default eth0)\n    -p Create pidfile\n    -s Run PROG at DHCP events\n    -t Send up to N Solicit packets\n    -T Pause between packets (default 3 seconds)\n    -A Wait N seconds after failure (default 20)\n    -f Run in foreground\n    -b Background if lease is not obtained\n    -n Exit if lease is not obtained\n    -q Exit after obtaining lease\n    -R Release IP on exit\n    -S Log to syslog too\n    -r Request this IP address\n    -v Verbose\n\n    Signals:\n    USR1  Renew current lease\n    USR2  Release current lease\n\n"
+
 #define HELP_dd "usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [bs=N] [count=N] [skip=N]\n        [seek=N] [conv=notrunc|noerror|sync|fsync] [status=noxfer|none]\n\nOptions:\nif=FILE   Read from FILE instead of stdin\nof=FILE   Write to FILE instead of stdout\nbs=N      Read and write N bytes at a time\nibs=N     Read N bytes at a time\nobs=N     Write N bytes at a time\ncount=N   Copy only N input blocks\nskip=N    Skip N input blocks\nseek=N    Skip N output blocks\nconv=notrunc  Don't truncate output file\nconv=noerror  Continue after read errors\nconv=sync     Pad blocks with zeros\nconv=fsync    Physically write data out before finishing\nstatus=noxfer Don't show transfer rate\nstatus=none   Don't show transfer rate or records in/out\n\nNumbers may be suffixed by c (*1), w (*2), b (*512), kD (*1000), k (*1024),\nMD (*1000*1000), M (*1024*1024), GD (*1000*1000*1000) or G (*1024*1024*1024).\n\n"
 
 #define HELP_crontab "usage: crontab [-u user] FILE\n               [-u user] [-e | -l | -r]\n               [-c dir]\n\nFiles used to schedule the execution of programs.\n\n-c crontab dir\n-e edit user's crontab\n-l list user's crontab\n-r delete user's crontab\n-u user\nFILE Replace crontab by FILE ('-': stdin)\n\n"
@@ -556,7 +556,7 @@
 
 #define HELP_date "usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]\n\nSet/get the current date/time. With no SET shows the current date.\n\nDefault SET format is \"MMDDhhmm[[CC]YY][.ss]\", that's (2 digits each)\nmonth, day, hour (0-23), and minute. Optionally century, year, and second.\nAlso accepts \"@UNIXTIME[.FRACTION]\" as seconds since midnight Jan 1 1970.\n\n-d	Show DATE instead of current time (convert date format)\n-D	+FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])\n-r	Use modification time of FILE instead of current date\n-u	Use UTC instead of current timezone\n\n+FORMAT specifies display format string using strftime(3) syntax:\n\n%% literal %             %n newline              %t tab\n%S seconds (00-60)       %M minute (00-59)       %m month (01-12)\n%H hour (0-23)           %I hour (01-12)         %p AM/PM\n%y short year (00-99)    %Y year                 %C century\n%a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)\n%b short month name      %B month name           %Z timezone name\n%j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)\n%N nanosec (output only)\n\n%U Week of year (0-53 start sunday)   %W Week of year (0-53 start monday)\n%V Week of year (1-53 start monday, week < 4 days not part of this year)\n\n%D = \"%m/%d/%y\"    %r = \"%I : %M : %S %p\"   %T = \"%H:%M:%S\"   %h = \"%b\"\n%x locale date     %X locale time           %c locale date/time\n\n"
 
-#define HELP_cut "usage: cut OPTION... [FILE]...\n\nPrint selected parts of lines from each FILE to standard output.\n\n-b LIST	select only these bytes from LIST\n-c LIST	select only these characters from LIST\n-f LIST	select only these fields\n-d DELIM	use DELIM instead of TAB for field delimiter\n-s	do not print lines not containing delimiters\n-n	don't split multibyte characters (ignored)\n\n"
+#define HELP_cut "usage: cut [-Ds] [-bcfF LIST] [-dO DELIM] [FILE...]\n\nPrint selected parts of lines from each FILE to standard output.\n\nEach selection LIST is comma separated, either numbers (counting from 1)\nor dash separated ranges (inclusive, with X- meaning to end of line and -X\nfrom start). By default selection ranges are sorted and collated, use -D\nto prevent that.\n\n-b	select bytes\n-c	select UTF-8 characters\n-d	use DELIM (default is TAB for -f, run of whitespace for -F)\n-D	Don't sort/collate selections\n-f	select fields (words) separated by single DELIM character\n-F	select fields separated by DELIM regex\n-O	output delimiter (default one space for -F, input delim for -f)\n-s	skip lines without delimiters\n\n"
 
 #define HELP_cpio "usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]\n       [ignored: -mdu -H newc]\n\ncopy files into and out of a \"newc\" format cpio archive\n\n-F FILE	use archive FILE instead of stdin/stdout\n-p DEST	copy-pass mode, copy stdin file list to directory DEST\n-i	extract from archive into file system (stdin=archive)\n-o	create archive (stdin=list of files, stdout=archive)\n-t	test files (list only, stdin=archive, stdout=list of files)\n-v	verbose (list files during create/extract)\n--no-preserve-owner (don't set ownership during extract)\n--trailer Add legacy trailer (prevents concatenation).\n\n"
 
diff --git a/generated/newtoys.h b/generated/newtoys.h
index ae90735..c332966 100644
--- a/generated/newtoys.h
+++ b/generated/newtoys.h
@@ -38,7 +38,7 @@
 USE_CPIO(NEWTOY(cpio, "(no-preserve-owner)(trailer)mduH:p:|i|t|F:v(verbose)o|[!pio][!pot][!pF]", TOYFLAG_BIN))
 USE_CROND(NEWTOY(crond, "fbSl#<0=8d#<0L:c:[-bf][-LS][-ld]", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
 USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
-USE_CUT(NEWTOY(cut, "b:|c:|f:|d:sn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CUT(NEWTOY(cut, "b*|c*|f*|F*|O(output-delimiter):d:sDn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_DATE(NEWTOY(date, "d:D:r:u[!dr]", TOYFLAG_BIN))
 USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEALLOCVT(NEWTOY(deallocvt, ">1", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NEEDROOT))
diff --git a/lib/lib.c b/lib/lib.c
index 70ad075..80570ed 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -12,7 +12,9 @@
   fprintf(stderr, "%s: ", toys.which->name);
   if (msg) vfprintf(stderr, msg, va);
   else s+=2;
-  if (err) fprintf(stderr, s, strerror(err));
+  if (err>0) fprintf(stderr, s, strerror(err));
+  if (err<0 && CFG_TOYBOX_HELP)
+    fprintf(stderr, " (See \"%s --help\")", toys.which->name);
   if (msg || err) putc('\n', stderr);
   if (!toys.exitval) toys.exitval++;
 }
@@ -66,12 +68,10 @@
 {
   va_list va;
 
-  if (CFG_TOYBOX_HELP)
-    fprintf(stderr, "See %s --help\n", toys.which->name);
-
-  if (msg) {
+  if (!msg) show_help(stdout);
+  else {
     va_start(va, msg);
-    verror_msg(msg, 0, va);
+    verror_msg(msg, -1, va);
     va_end(va);
   }
 
@@ -637,6 +637,19 @@
   loopfiles_rw(argv, O_RDONLY|O_CLOEXEC|WARN_ONLY, 0, function);
 }
 
+// call loopfiles with do_lines()
+static void (*do_lines_bridge)(char **pline, long len);
+static void loopfile_lines_bridge(int fd, char *name)
+{
+  do_lines(fd, do_lines_bridge);
+}
+
+void loopfiles_lines(char **argv, void (*function)(char **pline, long len))
+{
+  do_lines_bridge = function;
+  loopfiles(argv, loopfile_lines_bridge);
+}
+
 // Slow, but small.
 
 char *get_rawline(int fd, long *plen, char end)
diff --git a/lib/lib.h b/lib/lib.h
index 889430b..021ab44 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -216,6 +216,7 @@
 void loopfiles_rw(char **argv, int flags, int permissions,
   void (*function)(int fd, char *name));
 void loopfiles(char **argv, void (*function)(int fd, char *name));
+void loopfiles_lines(char **argv, void (*function)(char **pline, long len));
 long long xsendfile(int in, int out);
 int wfchmodat(int rc, char *name, mode_t mode);
 int copy_tempfile(int fdin, char *name, char **tempname);
diff --git a/scripts/config2help.c b/scripts/config2help.c
index 575b7b8..d238939 100644
--- a/scripts/config2help.c
+++ b/scripts/config2help.c
@@ -262,7 +262,7 @@
   // entry until we run out of matching pairs.
   for (;;) {
     struct symbol *throw = 0, *catch;
-    char *this, *that, *cusage, *tusage, *name;
+    char *this, *that, *cusage, *tusage, *name = 0;
     int len;
 
     // find a usage: name and collate all enabled entries with that name
@@ -270,16 +270,18 @@
       if (catch->enabled != 1) continue;
       if (catch->help && (that = keyword("usage:", catch->help->data))) {
         struct double_list *cfrom, *tfrom, *anchor;
-        char *try, **cdashlines, **tdashlines;
+        char *try, **cdashlines, **tdashlines, *usage;
         int clen, tlen;
 
         // Align usage: lines, finding a matching pair so we can suck help
         // text out of throw into catch, copying from this to that
-        if (!throw) name = that;
+        if (!throw) usage = that;
         else if (strncmp(name, that, len) || !isspace(that[len])) continue;
         catch->enabled++;
         while (!isspace(*that) && *that) that++;
-        if (!throw) len = that-name;
+        if (!throw) len = that-usage;
+        free(name);
+        name = strndup(usage, len);
         that = skip_spaces(that);
         if (!throw) {
           throw = catch;
diff --git a/toys/android/getprop.c b/toys/android/getprop.c
index 04a9b28..51ef7f6 100644
--- a/toys/android/getprop.c
+++ b/toys/android/getprop.c
@@ -57,6 +57,12 @@
   __system_property_read_callback(pi, read_callback, NULL);
 }
 
+static void print_callback(void *unused, const char *unused_name, const char *value,
+                           unsigned unused_serial)
+{
+  puts(value);
+}
+
 // Needed to supress extraneous "Loaded property_contexts from" message
 static int selinux_log_callback_local(int type, const char *fmt, ...)
 {
@@ -87,9 +93,12 @@
       puts(context);
       if (CFG_TOYBOX_FREE) free(context);
     } else {
-      if (__system_property_get(*toys.optargs, toybuf) <= 0)
-        strcpy(toybuf, toys.optargs[1] ? toys.optargs[1] : "");
-      puts(toybuf);
+      const prop_info* pi = __system_property_find(*toys.optargs);
+      if (pi == NULL) {
+        puts(toys.optargs[1] ? toys.optargs[1] : "");
+      } else {
+        __system_property_read_callback(pi, print_callback, NULL);
+      }
     }
   } else {
     size_t i;
diff --git a/toys/android/setprop.c b/toys/android/setprop.c
index ec411f4..14c24d9 100644
--- a/toys/android/setprop.c
+++ b/toys/android/setprop.c
@@ -29,7 +29,7 @@
   // recognize most failures (because it doesn't wait for init), so
   // we duplicate all of init's checks here to help the user.
 
-  if (value_len >= PROP_VALUE_MAX)
+  if (value_len >= PROP_VALUE_MAX && !strncmp(value, "ro.", 3))
     error_exit("value '%s' too long; try '%.*s'",
                value, PROP_VALUE_MAX - 1, value);
 
diff --git a/toys/other/sysctl.c b/toys/other/sysctl.c
index ad643c9..3773594 100644
--- a/toys/other/sysctl.c
+++ b/toys/other/sysctl.c
@@ -149,7 +149,7 @@
 
   // Loop through arguments, displaying or assigning as appropriate
   } else {
-    if (!*toys.optargs) help_exit(0);
+    if (!*toys.optargs) help_exit("Needs 1 arg");
     for (args = toys.optargs; *args; args++) process_key(*args, 0);
   }
 }
diff --git a/toys/posix/cut.c b/toys/posix/cut.c
index 2cb3ef1..f43d3e9 100644
--- a/toys/posix/cut.c
+++ b/toys/posix/cut.c
@@ -1,276 +1,188 @@
-/* cut.c - Cut from a file.
+/* cut.c - print selected ranges from a file
  *
- * Copyright 2012 Ranjan Kumar <ranjankumar.bth@gmail.com>
- * Copyright 2012 Kyungwan Han <asura321@gmail.com>
+ * Copyright 2016 Rob Landley <rob@landley.net>
  *
  * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html
  *
- * TODO: cleanup
+ * Deviations from posix: added -DF. We can only accept 512 selections, and
+ * "-" counts as start to end. Using spaces to separate a comma-separated list
+ * is silly and inconsistent with dd, ps, cp, and mount.
+ *
+ * todo: -n, -s with -c
 
-USE_CUT(NEWTOY(cut, "b:|c:|f:|d:sn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CUT(NEWTOY(cut, "b*|c*|f*|F*|O(output-delimiter):d:sDn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config CUT
   bool "cut"
   default y
   help
-    usage: cut OPTION... [FILE]...
+    usage: cut [-Ds] [-bcfF LIST] [-dO DELIM] [FILE...]
 
     Print selected parts of lines from each FILE to standard output.
 
-    -b LIST	select only these bytes from LIST
-    -c LIST	select only these characters from LIST
-    -f LIST	select only these fields
-    -d DELIM	use DELIM instead of TAB for field delimiter
-    -s	do not print lines not containing delimiters
-    -n	don't split multibyte characters (ignored)
+    Each selection LIST is comma separated, either numbers (counting from 1)
+    or dash separated ranges (inclusive, with X- meaning to end of line and -X
+    from start). By default selection ranges are sorted and collated, use -D
+    to prevent that.
+
+    -b	select bytes
+    -c	select UTF-8 characters
+    -d	use DELIM (default is TAB for -f, run of whitespace for -F)
+    -D	Don't sort/collate selections
+    -f	select fields (words) separated by single DELIM character
+    -F	select fields separated by DELIM regex
+    -O	output delimiter (default one space for -F, input delim for -f)
+    -s	skip lines without delimiters
 */
 #define FOR_cut
 #include "toys.h"
 
 GLOBALS(
-  char *delim;
-  char *flist;
-  char *clist;
-  char *blist;
+  char *d;
+  char *O;
+  struct arg_list *select[4]; // we treat them the same, so loop through
 
-  void *slist_head;
-  unsigned nelem;
-  void (*do_cut)(int fd);
+  int pairs;
+  regex_t reg;
 )
 
-struct slist {
-  struct slist *next;
-  int start, end;
-};
-
-static void add_to_list(int start, int end)
+// Apply selections to an input line, producing output
+static void cut_line(char **pline, long len)
 {
-  struct slist *current, *head_ref, *temp1_node;
+  unsigned *pairs = (void *)toybuf;
+  char *line = *pline;
+  int i, j;
 
-  head_ref = TT.slist_head;
-  temp1_node = xzalloc(sizeof(struct slist));
-  temp1_node->start = start;
-  temp1_node->end = end;
+  if (len && line[len-1]=='\n') line[--len] = 0;
 
-  /* Special case for the head end */
-  if (!head_ref || head_ref->start >= start) { 
-      temp1_node->next = head_ref; 
-      head_ref = temp1_node;
-  } else { 
-    /* Locate the node before the point of insertion */   
-    current = head_ref;   
-    while (current->next && current->next->start < temp1_node->start)
-        current = current->next;
-    temp1_node->next = current->next;   
-    current->next = temp1_node;
-  }
-  TT.slist_head = head_ref;
-}
+  // Loop through selections
+  for (i=0; i<TT.pairs; i++) {
+    unsigned start = pairs[2*i], end = pairs[(2*i)+1], count;
+    char *s = line, *ss;
 
-// parse list and add to slist.
-static void parse_list(char *list)
-{
-  for (;;) {
-    char *ctoken = strsep(&list, ","), *dtoken;
-    int start = 0, end = INT_MAX;
+    if (start) start--;
+    if (start>=len) continue;
+    if (!end || end>len) end = len;
+    count = end-start;
 
-    if (!ctoken) break;
-    if (!*ctoken) continue;
+    // Find start and end of output string for the relevant selection type
+    if (toys.optflags&FLAG_b) s += start;
+    else if (toys.optflags&FLAG_c) {
+      if (start) crunch_str(&s, start, 0, 0, 0);
+      if (!*s) continue;
+      start = s-line;
+      ss = s;
+      crunch_str(&ss, count, 0, 0, 0);
+      count = ss-s;
+    } else {
+      regmatch_t match;
 
-    // Get start position.
-    if (*(dtoken = strsep(&ctoken, "-"))) {
-      start = atolx_range(dtoken, 0, INT_MAX);
-      start = (start?(start-1):start);
-    }
-
-    // Get end position.
-    if (!ctoken) end = -1; //case e.g. 1,2,3
-    else if (*ctoken) {//case e.g. N-M
-      end = atolx_range(ctoken, 0, INT_MAX);
-      if (!end) end = INT_MAX;
-      end--;
-      if(end == start) end = -1;
-    }
-    add_to_list(start, end);
-    TT.nelem++;
-  }
-  // if list is missing in command line.
-  if (!TT.nelem) error_exit("missing positions list");
-}
-
-/*
- * retrive data from the file/s.
- */
-static void get_data(void)
-{
-  char **argv = toys.optargs; //file name.
-  toys.exitval = EXIT_SUCCESS;
-
-  if(!*argv) TT.do_cut(0); //for stdin
-  else {
-    for(; *argv; ++argv) {
-      if(strcmp(*argv, "-") == 0) TT.do_cut(0); //for stdin
-      else {
-        int fd = open(*argv, O_RDONLY, 0);
-        if (fd < 0) {//if file not present then continue with other files.
-          perror_msg_raw(*argv);
-          continue;
-        }
-        TT.do_cut(fd);
-        xclose(fd);
-      }
-    }
-  }
-}
-
-// perform cut operation on the given delimiter.
-static void do_fcut(int fd)
-{
-  char *buff, *pfield = 0, *delimiter = TT.delim;
-
-  for (;;) {
-    unsigned cpos = 0;
-    int start, ndelimiters = -1;
-    int  nprinted_fields = 0;
-    struct slist *temp_node = TT.slist_head;
-
-    free(pfield);
-    pfield = 0;
-
-    if (!(buff = get_line(fd))) break;
-
-    //does line have any delimiter?.
-    if (strrchr(buff, (int)delimiter[0]) == NULL) {
-      //if not then print whole line and move to next line.
-      if (!(toys.optflags & FLAG_s)) xputs(buff);
-      continue;
-    }
-
-    pfield = xzalloc(strlen(buff) + 1);
-
-    if (temp_node) {
-      //process list on each line.
-      while (cpos < TT.nelem && buff) {
-        if (!temp_node) break;
-        start = temp_node->start;
-        do {
-          char *field = 0;
-
-          //count number of delimeters per line.
-          while (buff) {
-            if (ndelimiters < start) {
-              ndelimiters++;
-              field = strsep(&buff, delimiter);
-            } else break;
-          }
-          //print field (if not yet printed).
-          if (!pfield[ndelimiters]) {
-            if (ndelimiters == start) {
-              //put delimiter.
-              if (nprinted_fields++ > 0) xputc(delimiter[0]);
-              if (field) fputs(field, stdout);
-              //make sure this field won't print again.
-              pfield[ndelimiters] = (char) 0x23; //put some char at this position.
-            }
-          }
-          start++;
-          if ((temp_node->end < 0) || !buff) break;          
-        } while(start <= temp_node->end);
-        temp_node = temp_node->next;
-        cpos++;
-      }
-    }
-    xputc('\n');
-  }
-}
-
-// perform cut operation char or byte.
-static void do_bccut(int fd)
-{
-  char *buff;
-
-  while ((buff = get_line(fd)) != NULL) {
-    unsigned cpos = 0;
-    int buffln = strlen(buff);
-    char *pfield = xzalloc(buffln + 1);
-    struct slist *temp_node = TT.slist_head;
-
-    if (temp_node != NULL) {
-      while (cpos < TT.nelem) {
-        int start;
-
-        if (!temp_node) break;
-        start = temp_node->start;
-        while (start < buffln) {
-          //to avoid duplicate field printing.
-          if (pfield[start]) {
-              if (++start <= temp_node->end) continue;
-              temp_node = temp_node->next;
-              break;
+      // Loop through skipping appropriate number of fields
+      for (j = 0; j<2; j++) {
+        ss = s;
+        if (j) start = count;
+        while (*ss && start) {
+          if (toys.optflags&FLAG_f) {
+            if (strchr(TT.d, *ss++)) start--;
           } else {
-            //make sure this field won't print again.
-            pfield[start] = (char) 0x23; //put some char at this position.
-            xputc(buff[start]);
-          }
-          if (++start > temp_node->end) {
-            temp_node = temp_node->next;
-            break;
+            if (regexec(&TT.reg, ss, 1, &match, REG_NOTBOL|REG_NOTEOL)) {
+              ss = line+len;
+              continue;
+            }
+            if (!match.rm_eo) return;
+            ss += (!--start && j) ? match.rm_so : match.rm_eo;
           }
         }
-        cpos++;
+        // did start run us off the end, so nothing to print?
+        if (!j) if (!*(s = ss)) break;
       }
-      xputc('\n');
+      if (!*s) continue;
+      count = ss-s;
     }
-    free(pfield);
-    pfield = NULL;
+    if (i && TT.O) fputs(TT.O, stdout);
+    fwrite(s, count, 1, stdout);
   }
+  xputc('\n');
+}
+
+static int compar(unsigned *a, unsigned *b)
+{
+  if (*a<*b) return -1;
+  if (*a>*b) return 1;
+  if (a[1]<b[1]) return -1;
+  if (a[1]>b[1]) return 1;
+
+  return 0;
+}
+
+// parse A or A-B or A- or -B
+static char *get_range(void *data, char *str, int len)
+{
+  char *end = str;
+  unsigned *pairs = (void *)toybuf, i;
+
+  // Using toybuf[] to store ranges means we can have 512 selections max.
+  if (TT.pairs == sizeof(toybuf)/sizeof(int)) perror_exit("select limit");
+  pairs += 2*TT.pairs++;
+
+  pairs[1] = UINT_MAX;
+  for (i = 0; ;i++) {
+    if (i==2) return end;
+    if (isdigit(*end)) {
+      long long ll = estrtol(end, &end, 10);
+
+      if (ll<1 || ll>UINT_MAX || errno) return end;
+      pairs[i] = ll;
+    }
+    if (*end++ != '-') break;
+  }
+  if (!i) pairs[1] = pairs[0];
+  if ((end-str)<len) return end;
+  if (pairs[0]>pairs[1]) return str;
+
+  // No error
+  return 0;
 }
 
 void cut_main(void)
 {
-  char delimiter = '\t'; //default delimiter.
-  char *list;
+  int i;
+  char buf[8];
 
-  TT.nelem = 0;
-  TT.slist_head = NULL;
-
-  //Get list and assign the function.
-  if (toys.optflags & FLAG_f) {
-    list = TT.flist;
-    TT.do_cut = do_fcut;
-  } else if (toys.optflags & FLAG_c) {
-    list = TT.clist;
-    TT.do_cut = do_bccut;
-  } else {
-    list = TT.blist;
-    TT.do_cut = do_bccut;
+  // Parse command line arguments
+  if ((toys.optflags&(FLAG_s|FLAG_f|FLAG_F))==FLAG_s)
+    error_exit("-s needs -Ff");
+  if ((toys.optflags&(FLAG_d|FLAG_f|FLAG_F))==FLAG_d)
+    error_exit("-d needs -Ff");
+  if (!TT.d) TT.d = (toys.optflags&FLAG_F) ? "[[:space:]][[:space:]]*" : "\t";
+  if (toys.optflags&FLAG_F) xregcomp(&TT.reg, TT.d, REG_EXTENDED);
+  if (!TT.O) {
+    if (toys.optflags&FLAG_F) TT.O = " ";
+    else if (toys.optflags&FLAG_f) TT.O = TT.d;
   }
 
-  if (toys.optflags & FLAG_d) {
-    //delimiter must be 1 char.
-    if(TT.delim[0] && TT.delim[1])
-      perror_exit("the delimiter must be a single character");
-    delimiter = TT.delim[0];
+  // Parse ranges, which are attached to a selection type (only one can be set)
+  for (i = 0; i<ARRAY_LEN(TT.select); i++) {
+    sprintf(buf, "bad -%c", "Ffcb"[i]);
+    if (TT.select[i]) comma_args(TT.select[i], 0, buf, get_range);
+  }
+  if (!TT.pairs) error_exit("no selections");
+
+  // Sort and collate selections
+  if (!(toys.optflags&FLAG_D)) {
+    int from, to;
+    unsigned *pairs = (void *)toybuf;
+
+    qsort(toybuf, TT.pairs, 8, (void *)compar);
+    for (to = 0, from = 2; from/2 < TT.pairs; from += 2) {
+      if (pairs[from] > pairs[to+1]) {
+        to += 2;
+        memcpy(pairs+to, pairs+from, 2*sizeof(unsigned));
+      } else if (pairs[from+1] > pairs[to+1]) pairs[to+1] = pairs[from+1];
+    }
+    TT.pairs = (to/2)+1;
   }
 
-  if(!(toys.optflags & FLAG_d) && (toys.optflags & FLAG_f)) {
-    TT.delim = xzalloc(2);
-    TT.delim[0] = delimiter;
-  }
-  
-  //when field is not specified, cutting has some special handling.
-  if (!(toys.optflags & FLAG_f)) {
-    if (toys.optflags & FLAG_s)
-      perror_exit("suppressing non-delimited lines operating on fields");
-    if (delimiter != '\t')
-      perror_exit("an input delimiter may be specified only when operating on fields");
-  }
-
-  parse_list(list);
-  get_data();
-  if (!(toys.optflags & FLAG_d) && (toys.optflags & FLAG_f)) {
-    free(TT.delim);
-    TT.delim = NULL;
-  }
-  llist_traverse(TT.slist_head, free);
+  // For each argument, loop through lines of file and call cut_line() on each
+  loopfiles_lines(toys.optargs, cut_line);
 }