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);
}