1da2e3ebdSchin /*********************************************************************** 2da2e3ebdSchin * * 3da2e3ebdSchin * This software is part of the ast package * 4*3e14f97fSRoger A. Faulkner * Copyright (c) 1982-2010 AT&T Intellectual Property * 5da2e3ebdSchin * and is licensed under the * 6da2e3ebdSchin * Common Public License, Version 1.0 * 77c2fbfb3SApril Chin * by AT&T Intellectual Property * 8da2e3ebdSchin * * 9da2e3ebdSchin * A copy of the License is available at * 10da2e3ebdSchin * http://www.opensource.org/licenses/cpl1.0.txt * 11da2e3ebdSchin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12da2e3ebdSchin * * 13da2e3ebdSchin * Information and Software Systems Research * 14da2e3ebdSchin * AT&T Research * 15da2e3ebdSchin * Florham Park NJ * 16da2e3ebdSchin * * 17da2e3ebdSchin * David Korn <dgk@research.att.com> * 18da2e3ebdSchin * * 19da2e3ebdSchin ***********************************************************************/ 20da2e3ebdSchin #pragma prototyped 21da2e3ebdSchin /* 22da2e3ebdSchin * bash style history expansion 23da2e3ebdSchin * 24da2e3ebdSchin * Author: 25da2e3ebdSchin * Karsten Fleischer 26da2e3ebdSchin * Omnium Software Engineering 27da2e3ebdSchin * An der Luisenburg 7 28da2e3ebdSchin * D-51379 Leverkusen 29da2e3ebdSchin * Germany 30da2e3ebdSchin * 31da2e3ebdSchin * <K.Fleischer@omnium.de> 32da2e3ebdSchin */ 33da2e3ebdSchin 34da2e3ebdSchin 35da2e3ebdSchin #include "defs.h" 36da2e3ebdSchin #include "edit.h" 37da2e3ebdSchin 38da2e3ebdSchin #if ! SHOPT_HISTEXPAND 39da2e3ebdSchin 40da2e3ebdSchin NoN(hexpand) 41da2e3ebdSchin 42da2e3ebdSchin #else 43da2e3ebdSchin 44da2e3ebdSchin static char *modifiers = "htrepqxs&"; 45da2e3ebdSchin static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 }; 46da2e3ebdSchin 47da2e3ebdSchin #define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;} 48da2e3ebdSchin 49da2e3ebdSchin struct subst 50da2e3ebdSchin { 51da2e3ebdSchin char *str[2]; /* [0] is "old", [1] is "new" string */ 52da2e3ebdSchin }; 53da2e3ebdSchin 54da2e3ebdSchin 55da2e3ebdSchin /* 56da2e3ebdSchin * parse an /old/new/ string, delimiter expected as first char. 57da2e3ebdSchin * if "old" not specified, keep sb->str[0] 58da2e3ebdSchin * if "new" not specified, set sb->str[1] to empty string 59da2e3ebdSchin * read up to third delimeter char, \n or \0, whichever comes first. 60da2e3ebdSchin * return adress is one past the last valid char in s: 61da2e3ebdSchin * - the address containing \n or \0 or 62da2e3ebdSchin * - one char beyond the third delimiter 63da2e3ebdSchin */ 64da2e3ebdSchin 65da2e3ebdSchin static char *parse_subst(const char *s, struct subst *sb) 66da2e3ebdSchin { 67da2e3ebdSchin char *cp,del; 68da2e3ebdSchin int off,n = 0; 69da2e3ebdSchin 70da2e3ebdSchin /* build the strings on the stack, mainly for '&' substition in "new" */ 71da2e3ebdSchin off = staktell(); 72da2e3ebdSchin 73da2e3ebdSchin /* init "new" with empty string */ 74da2e3ebdSchin if(sb->str[1]) 75da2e3ebdSchin free(sb->str[1]); 76da2e3ebdSchin sb->str[1] = strdup(""); 77da2e3ebdSchin 78da2e3ebdSchin /* get delimiter */ 79da2e3ebdSchin del = *s; 80da2e3ebdSchin 81da2e3ebdSchin cp = (char*) s + 1; 82da2e3ebdSchin 83da2e3ebdSchin while(n < 2) 84da2e3ebdSchin { 85da2e3ebdSchin if(*cp == del || *cp == '\n' || *cp == '\0') 86da2e3ebdSchin { 87da2e3ebdSchin /* delimiter or EOL */ 88da2e3ebdSchin if(staktell() != off) 89da2e3ebdSchin { 90da2e3ebdSchin /* dupe string on stack and rewind stack */ 91da2e3ebdSchin stakputc('\0'); 92da2e3ebdSchin if(sb->str[n]) 93da2e3ebdSchin free(sb->str[n]); 94da2e3ebdSchin sb->str[n] = strdup(stakptr(off)); 95da2e3ebdSchin stakseek(off); 96da2e3ebdSchin } 97da2e3ebdSchin n++; 98da2e3ebdSchin 99da2e3ebdSchin /* if not delimiter, we've reached EOL. Get outta here. */ 100da2e3ebdSchin if(*cp != del) 101da2e3ebdSchin break; 102da2e3ebdSchin } 103da2e3ebdSchin else if(*cp == '\\') 104da2e3ebdSchin { 105da2e3ebdSchin if(*(cp+1) == del) /* quote delimiter */ 106da2e3ebdSchin { 107da2e3ebdSchin stakputc(del); 108da2e3ebdSchin cp++; 109da2e3ebdSchin } 110da2e3ebdSchin else if(*(cp+1) == '&' && n == 1) 111da2e3ebdSchin { /* quote '&' only in "new" */ 112da2e3ebdSchin stakputc('&'); 113da2e3ebdSchin cp++; 114da2e3ebdSchin } 115da2e3ebdSchin else 116da2e3ebdSchin stakputc('\\'); 117da2e3ebdSchin } 118da2e3ebdSchin else if(*cp == '&' && n == 1 && sb->str[0]) 119da2e3ebdSchin /* substitute '&' with "old" in "new" */ 120da2e3ebdSchin stakputs(sb->str[0]); 121da2e3ebdSchin else 122da2e3ebdSchin stakputc(*cp); 123da2e3ebdSchin cp++; 124da2e3ebdSchin } 125da2e3ebdSchin 126da2e3ebdSchin /* rewind stack */ 127da2e3ebdSchin stakseek(off); 128da2e3ebdSchin 129da2e3ebdSchin return cp; 130da2e3ebdSchin } 131da2e3ebdSchin 132da2e3ebdSchin /* 133da2e3ebdSchin * history expansion main routine 134da2e3ebdSchin */ 135da2e3ebdSchin 136da2e3ebdSchin int hist_expand(const char *ln, char **xp) 137da2e3ebdSchin { 138da2e3ebdSchin int off, /* stack offset */ 139da2e3ebdSchin q, /* quotation flags */ 140da2e3ebdSchin p, /* flag */ 141da2e3ebdSchin c, /* current char */ 142da2e3ebdSchin flag=0; /* HIST_* flags */ 143da2e3ebdSchin Sfoff_t n, /* history line number, counter, etc. */ 144da2e3ebdSchin i, /* counter */ 145da2e3ebdSchin w[2]; /* word range */ 146da2e3ebdSchin char *sp, /* stack pointer */ 147da2e3ebdSchin *cp, /* current char in ln */ 148da2e3ebdSchin *str, /* search string */ 149da2e3ebdSchin *evp, /* event/word designator string, for error msgs */ 150da2e3ebdSchin *cc=0, /* copy of current line up to cp; temp ptr */ 151da2e3ebdSchin hc[3], /* default histchars */ 152da2e3ebdSchin *qc="\'\"`"; /* quote characters */ 153da2e3ebdSchin Sfio_t *ref=0, /* line referenced by event designator */ 154da2e3ebdSchin *tmp=0, /* temporary line buffer */ 155da2e3ebdSchin *tmp2=0;/* temporary line buffer */ 156da2e3ebdSchin Histloc_t hl; /* history location */ 157da2e3ebdSchin static Namval_t *np = 0; /* histchars variable */ 158da2e3ebdSchin static struct subst sb = {0,0}; /* substition strings */ 159da2e3ebdSchin static Sfio_t *wm=0; /* word match from !?string? event designator */ 160da2e3ebdSchin 161da2e3ebdSchin if(!wm) 162da2e3ebdSchin wm = sfopen(NULL, NULL, "swr"); 163da2e3ebdSchin 164da2e3ebdSchin hc[0] = '!'; 165da2e3ebdSchin hc[1] = '^'; 166da2e3ebdSchin hc[2] = 0; 167da2e3ebdSchin if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np))) 168da2e3ebdSchin { 169da2e3ebdSchin if(cp[0]) 170da2e3ebdSchin { 171da2e3ebdSchin hc[0] = cp[0]; 172da2e3ebdSchin if(cp[1]) 173da2e3ebdSchin { 174da2e3ebdSchin hc[1] = cp[1]; 175da2e3ebdSchin if(cp[2]) 176da2e3ebdSchin hc[2] = cp[2]; 177da2e3ebdSchin } 178da2e3ebdSchin } 179da2e3ebdSchin } 180da2e3ebdSchin 181da2e3ebdSchin /* save shell stack */ 182da2e3ebdSchin if(off = staktell()) 183da2e3ebdSchin sp = stakfreeze(0); 184da2e3ebdSchin 185da2e3ebdSchin cp = (char*)ln; 186da2e3ebdSchin 187da2e3ebdSchin while(cp && *cp) 188da2e3ebdSchin { 189da2e3ebdSchin /* read until event/quick substitution/comment designator */ 190da2e3ebdSchin if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2]) 191da2e3ebdSchin || (*cp == hc[1] && cp != ln)) 192da2e3ebdSchin { 193da2e3ebdSchin if(*cp == '\\') /* skip escaped designators */ 194da2e3ebdSchin stakputc(*cp++); 195da2e3ebdSchin else if(*cp == '\'') /* skip quoted designators */ 196da2e3ebdSchin { 197da2e3ebdSchin do 198da2e3ebdSchin stakputc(*cp); 199da2e3ebdSchin while(*++cp && *cp != '\''); 200da2e3ebdSchin } 201da2e3ebdSchin stakputc(*cp++); 202da2e3ebdSchin continue; 203da2e3ebdSchin } 204da2e3ebdSchin 205da2e3ebdSchin if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */ 206da2e3ebdSchin { 207da2e3ebdSchin stakputc(*cp++); 208da2e3ebdSchin stakputs(cp); 209da2e3ebdSchin DONE(); 210da2e3ebdSchin } 211da2e3ebdSchin 212da2e3ebdSchin n = -1; 213da2e3ebdSchin str = 0; 214da2e3ebdSchin flag &= HIST_EVENT; /* save event flag for returning later */ 215da2e3ebdSchin evp = cp; 216da2e3ebdSchin ref = 0; 217da2e3ebdSchin 218da2e3ebdSchin if(*cp == hc[1]) /* shortcut substitution */ 219da2e3ebdSchin { 220da2e3ebdSchin flag |= HIST_QUICKSUBST; 221da2e3ebdSchin goto getline; 222da2e3ebdSchin } 223da2e3ebdSchin 224da2e3ebdSchin if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */ 225da2e3ebdSchin { 226da2e3ebdSchin cp += 2; 227da2e3ebdSchin goto getline; 228da2e3ebdSchin } 229da2e3ebdSchin 230da2e3ebdSchin switch(c = *++cp) { 231da2e3ebdSchin case ' ': 232da2e3ebdSchin case '\t': 233da2e3ebdSchin case '\n': 234da2e3ebdSchin case '\0': 235da2e3ebdSchin case '=': 236da2e3ebdSchin case '(': 237da2e3ebdSchin stakputc(hc[0]); 238da2e3ebdSchin continue; 239da2e3ebdSchin case '#': /* the line up to current position */ 240da2e3ebdSchin flag |= HIST_HASH; 241da2e3ebdSchin cp++; 242da2e3ebdSchin n = staktell(); /* terminate string and dup */ 243da2e3ebdSchin stakputc('\0'); 244da2e3ebdSchin cc = strdup(stakptr(0)); 245da2e3ebdSchin stakseek(n); /* remove null byte again */ 246da2e3ebdSchin ref = sfopen(ref, cc, "s"); /* open as file */ 247da2e3ebdSchin n = 0; /* skip history file referencing */ 248da2e3ebdSchin break; 249da2e3ebdSchin case '-': /* back reference by number */ 250da2e3ebdSchin if(!isdigit(*(cp+1))) 251da2e3ebdSchin goto string_event; 252da2e3ebdSchin cp++; 253da2e3ebdSchin case '0': /* reference by number */ 254da2e3ebdSchin case '1': 255da2e3ebdSchin case '2': 256da2e3ebdSchin case '3': 257da2e3ebdSchin case '4': 258da2e3ebdSchin case '5': 259da2e3ebdSchin case '6': 260da2e3ebdSchin case '7': 261da2e3ebdSchin case '8': 262da2e3ebdSchin case '9': 263da2e3ebdSchin n = 0; 264da2e3ebdSchin while(isdigit(*cp)) 265da2e3ebdSchin n = n * 10 + (*cp++) - '0'; 266da2e3ebdSchin if(c == '-') 267da2e3ebdSchin n = -n; 268da2e3ebdSchin break; 269da2e3ebdSchin case '$': 270da2e3ebdSchin n = -1; 271da2e3ebdSchin case ':': 272da2e3ebdSchin break; 273da2e3ebdSchin case '?': 274da2e3ebdSchin cp++; 275da2e3ebdSchin flag |= HIST_QUESTION; 276da2e3ebdSchin string_event: 277da2e3ebdSchin default: 278da2e3ebdSchin /* read until end of string or word designator/modifier */ 279da2e3ebdSchin str = cp; 280da2e3ebdSchin while(*cp) 281da2e3ebdSchin { 282da2e3ebdSchin cp++; 283da2e3ebdSchin if((!(flag&HIST_QUESTION) && 284da2e3ebdSchin (*cp == ':' || isspace(*cp) 285da2e3ebdSchin || *cp == '^' || *cp == '$' 286da2e3ebdSchin || *cp == '*' || *cp == '-' 287da2e3ebdSchin || *cp == '%') 288da2e3ebdSchin ) 289da2e3ebdSchin || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n'))) 290da2e3ebdSchin { 291da2e3ebdSchin c = *cp; 292da2e3ebdSchin *cp = '\0'; 293da2e3ebdSchin } 294da2e3ebdSchin } 295da2e3ebdSchin break; 296da2e3ebdSchin } 297da2e3ebdSchin 298da2e3ebdSchin getline: 299da2e3ebdSchin flag |= HIST_EVENT; 300da2e3ebdSchin if(str) /* !string or !?string? event designator */ 301da2e3ebdSchin { 302da2e3ebdSchin 303da2e3ebdSchin /* search history for string */ 304da2e3ebdSchin hl = hist_find(sh.hist_ptr, str, 305da2e3ebdSchin sh.hist_ptr->histind, 306da2e3ebdSchin flag&HIST_QUESTION, -1); 307da2e3ebdSchin if((n = hl.hist_command) == -1) 308da2e3ebdSchin n = 0; /* not found */ 309da2e3ebdSchin } 310da2e3ebdSchin if(n) 311da2e3ebdSchin { 312da2e3ebdSchin if(n < 0) /* determine index for backref */ 313da2e3ebdSchin n = sh.hist_ptr->histind + n; 314da2e3ebdSchin /* search and use history file if found */ 315da2e3ebdSchin if(n > 0 && hist_seek(sh.hist_ptr, n) != -1) 316da2e3ebdSchin ref = sh.hist_ptr->histfp; 317da2e3ebdSchin 318da2e3ebdSchin } 319da2e3ebdSchin if(!ref) 320da2e3ebdSchin { 321da2e3ebdSchin /* string not found or command # out of range */ 322da2e3ebdSchin c = *cp; 323da2e3ebdSchin *cp = '\0'; 324da2e3ebdSchin errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp); 325da2e3ebdSchin *cp = c; 326da2e3ebdSchin DONE(); 327da2e3ebdSchin } 328da2e3ebdSchin 329da2e3ebdSchin if(str) /* string search: restore orig. line */ 330da2e3ebdSchin { 331da2e3ebdSchin if(flag&HIST_QUESTION) 332da2e3ebdSchin *cp++ = c; /* skip second question mark */ 333da2e3ebdSchin else 334da2e3ebdSchin *cp = c; 335da2e3ebdSchin } 336da2e3ebdSchin 337da2e3ebdSchin /* colon introduces either word designators or modifiers */ 338da2e3ebdSchin if(*(evp = cp) == ':') 339da2e3ebdSchin cp++; 340da2e3ebdSchin 341da2e3ebdSchin w[0] = 0; /* -1 means last word, -2 means match from !?string? */ 342da2e3ebdSchin w[1] = -1; /* -1 means last word, -2 means suppress last word */ 343da2e3ebdSchin 344da2e3ebdSchin if(flag & HIST_QUICKSUBST) /* shortcut substitution */ 345da2e3ebdSchin goto getsel; 346da2e3ebdSchin 347da2e3ebdSchin n = 0; 348da2e3ebdSchin while(n < 2) 349da2e3ebdSchin { 350da2e3ebdSchin switch(c = *cp++) { 351da2e3ebdSchin case '^': /* first word */ 352da2e3ebdSchin if(n == 0) 353da2e3ebdSchin { 354da2e3ebdSchin w[0] = w[1] = 1; 355da2e3ebdSchin goto skip; 356da2e3ebdSchin } 357da2e3ebdSchin else 358da2e3ebdSchin goto skip2; 359da2e3ebdSchin case '$': /* last word */ 360da2e3ebdSchin w[n] = -1; 361da2e3ebdSchin goto skip; 362da2e3ebdSchin case '%': /* match from !?string? event designator */ 363da2e3ebdSchin if(n == 0) 364da2e3ebdSchin { 365da2e3ebdSchin if(!str) 366da2e3ebdSchin { 367da2e3ebdSchin w[0] = 0; 368da2e3ebdSchin w[1] = -1; 369da2e3ebdSchin ref = wm; 370da2e3ebdSchin } 371da2e3ebdSchin else 372da2e3ebdSchin { 373da2e3ebdSchin w[0] = -2; 374da2e3ebdSchin w[1] = sftell(ref) + hl.hist_char; 375da2e3ebdSchin } 376da2e3ebdSchin sfseek(wm, 0, SEEK_SET); 377da2e3ebdSchin goto skip; 378da2e3ebdSchin } 379da2e3ebdSchin default: 380da2e3ebdSchin skip2: 381da2e3ebdSchin cp--; 382da2e3ebdSchin n = 2; 383da2e3ebdSchin break; 384da2e3ebdSchin case '*': /* until last word */ 385da2e3ebdSchin if(n == 0) 386da2e3ebdSchin w[0] = 1; 387da2e3ebdSchin w[1] = -1; 388da2e3ebdSchin skip: 389da2e3ebdSchin flag |= HIST_WORDDSGN; 390da2e3ebdSchin n = 2; 391da2e3ebdSchin break; 392da2e3ebdSchin case '-': /* until last word or specified index */ 393da2e3ebdSchin w[1] = -2; 394da2e3ebdSchin flag |= HIST_WORDDSGN; 395da2e3ebdSchin n = 1; 396da2e3ebdSchin break; 397da2e3ebdSchin case '0': 398da2e3ebdSchin case '1': 399da2e3ebdSchin case '2': 400da2e3ebdSchin case '3': 401da2e3ebdSchin case '4': 402da2e3ebdSchin case '5': 403da2e3ebdSchin case '6': 404da2e3ebdSchin case '7': 405da2e3ebdSchin case '8': 406da2e3ebdSchin case '9': /* specify index */ 407da2e3ebdSchin if((*evp == ':') || w[1] == -2) 408da2e3ebdSchin { 409da2e3ebdSchin w[n] = c - '0'; 410da2e3ebdSchin while(isdigit(c=*cp++)) 411da2e3ebdSchin w[n] = w[n] * 10 + c - '0'; 412da2e3ebdSchin flag |= HIST_WORDDSGN; 413da2e3ebdSchin if(n == 0) 414da2e3ebdSchin w[1] = w[0]; 415da2e3ebdSchin n++; 416da2e3ebdSchin } 417da2e3ebdSchin else 418da2e3ebdSchin n = 2; 419da2e3ebdSchin cp--; 420da2e3ebdSchin break; 421da2e3ebdSchin } 422da2e3ebdSchin } 423da2e3ebdSchin 424da2e3ebdSchin if(w[0] != -2 && w[1] > 0 && w[0] > w[1]) 425da2e3ebdSchin { 426da2e3ebdSchin c = *cp; 427da2e3ebdSchin *cp = '\0'; 428da2e3ebdSchin errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); 429da2e3ebdSchin *cp = c; 430da2e3ebdSchin DONE(); 431da2e3ebdSchin } 432da2e3ebdSchin 433da2e3ebdSchin /* no valid word designator after colon, rewind */ 434da2e3ebdSchin if(!(flag & HIST_WORDDSGN) && (*evp == ':')) 435da2e3ebdSchin cp = evp; 436da2e3ebdSchin 437da2e3ebdSchin getsel: 438da2e3ebdSchin /* open temp buffer, let sfio do the (re)allocation */ 439da2e3ebdSchin tmp = sfopen(NULL, NULL, "swr"); 440da2e3ebdSchin 441da2e3ebdSchin /* push selected words into buffer, squash 442da2e3ebdSchin whitespace into single blank or a newline */ 443da2e3ebdSchin n = i = q = 0; 444da2e3ebdSchin 445da2e3ebdSchin while((c = sfgetc(ref)) > 0) 446da2e3ebdSchin { 447da2e3ebdSchin if(isspace(c)) 448da2e3ebdSchin { 449da2e3ebdSchin flag |= (c == '\n' ? HIST_NEWLINE : 0); 450da2e3ebdSchin continue; 451da2e3ebdSchin } 452da2e3ebdSchin 453da2e3ebdSchin if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1)) 454da2e3ebdSchin { 455da2e3ebdSchin if(w[0] < 0) 456da2e3ebdSchin sfseek(tmp, 0, SEEK_SET); 457da2e3ebdSchin else 458da2e3ebdSchin i = sftell(tmp); 459da2e3ebdSchin 460da2e3ebdSchin if(i > 0) 461da2e3ebdSchin sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' '); 462da2e3ebdSchin 463da2e3ebdSchin flag &= ~HIST_NEWLINE; 464da2e3ebdSchin p = 1; 465da2e3ebdSchin } 466da2e3ebdSchin else 467da2e3ebdSchin p = 0; 468da2e3ebdSchin 469da2e3ebdSchin do 470da2e3ebdSchin { 471da2e3ebdSchin cc = strchr(qc, c); 472da2e3ebdSchin q ^= cc ? 1<<(int)(cc - qc) : 0; 473da2e3ebdSchin if(p) 474da2e3ebdSchin sfputc(tmp, c); 475da2e3ebdSchin } 476da2e3ebdSchin while((c = sfgetc(ref)) > 0 && (!isspace(c) || q)); 477da2e3ebdSchin 478da2e3ebdSchin if(w[0] == -2 && sftell(ref) > w[1]) 479da2e3ebdSchin break; 480da2e3ebdSchin 481da2e3ebdSchin flag |= (c == '\n' ? HIST_NEWLINE : 0); 482da2e3ebdSchin n++; 483da2e3ebdSchin } 484da2e3ebdSchin if(w[0] != -2 && w[1] >= 0 && w[1] >= n) 485da2e3ebdSchin { 486da2e3ebdSchin c = *cp; 487da2e3ebdSchin *cp = '\0'; 488da2e3ebdSchin errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); 489da2e3ebdSchin *cp = c; 490da2e3ebdSchin DONE(); 491da2e3ebdSchin } 492da2e3ebdSchin else if(w[1] == -2) /* skip last word */ 493da2e3ebdSchin sfseek(tmp, i, SEEK_SET); 494da2e3ebdSchin 495da2e3ebdSchin /* remove trailing newline */ 496da2e3ebdSchin if(sftell(tmp)) 497da2e3ebdSchin { 498da2e3ebdSchin sfseek(tmp, -1, SEEK_CUR); 499da2e3ebdSchin if(sfgetc(tmp) == '\n') 500da2e3ebdSchin sfungetc(tmp, '\n'); 501da2e3ebdSchin } 502da2e3ebdSchin 503da2e3ebdSchin sfputc(tmp, '\0'); 504da2e3ebdSchin 505da2e3ebdSchin if(str) 506da2e3ebdSchin { 507da2e3ebdSchin if(wm) 508da2e3ebdSchin sfclose(wm); 509da2e3ebdSchin wm = tmp; 510da2e3ebdSchin } 511da2e3ebdSchin 512da2e3ebdSchin if(cc && (flag&HIST_HASH)) 513da2e3ebdSchin { 514da2e3ebdSchin /* close !# temp file */ 515da2e3ebdSchin sfclose(ref); 516da2e3ebdSchin flag &= ~HIST_HASH; 517da2e3ebdSchin free(cc); 518da2e3ebdSchin cc = 0; 519da2e3ebdSchin } 520da2e3ebdSchin 521da2e3ebdSchin evp = cp; 522da2e3ebdSchin 523da2e3ebdSchin /* selected line/words are now in buffer, now go for the modifiers */ 524da2e3ebdSchin while(*cp == ':' || (flag & HIST_QUICKSUBST)) 525da2e3ebdSchin { 526da2e3ebdSchin if(flag & HIST_QUICKSUBST) 527da2e3ebdSchin { 528da2e3ebdSchin flag &= ~HIST_QUICKSUBST; 529da2e3ebdSchin c = 's'; 530da2e3ebdSchin cp--; 531da2e3ebdSchin } 532da2e3ebdSchin else 533da2e3ebdSchin c = *++cp; 534da2e3ebdSchin 535da2e3ebdSchin sfseek(tmp, 0, SEEK_SET); 536da2e3ebdSchin tmp2 = sfopen(tmp2, NULL, "swr"); 537da2e3ebdSchin 538da2e3ebdSchin if(c == 'g') /* global substitution */ 539da2e3ebdSchin { 540da2e3ebdSchin flag |= HIST_GLOBALSUBST; 541da2e3ebdSchin c = *++cp; 542da2e3ebdSchin } 543da2e3ebdSchin 544da2e3ebdSchin if(cc = strchr(modifiers, c)) 545da2e3ebdSchin flag |= mod_flags[cc - modifiers]; 546da2e3ebdSchin else 547da2e3ebdSchin { 548da2e3ebdSchin errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c); 549da2e3ebdSchin DONE(); 550da2e3ebdSchin } 551da2e3ebdSchin 552da2e3ebdSchin if(c == 'h' || c == 'r') /* head or base */ 553da2e3ebdSchin { 554da2e3ebdSchin n = -1; 555da2e3ebdSchin while((c = sfgetc(tmp)) > 0) 556da2e3ebdSchin { /* remember position of / or . */ 557da2e3ebdSchin if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r')) 558da2e3ebdSchin n = sftell(tmp2); 559da2e3ebdSchin sfputc(tmp2, c); 560da2e3ebdSchin } 561da2e3ebdSchin if(n > 0) 562da2e3ebdSchin { /* rewind to last / or . */ 563da2e3ebdSchin sfseek(tmp2, n, SEEK_SET); 564da2e3ebdSchin /* end string there */ 565da2e3ebdSchin sfputc(tmp2, '\0'); 566da2e3ebdSchin } 567da2e3ebdSchin } 568da2e3ebdSchin else if(c == 't' || c == 'e') /* tail or suffix */ 569da2e3ebdSchin { 570da2e3ebdSchin n = 0; 571da2e3ebdSchin while((c = sfgetc(tmp)) > 0) 572da2e3ebdSchin { /* remember position of / or . */ 573da2e3ebdSchin if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e')) 574da2e3ebdSchin n = sftell(tmp); 575da2e3ebdSchin } 576da2e3ebdSchin /* rewind to last / or . */ 577da2e3ebdSchin sfseek(tmp, n, SEEK_SET); 578da2e3ebdSchin /* copy from there on */ 579da2e3ebdSchin while((c = sfgetc(tmp)) > 0) 580da2e3ebdSchin sfputc(tmp2, c); 581da2e3ebdSchin } 582da2e3ebdSchin else if(c == 's' || c == '&') 583da2e3ebdSchin { 584da2e3ebdSchin cp++; 585da2e3ebdSchin 586da2e3ebdSchin if(c == 's') 587da2e3ebdSchin { 588da2e3ebdSchin /* preset old with match from !?string? */ 589da2e3ebdSchin if(!sb.str[0] && wm) 590da2e3ebdSchin sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0)); 591da2e3ebdSchin cp = parse_subst(cp, &sb); 592da2e3ebdSchin } 593da2e3ebdSchin 594da2e3ebdSchin if(!sb.str[0] || !sb.str[1]) 595da2e3ebdSchin { 596da2e3ebdSchin c = *cp; 597da2e3ebdSchin *cp = '\0'; 598da2e3ebdSchin errormsg(SH_DICT, ERROR_ERROR, 599da2e3ebdSchin "%s%s: no previous substitution", 600da2e3ebdSchin (flag & HIST_QUICKSUBST) ? ":s" : "", 601da2e3ebdSchin evp); 602da2e3ebdSchin *cp = c; 603da2e3ebdSchin DONE(); 604da2e3ebdSchin } 605da2e3ebdSchin 606da2e3ebdSchin /* need pointer for strstr() */ 607da2e3ebdSchin str = sfsetbuf(tmp, (Void_t*)1, 0); 608da2e3ebdSchin 609da2e3ebdSchin flag |= HIST_SUBSTITUTE; 610da2e3ebdSchin while(flag & HIST_SUBSTITUTE) 611da2e3ebdSchin { 612da2e3ebdSchin /* find string */ 613da2e3ebdSchin if(cc = strstr(str, sb.str[0])) 614da2e3ebdSchin { /* replace it */ 615da2e3ebdSchin c = *cc; 616da2e3ebdSchin *cc = '\0'; 617da2e3ebdSchin sfputr(tmp2, str, -1); 618da2e3ebdSchin sfputr(tmp2, sb.str[1], -1); 619da2e3ebdSchin *cc = c; 620da2e3ebdSchin str = cc + strlen(sb.str[0]); 621da2e3ebdSchin } 622da2e3ebdSchin else if(!sftell(tmp2)) 623da2e3ebdSchin { /* not successfull */ 624da2e3ebdSchin c = *cp; 625da2e3ebdSchin *cp = '\0'; 626da2e3ebdSchin errormsg(SH_DICT, ERROR_ERROR, 627da2e3ebdSchin "%s%s: substitution failed", 628da2e3ebdSchin (flag & HIST_QUICKSUBST) ? ":s" : "", 629da2e3ebdSchin evp); 630da2e3ebdSchin *cp = c; 631da2e3ebdSchin DONE(); 632da2e3ebdSchin } 633da2e3ebdSchin /* loop if g modifier specified */ 634da2e3ebdSchin if(!cc || !(flag & HIST_GLOBALSUBST)) 635da2e3ebdSchin flag &= ~HIST_SUBSTITUTE; 636da2e3ebdSchin } 637da2e3ebdSchin /* output rest of line */ 638da2e3ebdSchin sfputr(tmp2, str, -1); 639da2e3ebdSchin if(*cp) 640da2e3ebdSchin cp--; 641da2e3ebdSchin } 642da2e3ebdSchin 643da2e3ebdSchin if(sftell(tmp2)) 644da2e3ebdSchin { /* if any substitions done, swap buffers */ 645da2e3ebdSchin if(wm != tmp) 646da2e3ebdSchin sfclose(tmp); 647da2e3ebdSchin tmp = tmp2; 648da2e3ebdSchin tmp2 = 0; 649da2e3ebdSchin } 650da2e3ebdSchin cc = 0; 651da2e3ebdSchin if(*cp) 652da2e3ebdSchin cp++; 653da2e3ebdSchin } 654da2e3ebdSchin 655da2e3ebdSchin /* flush temporary buffer to stack */ 656da2e3ebdSchin if(tmp) 657da2e3ebdSchin { 658da2e3ebdSchin sfseek(tmp, 0, SEEK_SET); 659da2e3ebdSchin 660da2e3ebdSchin if(flag & HIST_QUOTE) 661da2e3ebdSchin stakputc('\''); 662da2e3ebdSchin 663da2e3ebdSchin while((c = sfgetc(tmp)) > 0) 664da2e3ebdSchin { 665da2e3ebdSchin if(isspace(c)) 666da2e3ebdSchin { 667da2e3ebdSchin flag = flag & ~HIST_NEWLINE; 668da2e3ebdSchin 669da2e3ebdSchin /* squash white space to either a 670da2e3ebdSchin blank or a newline */ 671da2e3ebdSchin do 672da2e3ebdSchin flag |= (c == '\n' ? HIST_NEWLINE : 0); 673da2e3ebdSchin while((c = sfgetc(tmp)) > 0 && isspace(c)); 674da2e3ebdSchin 675da2e3ebdSchin sfungetc(tmp, c); 676da2e3ebdSchin 677da2e3ebdSchin c = (flag & HIST_NEWLINE) ? '\n' : ' '; 678da2e3ebdSchin 679da2e3ebdSchin if(flag & HIST_QUOTE_BR) 680da2e3ebdSchin { 681da2e3ebdSchin stakputc('\''); 682da2e3ebdSchin stakputc(c); 683da2e3ebdSchin stakputc('\''); 684da2e3ebdSchin } 685da2e3ebdSchin else 686da2e3ebdSchin stakputc(c); 687da2e3ebdSchin } 688da2e3ebdSchin else if((c == '\'') && (flag & HIST_QUOTE)) 689da2e3ebdSchin { 690da2e3ebdSchin stakputc('\''); 691da2e3ebdSchin stakputc('\\'); 692da2e3ebdSchin stakputc(c); 693da2e3ebdSchin stakputc('\''); 694da2e3ebdSchin } 695da2e3ebdSchin else 696da2e3ebdSchin stakputc(c); 697da2e3ebdSchin } 698da2e3ebdSchin if(flag & HIST_QUOTE) 699da2e3ebdSchin stakputc('\''); 700da2e3ebdSchin } 701da2e3ebdSchin } 702da2e3ebdSchin 703da2e3ebdSchin stakputc('\0'); 704da2e3ebdSchin 705da2e3ebdSchin done: 706da2e3ebdSchin if(cc && (flag&HIST_HASH)) 707da2e3ebdSchin { 708da2e3ebdSchin /* close !# temp file */ 709da2e3ebdSchin sfclose(ref); 710da2e3ebdSchin free(cc); 711da2e3ebdSchin cc = 0; 712da2e3ebdSchin } 713da2e3ebdSchin 714da2e3ebdSchin /* error? */ 715da2e3ebdSchin if(staktell() && !(flag & HIST_ERROR)) 716da2e3ebdSchin *xp = strdup(stakfreeze(1)); 717da2e3ebdSchin 718da2e3ebdSchin /* restore shell stack */ 719da2e3ebdSchin if(off) 720da2e3ebdSchin stakset(sp,off); 721da2e3ebdSchin else 722da2e3ebdSchin stakseek(0); 723da2e3ebdSchin 724da2e3ebdSchin /* drop temporary files */ 725da2e3ebdSchin 726da2e3ebdSchin if(tmp && tmp != wm) 727da2e3ebdSchin sfclose(tmp); 728da2e3ebdSchin if(tmp2) 729da2e3ebdSchin sfclose(tmp2); 730da2e3ebdSchin 731da2e3ebdSchin return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK); 732da2e3ebdSchin } 733da2e3ebdSchin 734da2e3ebdSchin #endif 735