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