summaryrefslogtreecommitdiffstats
path: root/builtins/mapfile.def
diff options
context:
space:
mode:
Diffstat (limited to 'builtins/mapfile.def')
-rw-r--r--builtins/mapfile.def364
1 files changed, 364 insertions, 0 deletions
diff --git a/builtins/mapfile.def b/builtins/mapfile.def
new file mode 100644
index 0000000..4ddd723
--- /dev/null
+++ b/builtins/mapfile.def
@@ -0,0 +1,364 @@
+This file is mapfile.def, from which is created mapfile.c.
+It implements the builtin "mapfile" in Bash.
+
+Copyright (C) 2005-2006 Rocky Bernstein for Free Software Foundation, Inc.
+Copyright (C) 2008-2021 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES mapfile.c
+
+$BUILTIN mapfile
+$FUNCTION mapfile_builtin
+$SHORT_DOC mapfile [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
+Read lines from the standard input into an indexed array variable.
+
+Read lines from the standard input into the indexed array variable ARRAY, or
+from file descriptor FD if the -u option is supplied. The variable MAPFILE
+is the default ARRAY.
+
+Options:
+ -d delim Use DELIM to terminate lines, instead of newline
+ -n count Copy at most COUNT lines. If COUNT is 0, all lines are copied
+ -O origin Begin assigning to ARRAY at index ORIGIN. The default index is 0
+ -s count Discard the first COUNT lines read
+ -t Remove a trailing DELIM from each line read (default newline)
+ -u fd Read lines from file descriptor FD instead of the standard input
+ -C callback Evaluate CALLBACK each time QUANTUM lines are read
+ -c quantum Specify the number of lines read between each call to
+ CALLBACK
+
+Arguments:
+ ARRAY Array variable name to use for file data
+
+If -C is supplied without -c, the default quantum is 5000. When
+CALLBACK is evaluated, it is supplied the index of the next array
+element to be assigned and the line to be assigned to that element
+as additional arguments.
+
+If not supplied with an explicit origin, mapfile will clear ARRAY before
+assigning to it.
+
+Exit Status:
+Returns success unless an invalid option is given or ARRAY is readonly or
+not an indexed array.
+$END
+
+$BUILTIN readarray
+$FUNCTION mapfile_builtin
+$SHORT_DOC readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
+Read lines from a file into an array variable.
+
+A synonym for `mapfile'.
+$END
+
+#include <config.h>
+
+#include "builtins.h"
+#include "../bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "../bashintl.h"
+#include "../shell.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#if defined (ARRAY_VARS)
+
+static int run_callback PARAMS((const char *, unsigned int, const char *));
+
+#define DEFAULT_ARRAY_NAME "MAPFILE"
+#define DEFAULT_VARIABLE_NAME "MAPLINE" /* not used right now */
+
+/* The value specifying how frequently `mapfile' calls the callback. */
+#define DEFAULT_QUANTUM 5000
+
+/* Values for FLAGS */
+#define MAPF_CLEARARRAY 0x01
+#define MAPF_CHOP 0x02
+
+static int delim;
+
+static int
+run_callback (callback, curindex, curline)
+ const char *callback;
+ unsigned int curindex;
+ const char *curline;
+{
+ unsigned int execlen;
+ char *execstr, *qline;
+ int flags;
+
+ qline = sh_single_quote (curline);
+ execlen = strlen (callback) + strlen (qline) + 10;
+ /* 1 for each space between %s and %d,
+ another 1 for the last nul char for C string. */
+ execlen += 3;
+ execstr = xmalloc (execlen);
+
+ flags = SEVAL_NOHIST;
+#if 0
+ if (interactive)
+ flags |= SEVAL_INTERACT;
+#endif
+ snprintf (execstr, execlen, "%s %d %s", callback, curindex, qline);
+ free (qline);
+ return evalstring (execstr, NULL, flags);
+}
+
+static void
+do_chop(line, delim)
+ char *line;
+ unsigned char delim;
+{
+ int length;
+
+ length = strlen (line);
+ if (length && (unsigned char)line[length-1] == delim)
+ line[length-1] = '\0';
+}
+
+static int
+mapfile (fd, line_count_goal, origin, nskip, callback_quantum, callback, array_name, delim, flags)
+ int fd;
+ long line_count_goal, origin, nskip, callback_quantum;
+ char *callback, *array_name;
+ int delim;
+ int flags;
+{
+ char *line;
+ size_t line_length;
+ unsigned int array_index, line_count;
+ SHELL_VAR *entry;
+ struct stat sb;
+ int unbuffered_read;
+
+ line = NULL;
+ line_length = 0;
+ unbuffered_read = 0;
+
+ /* The following check should be done before reading any lines. Doing it
+ here allows us to call bind_array_element instead of bind_array_variable
+ and skip the variable lookup on every call. */
+ entry = builtin_find_indexed_array (array_name, flags & MAPF_CLEARARRAY);
+ if (entry == 0)
+ return EXECUTION_FAILURE;
+
+#ifndef __CYGWIN__
+ /* If the delimiter is a newline, turn on unbuffered reads for pipes
+ (terminals are ok). If the delimiter is not a newline, unbuffered reads
+ for every file descriptor that's not a regular file. */
+ if (delim == '\n')
+ unbuffered_read = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE);
+ else
+ unbuffered_read = (fstat (fd, &sb) != 0) || (S_ISREG (sb.st_mode) == 0);
+#else
+ unbuffered_read = 1;
+#endif
+
+ zreset ();
+
+ /* Skip any lines at beginning of file? */
+ for (line_count = 0; line_count < nskip; line_count++)
+ if (zgetline (fd, &line, &line_length, delim, unbuffered_read) < 0)
+ break;
+
+ line = 0;
+ line_length = 0;
+
+ /* Reset the buffer for bash own stream */
+ for (array_index = origin, line_count = 1;
+ zgetline (fd, &line, &line_length, delim, unbuffered_read) != -1;
+ array_index++)
+ {
+ /* Remove trailing newlines? */
+ if (flags & MAPF_CHOP)
+ do_chop (line, delim);
+
+ /* Has a callback been registered and if so is it time to call it? */
+ if (callback && line_count && (line_count % callback_quantum) == 0)
+ {
+ /* Reset the buffer for bash own stream. */
+ if (unbuffered_read == 0)
+ zsyncfd (fd);
+
+ run_callback (callback, array_index, line);
+ }
+
+ /* XXX - bad things can happen if the callback modifies ENTRY, e.g.,
+ unsetting it or changing it to a non-indexed-array type. */
+ bind_array_element (entry, array_index, line, 0);
+
+ /* Have we exceeded # of lines to store? */
+ line_count++;
+ if (line_count_goal != 0 && line_count > line_count_goal)
+ break;
+ }
+
+ free (line);
+
+ if (unbuffered_read == 0)
+ zsyncfd (fd);
+
+ return EXECUTION_SUCCESS;
+}
+
+int
+mapfile_builtin (list)
+ WORD_LIST *list;
+{
+ int opt, code, fd, flags;
+ intmax_t intval;
+ long lines, origin, nskip, callback_quantum;
+ char *array_name, *callback;
+
+ fd = 0;
+ lines = origin = nskip = 0;
+ flags = MAPF_CLEARARRAY;
+ callback_quantum = DEFAULT_QUANTUM;
+ callback = 0;
+ delim = '\n';
+
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, "d:u:n:O:tC:c:s:")) != -1)
+ {
+ switch (opt)
+ {
+ case 'd':
+ delim = *list_optarg;
+ break;
+ case 'u':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval < 0 || intval != (int)intval)
+ {
+ builtin_error (_("%s: invalid file descriptor specification"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ fd = intval;
+
+ if (sh_validfd (fd) == 0)
+ {
+ builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno));
+ return (EXECUTION_FAILURE);
+ }
+ break;
+
+ case 'n':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval < 0 || intval != (unsigned)intval)
+ {
+ builtin_error (_("%s: invalid line count"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ lines = intval;
+ break;
+
+ case 'O':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval < 0 || intval != (unsigned)intval)
+ {
+ builtin_error (_("%s: invalid array origin"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ origin = intval;
+ flags &= ~MAPF_CLEARARRAY;
+ break;
+ case 't':
+ flags |= MAPF_CHOP;
+ break;
+ case 'C':
+ callback = list_optarg;
+ break;
+ case 'c':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval <= 0 || intval != (unsigned)intval)
+ {
+ builtin_error (_("%s: invalid callback quantum"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ callback_quantum = intval;
+ break;
+ case 's':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval < 0 || intval != (unsigned)intval)
+ {
+ builtin_error (_("%s: invalid line count"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ nskip = intval;
+ break;
+ CASE_HELPOPT;
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+ list = loptend;
+
+ if (list == 0)
+ array_name = DEFAULT_ARRAY_NAME;
+ else if (list->word == 0 || list->word->word == 0)
+ {
+ builtin_error ("internal error: getting variable name");
+ return (EXECUTION_FAILURE);
+ }
+ else if (list->word->word[0] == '\0')
+ {
+ builtin_error (_("empty array variable name"));
+ return (EX_USAGE);
+ }
+ else
+ array_name = list->word->word;
+
+ if (legal_identifier (array_name) == 0)
+ {
+ sh_invalidid (array_name);
+ return (EXECUTION_FAILURE);
+ }
+
+ return mapfile (fd, lines, origin, nskip, callback_quantum, callback, array_name, delim, flags);
+}
+
+#else
+
+int
+mapfile_builtin (list)
+ WORD_LIST *list;
+{
+ builtin_error (_("array variable support required"));
+ return (EXECUTION_FAILURE);
+}
+
+#endif /* ARRAY_VARS */