12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661 |
- /* histexpand.c -- history expansion. */
- /* Copyright (C) 1989-2010 Free Software Foundation, Inc.
- This file contains the GNU History Library (History), a set of
- routines for managing the text of previously typed lines.
- History 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.
- History 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 History. If not, see <http://www.gnu.org/licenses/>.
- */
- #define READLINE_LIBRARY
- #if defined (HAVE_CONFIG_H)
- # include <config.h>
- #endif
- #include <stdio.h>
- #if defined (HAVE_STDLIB_H)
- # include <stdlib.h>
- #else
- # include "ansi_stdlib.h"
- #endif /* HAVE_STDLIB_H */
- #if defined (HAVE_UNISTD_H)
- # ifndef _MINIX
- # include <sys/types.h>
- # endif
- # include <unistd.h>
- #endif
- #include "rlmbutil.h"
- #include "history.h"
- #include "histlib.h"
- #include "rlshell.h"
- #include "xmalloc.h"
- #define HISTORY_WORD_DELIMITERS " \t\n;&()|<>"
- #define HISTORY_QUOTE_CHARACTERS "\"'`"
- #define slashify_in_quotes "\\`\"$"
- typedef int _hist_search_func_t PARAMS((const char *, int));
- static char error_pointer;
- static char *subst_lhs;
- static char *subst_rhs;
- static int subst_lhs_len;
- static int subst_rhs_len;
- static char *get_history_word_specifier PARAMS((char *, char *, int *));
- static int history_tokenize_word PARAMS((const char *, int));
- static char **history_tokenize_internal PARAMS((const char *, int, int *));
- static char *history_substring PARAMS((const char *, int, int));
- static void freewords PARAMS((char **, int));
- static char *history_find_word PARAMS((char *, int));
- static char *quote_breaks PARAMS((char *));
- /* Variables exported by this file. */
- /* The character that represents the start of a history expansion
- request. This is usually `!'. */
- char history_expansion_char = '!';
- /* The character that invokes word substitution if found at the start of
- a line. This is usually `^'. */
- char history_subst_char = '^';
- /* During tokenization, if this character is seen as the first character
- of a word, then it, and all subsequent characters upto a newline are
- ignored. For a Bourne shell, this should be '#'. Bash special cases
- the interactive comment character to not be a comment delimiter. */
- char history_comment_char = '\0';
- /* The list of characters which inhibit the expansion of text if found
- immediately following history_expansion_char. */
- char *history_no_expand_chars = " \t\n\r=";
- /* If set to a non-zero value, single quotes inhibit history expansion.
- The default is 0. */
- int history_quotes_inhibit_expansion = 0;
- /* Used to split words by history_tokenize_internal. */
- char *history_word_delimiters = HISTORY_WORD_DELIMITERS;
- /* If set, this points to a function that is called to verify that a
- particular history expansion should be performed. */
- rl_linebuf_func_t *history_inhibit_expansion_function;
- /* **************************************************************** */
- /* */
- /* History Expansion */
- /* */
- /* **************************************************************** */
- /* Hairy history expansion on text, not tokens. This is of general
- use, and thus belongs in this library. */
- /* The last string searched for by a !?string? search. */
- static char *search_string;
- /* The last string matched by a !?string? search. */
- static char *search_match;
- /* Return the event specified at TEXT + OFFSET modifying OFFSET to
- point to after the event specifier. Just a pointer to the history
- line is returned; NULL is returned in the event of a bad specifier.
- You pass STRING with *INDEX equal to the history_expansion_char that
- begins this specification.
- DELIMITING_QUOTE is a character that is allowed to end the string
- specification for what to search for in addition to the normal
- characters `:', ` ', `\t', `\n', and sometimes `?'.
- So you might call this function like:
- line = get_history_event ("!echo:p", &index, 0); */
- char *
- get_history_event (string, caller_index, delimiting_quote)
- const char *string;
- int *caller_index;
- int delimiting_quote;
- {
- register int i;
- register char c;
- HIST_ENTRY *entry;
- int which, sign, local_index, substring_okay;
- _hist_search_func_t *search_func;
- char *temp;
- /* The event can be specified in a number of ways.
- !! the previous command
- !n command line N
- !-n current command-line minus N
- !str the most recent command starting with STR
- !?str[?]
- the most recent command containing STR
- All values N are determined via HISTORY_BASE. */
- i = *caller_index;
- if (string[i] != history_expansion_char)
- return ((char *)NULL);
- /* Move on to the specification. */
- i++;
- sign = 1;
- substring_okay = 0;
- #define RETURN_ENTRY(e, w) \
- return ((e = history_get (w)) ? e->line : (char *)NULL)
- /* Handle !! case. */
- if (string[i] == history_expansion_char)
- {
- i++;
- which = history_base + (history_length - 1);
- *caller_index = i;
- RETURN_ENTRY (entry, which);
- }
- /* Hack case of numeric line specification. */
- if (string[i] == '-')
- {
- sign = -1;
- i++;
- }
- if (_rl_digit_p (string[i]))
- {
- /* Get the extent of the digits and compute the value. */
- for (which = 0; _rl_digit_p (string[i]); i++)
- which = (which * 10) + _rl_digit_value (string[i]);
- *caller_index = i;
- if (sign < 0)
- which = (history_length + history_base) - which;
- RETURN_ENTRY (entry, which);
- }
- /* This must be something to search for. If the spec begins with
- a '?', then the string may be anywhere on the line. Otherwise,
- the string must be found at the start of a line. */
- if (string[i] == '?')
- {
- substring_okay++;
- i++;
- }
- /* Only a closing `?' or a newline delimit a substring search string. */
- for (local_index = i; c = string[i]; i++)
- {
- #if defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- int v;
- mbstate_t ps;
- memset (&ps, 0, sizeof (mbstate_t));
- /* These produce warnings because we're passing a const string to a
- function that takes a non-const string. */
- _rl_adjust_point ((char *)string, i, &ps);
- if ((v = _rl_get_char_len ((char *)string + i, &ps)) > 1)
- {
- i += v - 1;
- continue;
- }
- }
- #endif /* HANDLE_MULTIBYTE */
- if ((!substring_okay && (whitespace (c) || c == ':' ||
- (history_search_delimiter_chars && member (c, history_search_delimiter_chars)) ||
- string[i] == delimiting_quote)) ||
- string[i] == '\n' ||
- (substring_okay && string[i] == '?'))
- break;
- }
- which = i - local_index;
- temp = (char *)xmalloc (1 + which);
- if (which)
- strncpy (temp, string + local_index, which);
- temp[which] = '\0';
- if (substring_okay && string[i] == '?')
- i++;
- *caller_index = i;
- #define FAIL_SEARCH() \
- do { \
- history_offset = history_length; xfree (temp) ; return (char *)NULL; \
- } while (0)
- /* If there is no search string, try to use the previous search string,
- if one exists. If not, fail immediately. */
- if (*temp == '\0' && substring_okay)
- {
- if (search_string)
- {
- xfree (temp);
- temp = savestring (search_string);
- }
- else
- FAIL_SEARCH ();
- }
- search_func = substring_okay ? history_search : history_search_prefix;
- while (1)
- {
- local_index = (*search_func) (temp, -1);
- if (local_index < 0)
- FAIL_SEARCH ();
- if (local_index == 0 || substring_okay)
- {
- entry = current_history ();
- history_offset = history_length;
-
- /* If this was a substring search, then remember the
- string that we matched for word substitution. */
- if (substring_okay)
- {
- FREE (search_string);
- search_string = temp;
- FREE (search_match);
- search_match = history_find_word (entry->line, local_index);
- }
- else
- xfree (temp);
- return (entry->line);
- }
- if (history_offset)
- history_offset--;
- else
- FAIL_SEARCH ();
- }
- #undef FAIL_SEARCH
- #undef RETURN_ENTRY
- }
- /* Function for extracting single-quoted strings. Used for inhibiting
- history expansion within single quotes. */
- /* Extract the contents of STRING as if it is enclosed in single quotes.
- SINDEX, when passed in, is the offset of the character immediately
- following the opening single quote; on exit, SINDEX is left pointing
- to the closing single quote. FLAGS currently used to allow backslash
- to escape a single quote (e.g., for bash $'...'). */
- static void
- hist_string_extract_single_quoted (string, sindex, flags)
- char *string;
- int *sindex, flags;
- {
- register int i;
- for (i = *sindex; string[i] && string[i] != '\''; i++)
- {
- if ((flags & 1) && string[i] == '\\' && string[i+1])
- i++;
- }
- *sindex = i;
- }
- static char *
- quote_breaks (s)
- char *s;
- {
- register char *p, *r;
- char *ret;
- int len = 3;
- for (p = s; p && *p; p++, len++)
- {
- if (*p == '\'')
- len += 3;
- else if (whitespace (*p) || *p == '\n')
- len += 2;
- }
- r = ret = (char *)xmalloc (len);
- *r++ = '\'';
- for (p = s; p && *p; )
- {
- if (*p == '\'')
- {
- *r++ = '\'';
- *r++ = '\\';
- *r++ = '\'';
- *r++ = '\'';
- p++;
- }
- else if (whitespace (*p) || *p == '\n')
- {
- *r++ = '\'';
- *r++ = *p++;
- *r++ = '\'';
- }
- else
- *r++ = *p++;
- }
- *r++ = '\'';
- *r = '\0';
- return ret;
- }
- static char *
- hist_error(s, start, current, errtype)
- char *s;
- int start, current, errtype;
- {
- char *temp;
- const char *emsg;
- int ll, elen;
- ll = current - start;
- switch (errtype)
- {
- case EVENT_NOT_FOUND:
- emsg = "event not found";
- elen = 15;
- break;
- case BAD_WORD_SPEC:
- emsg = "bad word specifier";
- elen = 18;
- break;
- case SUBST_FAILED:
- emsg = "substitution failed";
- elen = 19;
- break;
- case BAD_MODIFIER:
- emsg = "unrecognized history modifier";
- elen = 29;
- break;
- case NO_PREV_SUBST:
- emsg = "no previous substitution";
- elen = 24;
- break;
- default:
- emsg = "unknown expansion error";
- elen = 23;
- break;
- }
- temp = (char *)xmalloc (ll + elen + 3);
- strncpy (temp, s + start, ll);
- temp[ll] = ':';
- temp[ll + 1] = ' ';
- strcpy (temp + ll + 2, emsg);
- return (temp);
- }
- /* Get a history substitution string from STR starting at *IPTR
- and return it. The length is returned in LENPTR.
- A backslash can quote the delimiter. If the string is the
- empty string, the previous pattern is used. If there is
- no previous pattern for the lhs, the last history search
- string is used.
- If IS_RHS is 1, we ignore empty strings and set the pattern
- to "" anyway. subst_lhs is not changed if the lhs is empty;
- subst_rhs is allowed to be set to the empty string. */
- static char *
- get_subst_pattern (str, iptr, delimiter, is_rhs, lenptr)
- char *str;
- int *iptr, delimiter, is_rhs, *lenptr;
- {
- register int si, i, j, k;
- char *s;
- #if defined (HANDLE_MULTIBYTE)
- mbstate_t ps;
- #endif
- s = (char *)NULL;
- i = *iptr;
- #if defined (HANDLE_MULTIBYTE)
- memset (&ps, 0, sizeof (mbstate_t));
- _rl_adjust_point (str, i, &ps);
- #endif
- for (si = i; str[si] && str[si] != delimiter; si++)
- #if defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- int v;
- if ((v = _rl_get_char_len (str + si, &ps)) > 1)
- si += v - 1;
- else if (str[si] == '\\' && str[si + 1] == delimiter)
- si++;
- }
- else
- #endif /* HANDLE_MULTIBYTE */
- if (str[si] == '\\' && str[si + 1] == delimiter)
- si++;
- if (si > i || is_rhs)
- {
- s = (char *)xmalloc (si - i + 1);
- for (j = 0, k = i; k < si; j++, k++)
- {
- /* Remove a backslash quoting the search string delimiter. */
- if (str[k] == '\\' && str[k + 1] == delimiter)
- k++;
- s[j] = str[k];
- }
- s[j] = '\0';
- if (lenptr)
- *lenptr = j;
- }
- i = si;
- if (str[i])
- i++;
- *iptr = i;
- return s;
- }
- static void
- postproc_subst_rhs ()
- {
- char *new;
- int i, j, new_size;
- new = (char *)xmalloc (new_size = subst_rhs_len + subst_lhs_len);
- for (i = j = 0; i < subst_rhs_len; i++)
- {
- if (subst_rhs[i] == '&')
- {
- if (j + subst_lhs_len >= new_size)
- new = (char *)xrealloc (new, (new_size = new_size * 2 + subst_lhs_len));
- strcpy (new + j, subst_lhs);
- j += subst_lhs_len;
- }
- else
- {
- /* a single backslash protects the `&' from lhs interpolation */
- if (subst_rhs[i] == '\\' && subst_rhs[i + 1] == '&')
- i++;
- if (j >= new_size)
- new = (char *)xrealloc (new, new_size *= 2);
- new[j++] = subst_rhs[i];
- }
- }
- new[j] = '\0';
- xfree (subst_rhs);
- subst_rhs = new;
- subst_rhs_len = j;
- }
- /* Expand the bulk of a history specifier starting at STRING[START].
- Returns 0 if everything is OK, -1 if an error occurred, and 1
- if the `p' modifier was supplied and the caller should just print
- the returned string. Returns the new index into string in
- *END_INDEX_PTR, and the expanded specifier in *RET_STRING. */
- static int
- history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
- char *string;
- int start, *end_index_ptr;
- char **ret_string;
- char *current_line; /* for !# */
- {
- int i, n, starting_index;
- int substitute_globally, subst_bywords, want_quotes, print_only;
- char *event, *temp, *result, *tstr, *t, c, *word_spec;
- int result_len;
- #if defined (HANDLE_MULTIBYTE)
- mbstate_t ps;
- memset (&ps, 0, sizeof (mbstate_t));
- #endif
- result = (char *)xmalloc (result_len = 128);
- i = start;
- /* If it is followed by something that starts a word specifier,
- then !! is implied as the event specifier. */
- if (member (string[i + 1], ":$*%^"))
- {
- char fake_s[3];
- int fake_i = 0;
- i++;
- fake_s[0] = fake_s[1] = history_expansion_char;
- fake_s[2] = '\0';
- event = get_history_event (fake_s, &fake_i, 0);
- }
- else if (string[i + 1] == '#')
- {
- i += 2;
- event = current_line;
- }
- else
- {
- int quoted_search_delimiter = 0;
- /* If the character before this `!' is a double or single
- quote, then this expansion takes place inside of the
- quoted string. If we have to search for some text ("!foo"),
- allow the delimiter to end the search string. */
- #if defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- int ch, l;
- l = _rl_find_prev_mbchar (string, i, MB_FIND_ANY);
- ch = string[l];
- /* XXX - original patch had i - 1 ??? If i == 0 it would fail. */
- if (i && (ch == '\'' || ch == '"'))
- quoted_search_delimiter = ch;
- }
- else
- #endif /* HANDLE_MULTIBYTE */
- if (i && (string[i - 1] == '\'' || string[i - 1] == '"'))
- quoted_search_delimiter = string[i - 1];
- event = get_history_event (string, &i, quoted_search_delimiter);
- }
-
- if (event == 0)
- {
- *ret_string = hist_error (string, start, i, EVENT_NOT_FOUND);
- xfree (result);
- return (-1);
- }
- /* If a word specifier is found, then do what that requires. */
- starting_index = i;
- word_spec = get_history_word_specifier (string, event, &i);
- /* There is no such thing as a `malformed word specifier'. However,
- it is possible for a specifier that has no match. In that case,
- we complain. */
- if (word_spec == (char *)&error_pointer)
- {
- *ret_string = hist_error (string, starting_index, i, BAD_WORD_SPEC);
- xfree (result);
- return (-1);
- }
- /* If no word specifier, than the thing of interest was the event. */
- temp = word_spec ? savestring (word_spec) : savestring (event);
- FREE (word_spec);
- /* Perhaps there are other modifiers involved. Do what they say. */
- want_quotes = substitute_globally = subst_bywords = print_only = 0;
- starting_index = i;
- while (string[i] == ':')
- {
- c = string[i + 1];
- if (c == 'g' || c == 'a')
- {
- substitute_globally = 1;
- i++;
- c = string[i + 1];
- }
- else if (c == 'G')
- {
- subst_bywords = 1;
- i++;
- c = string[i + 1];
- }
- switch (c)
- {
- default:
- *ret_string = hist_error (string, i+1, i+2, BAD_MODIFIER);
- xfree (result);
- xfree (temp);
- return -1;
- case 'q':
- want_quotes = 'q';
- break;
- case 'x':
- want_quotes = 'x';
- break;
- /* :p means make this the last executed line. So we
- return an error state after adding this line to the
- history. */
- case 'p':
- print_only++;
- break;
- /* :t discards all but the last part of the pathname. */
- case 't':
- tstr = strrchr (temp, '/');
- if (tstr)
- {
- tstr++;
- t = savestring (tstr);
- xfree (temp);
- temp = t;
- }
- break;
- /* :h discards the last part of a pathname. */
- case 'h':
- tstr = strrchr (temp, '/');
- if (tstr)
- *tstr = '\0';
- break;
- /* :r discards the suffix. */
- case 'r':
- tstr = strrchr (temp, '.');
- if (tstr)
- *tstr = '\0';
- break;
- /* :e discards everything but the suffix. */
- case 'e':
- tstr = strrchr (temp, '.');
- if (tstr)
- {
- t = savestring (tstr);
- xfree (temp);
- temp = t;
- }
- break;
- /* :s/this/that substitutes `that' for the first
- occurrence of `this'. :gs/this/that substitutes `that'
- for each occurrence of `this'. :& repeats the last
- substitution. :g& repeats the last substitution
- globally. */
- case '&':
- case 's':
- {
- char *new_event;
- int delimiter, failed, si, l_temp, ws, we;
- if (c == 's')
- {
- if (i + 2 < (int)strlen (string))
- {
- #if defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- _rl_adjust_point (string, i + 2, &ps);
- if (_rl_get_char_len (string + i + 2, &ps) > 1)
- delimiter = 0;
- else
- delimiter = string[i + 2];
- }
- else
- #endif /* HANDLE_MULTIBYTE */
- delimiter = string[i + 2];
- }
- else
- break; /* no search delimiter */
- i += 3;
- t = get_subst_pattern (string, &i, delimiter, 0, &subst_lhs_len);
- /* An empty substitution lhs with no previous substitution
- uses the last search string as the lhs. */
- if (t)
- {
- FREE (subst_lhs);
- subst_lhs = t;
- }
- else if (!subst_lhs)
- {
- if (search_string && *search_string)
- {
- subst_lhs = savestring (search_string);
- subst_lhs_len = strlen (subst_lhs);
- }
- else
- {
- subst_lhs = (char *) NULL;
- subst_lhs_len = 0;
- }
- }
- FREE (subst_rhs);
- subst_rhs = get_subst_pattern (string, &i, delimiter, 1, &subst_rhs_len);
- /* If `&' appears in the rhs, it's supposed to be replaced
- with the lhs. */
- if (member ('&', subst_rhs))
- postproc_subst_rhs ();
- }
- else
- i += 2;
- /* If there is no lhs, the substitution can't succeed. */
- if (subst_lhs_len == 0)
- {
- *ret_string = hist_error (string, starting_index, i, NO_PREV_SUBST);
- xfree (result);
- xfree (temp);
- return -1;
- }
- l_temp = strlen (temp);
- /* Ignore impossible cases. */
- if (subst_lhs_len > l_temp)
- {
- *ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
- xfree (result);
- xfree (temp);
- return (-1);
- }
- /* Find the first occurrence of THIS in TEMP. */
- /* Substitute SUBST_RHS for SUBST_LHS in TEMP. There are three
- cases to consider:
- 1. substitute_globally == subst_bywords == 0
- 2. substitute_globally == 1 && subst_bywords == 0
- 3. substitute_globally == 0 && subst_bywords == 1
- In the first case, we substitute for the first occurrence only.
- In the second case, we substitute for every occurrence.
- In the third case, we tokenize into words and substitute the
- first occurrence of each word. */
- si = we = 0;
- for (failed = 1; (si + subst_lhs_len) <= l_temp; si++)
- {
- /* First skip whitespace and find word boundaries if
- we're past the end of the word boundary we found
- the last time. */
- if (subst_bywords && si > we)
- {
- for (; temp[si] && whitespace (temp[si]); si++)
- ;
- ws = si;
- we = history_tokenize_word (temp, si);
- }
- if (STREQN (temp+si, subst_lhs, subst_lhs_len))
- {
- int len = subst_rhs_len - subst_lhs_len + l_temp;
- new_event = (char *)xmalloc (1 + len);
- strncpy (new_event, temp, si);
- strncpy (new_event + si, subst_rhs, subst_rhs_len);
- strncpy (new_event + si + subst_rhs_len,
- temp + si + subst_lhs_len,
- l_temp - (si + subst_lhs_len));
- new_event[len] = '\0';
- xfree (temp);
- temp = new_event;
- failed = 0;
- if (substitute_globally)
- {
- /* Reported to fix a bug that causes it to skip every
- other match when matching a single character. Was
- si += subst_rhs_len previously. */
- si += subst_rhs_len - 1;
- l_temp = strlen (temp);
- substitute_globally++;
- continue;
- }
- else if (subst_bywords)
- {
- si = we;
- l_temp = strlen (temp);
- continue;
- }
- else
- break;
- }
- }
- if (substitute_globally > 1)
- {
- substitute_globally = 0;
- continue; /* don't want to increment i */
- }
- if (failed == 0)
- continue; /* don't want to increment i */
- *ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
- xfree (result);
- xfree (temp);
- return (-1);
- }
- }
- i += 2;
- }
- /* Done with modfiers. */
- /* Believe it or not, we have to back the pointer up by one. */
- --i;
- if (want_quotes)
- {
- char *x;
- if (want_quotes == 'q')
- x = sh_single_quote (temp);
- else if (want_quotes == 'x')
- x = quote_breaks (temp);
- else
- x = savestring (temp);
- xfree (temp);
- temp = x;
- }
- n = strlen (temp);
- if (n >= result_len)
- result = (char *)xrealloc (result, n + 2);
- strcpy (result, temp);
- xfree (temp);
- *end_index_ptr = i;
- *ret_string = result;
- return (print_only);
- }
- /* Expand the string STRING, placing the result into OUTPUT, a pointer
- to a string. Returns:
- -1) If there was an error in expansion.
- 0) If no expansions took place (or, if the only change in
- the text was the de-slashifying of the history expansion
- character)
- 1) If expansions did take place
- 2) If the `p' modifier was given and the caller should print the result
- If an error ocurred in expansion, then OUTPUT contains a descriptive
- error message. */
- #define ADD_STRING(s) \
- do \
- { \
- int sl = strlen (s); \
- j += sl; \
- if (j >= result_len) \
- { \
- while (j >= result_len) \
- result_len += 128; \
- result = (char *)xrealloc (result, result_len); \
- } \
- strcpy (result + j - sl, s); \
- } \
- while (0)
- #define ADD_CHAR(c) \
- do \
- { \
- if (j >= result_len - 1) \
- result = (char *)xrealloc (result, result_len += 64); \
- result[j++] = c; \
- result[j] = '\0'; \
- } \
- while (0)
- int
- history_expand (hstring, output)
- char *hstring;
- char **output;
- {
- register int j;
- int i, r, l, passc, cc, modified, eindex, only_printing, dquote, flag;
- char *string;
- /* The output string, and its length. */
- int result_len;
- char *result;
- #if defined (HANDLE_MULTIBYTE)
- char mb[MB_LEN_MAX];
- mbstate_t ps;
- #endif
- /* Used when adding the string. */
- char *temp;
- if (output == 0)
- return 0;
- /* Setting the history expansion character to 0 inhibits all
- history expansion. */
- if (history_expansion_char == 0)
- {
- *output = savestring (hstring);
- return (0);
- }
-
- /* Prepare the buffer for printing error messages. */
- result = (char *)xmalloc (result_len = 256);
- result[0] = '\0';
- only_printing = modified = 0;
- l = strlen (hstring);
- /* Grovel the string. Only backslash and single quotes can quote the
- history escape character. We also handle arg specifiers. */
- /* Before we grovel forever, see if the history_expansion_char appears
- anywhere within the text. */
- /* The quick substitution character is a history expansion all right. That
- is to say, "^this^that^" is equivalent to "!!:s^this^that^", and in fact,
- that is the substitution that we do. */
- if (hstring[0] == history_subst_char)
- {
- string = (char *)xmalloc (l + 5);
- string[0] = string[1] = history_expansion_char;
- string[2] = ':';
- string[3] = 's';
- strcpy (string + 4, hstring);
- l += 4;
- }
- else
- {
- #if defined (HANDLE_MULTIBYTE)
- memset (&ps, 0, sizeof (mbstate_t));
- #endif
- string = hstring;
- /* If not quick substitution, still maybe have to do expansion. */
- /* `!' followed by one of the characters in history_no_expand_chars
- is NOT an expansion. */
- for (i = dquote = 0; string[i]; i++)
- {
- #if defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- int v;
- v = _rl_get_char_len (string + i, &ps);
- if (v > 1)
- {
- i += v - 1;
- continue;
- }
- }
- #endif /* HANDLE_MULTIBYTE */
- cc = string[i + 1];
- /* The history_comment_char, if set, appearing at the beginning
- of a word signifies that the rest of the line should not have
- history expansion performed on it.
- Skip the rest of the line and break out of the loop. */
- if (history_comment_char && string[i] == history_comment_char &&
- (i == 0 || member (string[i - 1], history_word_delimiters)))
- {
- while (string[i])
- i++;
- break;
- }
- else if (string[i] == history_expansion_char)
- {
- if (cc == 0 || member (cc, history_no_expand_chars))
- continue;
- /* If the calling application has set
- history_inhibit_expansion_function to a function that checks
- for special cases that should not be history expanded,
- call the function and skip the expansion if it returns a
- non-zero value. */
- else if (history_inhibit_expansion_function &&
- (*history_inhibit_expansion_function) (string, i))
- continue;
- else
- break;
- }
- /* Shell-like quoting: allow backslashes to quote double quotes
- inside a double-quoted string. */
- else if (dquote && string[i] == '\\' && cc == '"')
- i++;
- /* More shell-like quoting: if we're paying attention to single
- quotes and letting them quote the history expansion character,
- then we need to pay attention to double quotes, because single
- quotes are not special inside double-quoted strings. */
- else if (history_quotes_inhibit_expansion && string[i] == '"')
- {
- dquote = 1 - dquote;
- }
- else if (dquote == 0 && history_quotes_inhibit_expansion && string[i] == '\'')
- {
- /* If this is bash, single quotes inhibit history expansion. */
- flag = (i > 0 && string[i - 1] == '$');
- i++;
- hist_string_extract_single_quoted (string, &i, flag);
- }
- else if (history_quotes_inhibit_expansion && string[i] == '\\')
- {
- /* If this is bash, allow backslashes to quote single
- quotes and the history expansion character. */
- if (cc == '\'' || cc == history_expansion_char)
- i++;
- }
-
- }
-
- if (string[i] != history_expansion_char)
- {
- xfree (result);
- *output = savestring (string);
- return (0);
- }
- }
- /* Extract and perform the substitution. */
- for (passc = dquote = i = j = 0; i < l; i++)
- {
- int tchar = string[i];
- if (passc)
- {
- passc = 0;
- ADD_CHAR (tchar);
- continue;
- }
- #if defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- int k, c;
- c = tchar;
- memset (mb, 0, sizeof (mb));
- for (k = 0; k < MB_LEN_MAX; k++)
- {
- mb[k] = (char)c;
- memset (&ps, 0, sizeof (mbstate_t));
- if (_rl_get_char_len (mb, &ps) == -2)
- c = string[++i];
- else
- break;
- }
- if (strlen (mb) > 1)
- {
- ADD_STRING (mb);
- continue;
- }
- }
- #endif /* HANDLE_MULTIBYTE */
- if (tchar == history_expansion_char)
- tchar = -3;
- else if (tchar == history_comment_char)
- tchar = -2;
- switch (tchar)
- {
- default:
- ADD_CHAR (string[i]);
- break;
- case '\\':
- passc++;
- ADD_CHAR (tchar);
- break;
- case '"':
- dquote = 1 - dquote;
- ADD_CHAR (tchar);
- break;
-
- case '\'':
- {
- /* If history_quotes_inhibit_expansion is set, single quotes
- inhibit history expansion. */
- if (dquote == 0 && history_quotes_inhibit_expansion)
- {
- int quote, slen;
- flag = (i > 0 && string[i - 1] == '$');
- quote = i++;
- hist_string_extract_single_quoted (string, &i, flag);
- slen = i - quote + 2;
- temp = (char *)xmalloc (slen);
- strncpy (temp, string + quote, slen);
- temp[slen - 1] = '\0';
- ADD_STRING (temp);
- xfree (temp);
- }
- else
- ADD_CHAR (string[i]);
- break;
- }
- case -2: /* history_comment_char */
- if (i == 0 || member (string[i - 1], history_word_delimiters))
- {
- temp = (char *)xmalloc (l - i + 1);
- strcpy (temp, string + i);
- ADD_STRING (temp);
- xfree (temp);
- i = l;
- }
- else
- ADD_CHAR (string[i]);
- break;
- case -3: /* history_expansion_char */
- cc = string[i + 1];
- /* If the history_expansion_char is followed by one of the
- characters in history_no_expand_chars, then it is not a
- candidate for expansion of any kind. */
- if (cc == 0 || member (cc, history_no_expand_chars) ||
- (history_inhibit_expansion_function && (*history_inhibit_expansion_function) (string, i)))
- {
- ADD_CHAR (string[i]);
- break;
- }
- #if defined (NO_BANG_HASH_MODIFIERS)
- /* There is something that is listed as a `word specifier' in csh
- documentation which means `the expanded text to this point'.
- That is not a word specifier, it is an event specifier. If we
- don't want to allow modifiers with `!#', just stick the current
- output line in again. */
- if (cc == '#')
- {
- if (result)
- {
- temp = (char *)xmalloc (1 + strlen (result));
- strcpy (temp, result);
- ADD_STRING (temp);
- xfree (temp);
- }
- i++;
- break;
- }
- #endif
- r = history_expand_internal (string, i, &eindex, &temp, result);
- if (r < 0)
- {
- *output = temp;
- xfree (result);
- if (string != hstring)
- xfree (string);
- return -1;
- }
- else
- {
- if (temp)
- {
- modified++;
- if (*temp)
- ADD_STRING (temp);
- xfree (temp);
- }
- only_printing = r == 1;
- i = eindex;
- }
- break;
- }
- }
- *output = result;
- if (string != hstring)
- xfree (string);
- if (only_printing)
- {
- #if 0
- add_history (result);
- #endif
- return (2);
- }
- return (modified != 0);
- }
- /* Return a consed string which is the word specified in SPEC, and found
- in FROM. NULL is returned if there is no spec. The address of
- ERROR_POINTER is returned if the word specified cannot be found.
- CALLER_INDEX is the offset in SPEC to start looking; it is updated
- to point to just after the last character parsed. */
- static char *
- get_history_word_specifier (spec, from, caller_index)
- char *spec, *from;
- int *caller_index;
- {
- register int i = *caller_index;
- int first, last;
- int expecting_word_spec = 0;
- char *result;
- /* The range of words to return doesn't exist yet. */
- first = last = 0;
- result = (char *)NULL;
- /* If we found a colon, then this *must* be a word specification. If
- it isn't, then it is an error. */
- if (spec[i] == ':')
- {
- i++;
- expecting_word_spec++;
- }
- /* Handle special cases first. */
- /* `%' is the word last searched for. */
- if (spec[i] == '%')
- {
- *caller_index = i + 1;
- return (search_match ? savestring (search_match) : savestring (""));
- }
- /* `*' matches all of the arguments, but not the command. */
- if (spec[i] == '*')
- {
- *caller_index = i + 1;
- result = history_arg_extract (1, '$', from);
- return (result ? result : savestring (""));
- }
- /* `$' is last arg. */
- if (spec[i] == '$')
- {
- *caller_index = i + 1;
- return (history_arg_extract ('$', '$', from));
- }
- /* Try to get FIRST and LAST figured out. */
- if (spec[i] == '-')
- first = 0;
- else if (spec[i] == '^')
- {
- first = 1;
- i++;
- }
- else if (_rl_digit_p (spec[i]) && expecting_word_spec)
- {
- for (first = 0; _rl_digit_p (spec[i]); i++)
- first = (first * 10) + _rl_digit_value (spec[i]);
- }
- else
- return ((char *)NULL); /* no valid `first' for word specifier */
- if (spec[i] == '^' || spec[i] == '*')
- {
- last = (spec[i] == '^') ? 1 : '$'; /* x* abbreviates x-$ */
- i++;
- }
- else if (spec[i] != '-')
- last = first;
- else
- {
- i++;
- if (_rl_digit_p (spec[i]))
- {
- for (last = 0; _rl_digit_p (spec[i]); i++)
- last = (last * 10) + _rl_digit_value (spec[i]);
- }
- else if (spec[i] == '$')
- {
- i++;
- last = '$';
- }
- #if 0
- else if (!spec[i] || spec[i] == ':')
- /* check against `:' because there could be a modifier separator */
- #else
- else
- /* csh seems to allow anything to terminate the word spec here,
- leaving it as an abbreviation. */
- #endif
- last = -1; /* x- abbreviates x-$ omitting word `$' */
- }
- *caller_index = i;
- if (last >= first || last == '$' || last < 0)
- result = history_arg_extract (first, last, from);
- return (result ? result : (char *)&error_pointer);
- }
- /* Extract the args specified, starting at FIRST, and ending at LAST.
- The args are taken from STRING. If either FIRST or LAST is < 0,
- then make that arg count from the right (subtract from the number of
- tokens, so that FIRST = -1 means the next to last token on the line).
- If LAST is `$' the last arg from STRING is used. */
- char *
- history_arg_extract (first, last, string)
- int first, last;
- const char *string;
- {
- register int i, len;
- char *result;
- int size, offset;
- char **list;
- /* XXX - think about making history_tokenize return a struct array,
- each struct in array being a string and a length to avoid the
- calls to strlen below. */
- if ((list = history_tokenize (string)) == NULL)
- return ((char *)NULL);
- for (len = 0; list[len]; len++)
- ;
- if (last < 0)
- last = len + last - 1;
- if (first < 0)
- first = len + first - 1;
- if (last == '$')
- last = len - 1;
- if (first == '$')
- first = len - 1;
- last++;
- if (first >= len || last > len || first < 0 || last < 0 || first > last)
- result = ((char *)NULL);
- else
- {
- for (size = 0, i = first; i < last; i++)
- size += strlen (list[i]) + 1;
- result = (char *)xmalloc (size + 1);
- result[0] = '\0';
- for (i = first, offset = 0; i < last; i++)
- {
- strcpy (result + offset, list[i]);
- offset += strlen (list[i]);
- if (i + 1 < last)
- {
- result[offset++] = ' ';
- result[offset] = 0;
- }
- }
- }
- for (i = 0; i < len; i++)
- xfree (list[i]);
- xfree (list);
- return (result);
- }
- static int
- history_tokenize_word (string, ind)
- const char *string;
- int ind;
- {
- register int i;
- int delimiter, nestdelim, delimopen;
- i = ind;
- delimiter = nestdelim = 0;
- if (member (string[i], "()\n"))
- {
- i++;
- return i;
- }
- if (member (string[i], "<>;&|$"))
- {
- int peek = string[i + 1];
- if (peek == string[i] && peek != '$')
- {
- if (peek == '<' && string[i + 2] == '-')
- i++;
- else if (peek == '<' && string[i + 2] == '<')
- i++;
- i += 2;
- return i;
- }
- else if ((peek == '&' && (string[i] == '>' || string[i] == '<')) ||
- (peek == '>' && string[i] == '&'))
- {
- i += 2;
- return i;
- }
- /* XXX - separated out for later -- bash-4.2 */
- else if ((peek == '(' && (string[i] == '>' || string[i] == '<')) || /* ) */
- (peek == '(' && string[i] == '$')) /*)*/
- {
- i += 2;
- delimopen = '(';
- delimiter = ')';
- nestdelim = 1;
- goto get_word;
- }
- #if 0
- else if (peek == '\'' && string[i] == '$')
- {
- i += 2; /* XXX */
- return i;
- }
- #endif
- if (string[i] != '$')
- {
- i++;
- return i;
- }
- }
- /* same code also used for $(...)/<(...)/>(...) above */
- if (member (string[i], "!@?+*"))
- {
- int peek = string[i + 1];
- if (peek == '(') /*)*/
- {
- /* Shell extended globbing patterns */
- i += 2;
- delimopen = '(';
- delimiter = ')'; /* XXX - not perfect */
- nestdelim = 1;
- }
- }
- get_word:
- /* Get word from string + i; */
- if (delimiter == 0 && member (string[i], HISTORY_QUOTE_CHARACTERS))
- delimiter = string[i++];
- for (; string[i]; i++)
- {
- if (string[i] == '\\' && string[i + 1] == '\n')
- {
- i++;
- continue;
- }
- if (string[i] == '\\' && delimiter != '\'' &&
- (delimiter != '"' || member (string[i], slashify_in_quotes)))
- {
- i++;
- continue;
- }
- /* delimiter must be set and set to something other than a quote if
- nestdelim is set, so these tests are safe. */
- if (nestdelim && string[i] == delimopen)
- {
- nestdelim++;
- continue;
- }
- if (nestdelim && string[i] == delimiter)
- {
- nestdelim--;
- if (nestdelim == 0)
- delimiter = 0;
- continue;
- }
-
- if (delimiter && string[i] == delimiter)
- {
- delimiter = 0;
- continue;
- }
- if (delimiter == 0 && (member (string[i], history_word_delimiters)))
- break;
- if (delimiter == 0 && member (string[i], HISTORY_QUOTE_CHARACTERS))
- delimiter = string[i];
- }
- return i;
- }
- static char *
- history_substring (string, start, end)
- const char *string;
- int start, end;
- {
- register int len;
- register char *result;
- len = end - start;
- result = (char *)xmalloc (len + 1);
- strncpy (result, string + start, len);
- result[len] = '\0';
- return result;
- }
- /* Parse STRING into tokens and return an array of strings. If WIND is
- not -1 and INDP is not null, we also want the word surrounding index
- WIND. The position in the returned array of strings is returned in
- *INDP. */
- static char **
- history_tokenize_internal (string, wind, indp)
- const char *string;
- int wind, *indp;
- {
- char **result;
- register int i, start, result_index, size;
- /* If we're searching for a string that's not part of a word (e.g., " "),
- make sure we set *INDP to a reasonable value. */
- if (indp && wind != -1)
- *indp = -1;
- /* Get a token, and stuff it into RESULT. The tokens are split
- exactly where the shell would split them. */
- for (i = result_index = size = 0, result = (char **)NULL; string[i]; )
- {
- /* Skip leading whitespace. */
- for (; string[i] && whitespace (string[i]); i++)
- ;
- if (string[i] == 0 || string[i] == history_comment_char)
- return (result);
- start = i;
- i = history_tokenize_word (string, start);
- /* If we have a non-whitespace delimiter character (which would not be
- skipped by the loop above), use it and any adjacent delimiters to
- make a separate field. Any adjacent white space will be skipped the
- next time through the loop. */
- if (i == start && history_word_delimiters)
- {
- i++;
- while (string[i] && member (string[i], history_word_delimiters))
- i++;
- }
- /* If we are looking for the word in which the character at a
- particular index falls, remember it. */
- if (indp && wind != -1 && wind >= start && wind < i)
- *indp = result_index;
- if (result_index + 2 >= size)
- result = (char **)xrealloc (result, ((size += 10) * sizeof (char *)));
- result[result_index++] = history_substring (string, start, i);
- result[result_index] = (char *)NULL;
- }
- return (result);
- }
- /* Return an array of tokens, much as the shell might. The tokens are
- parsed out of STRING. */
- char **
- history_tokenize (string)
- const char *string;
- {
- return (history_tokenize_internal (string, -1, (int *)NULL));
- }
- /* Free members of WORDS from START to an empty string */
- static void
- freewords (words, start)
- char **words;
- int start;
- {
- register int i;
- for (i = start; words[i]; i++)
- xfree (words[i]);
- }
- /* Find and return the word which contains the character at index IND
- in the history line LINE. Used to save the word matched by the
- last history !?string? search. */
- static char *
- history_find_word (line, ind)
- char *line;
- int ind;
- {
- char **words, *s;
- int i, wind;
- words = history_tokenize_internal (line, ind, &wind);
- if (wind == -1 || words == 0)
- {
- if (words)
- freewords (words, 0);
- FREE (words);
- return ((char *)NULL);
- }
- s = words[wind];
- for (i = 0; i < wind; i++)
- xfree (words[i]);
- freewords (words, wind + 1);
- xfree (words);
- return s;
- }
|