aboutsummaryrefslogtreecommitdiff
path: root/shell/ash.c
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2008-03-25 01:17:40 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2008-03-25 01:17:40 +0000
commit92e13c2a11381eec06a382de6917369ded240804 (patch)
treeac3d3ce111212fb83a731dc64e653fae4ec9dc03 /shell/ash.c
parent59f351ccda4c66785b864086bf23868768ca6f61 (diff)
downloadbusybox-w32-92e13c2a11381eec06a382de6917369ded240804.tar.gz
busybox-w32-92e13c2a11381eec06a382de6917369ded240804.tar.bz2
busybox-w32-92e13c2a11381eec06a382de6917369ded240804.zip
ash: optional bash-like pattern subst and substring opts
(by James Simmons <jsimmons AT infradead.org>) TODO: write testsuite! BASH_COMPAT off: scanleft 101 262 +161 subevalvar 346 335 -11 BASH_COMPAT on: subevalvar 346 1397 +1051 scanleft 101 262 +161 readtoken1 2739 2807 +68 cmdputs 397 399 +2 static.vstype 64 48 -16 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/1 up/down: 1282/-16) Total: 1266 bytes
Diffstat (limited to 'shell/ash.c')
-rw-r--r--shell/ash.c353
1 files changed, 311 insertions, 42 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 2b6133d20..62380b3ab 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -466,16 +466,21 @@ out2str(const char *p)
466#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ 466#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */
467 467
468/* values of VSTYPE field */ 468/* values of VSTYPE field */
469#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ 469#define VSNORMAL 0x1 /* normal variable: $var or ${var} */
470#define VSMINUS 0x2 /* ${var-text} */ 470#define VSMINUS 0x2 /* ${var-text} */
471#define VSPLUS 0x3 /* ${var+text} */ 471#define VSPLUS 0x3 /* ${var+text} */
472#define VSQUESTION 0x4 /* ${var?message} */ 472#define VSQUESTION 0x4 /* ${var?message} */
473#define VSASSIGN 0x5 /* ${var=text} */ 473#define VSASSIGN 0x5 /* ${var=text} */
474#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ 474#define VSTRIMRIGHT 0x6 /* ${var%pattern} */
475#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ 475#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */
476#define VSTRIMLEFT 0x8 /* ${var#pattern} */ 476#define VSTRIMLEFT 0x8 /* ${var#pattern} */
477#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ 477#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
478#define VSLENGTH 0xa /* ${#var} */ 478#define VSLENGTH 0xa /* ${#var} */
479#if ENABLE_ASH_BASH_COMPAT
480#define VSSUBSTR 0xc /* ${var:position:length} */
481#define VSREPLACE 0xd /* ${var/pattern/replacement} */
482#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */
483#endif
479 484
480static const char dolatstr[] ALIGN1 = { 485static const char dolatstr[] ALIGN1 = {
481 CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' 486 CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
@@ -3471,6 +3476,7 @@ getjob(const char *name, int getctl)
3471 } 3476 }
3472 3477
3473 if (is_number(p)) { 3478 if (is_number(p)) {
3479// TODO: number() instead? It does error checking...
3474 num = atoi(p); 3480 num = atoi(p);
3475 if (num < njobs) { 3481 if (num < njobs) {
3476 jp = jobtab + num - 1; 3482 jp = jobtab + num - 1;
@@ -4178,15 +4184,17 @@ static char *cmdnextc;
4178static void 4184static void
4179cmdputs(const char *s) 4185cmdputs(const char *s)
4180{ 4186{
4187 static const char vstype[VSTYPE + 1][3] = {
4188 "", "}", "-", "+", "?", "=",
4189 "%", "%%", "#", "##"
4190 USE_ASH_BASH_COMPAT(, ":", "/", "//")
4191 };
4192
4181 const char *p, *str; 4193 const char *p, *str;
4182 char c, cc[2] = " "; 4194 char c, cc[2] = " ";
4183 char *nextc; 4195 char *nextc;
4184 int subtype = 0; 4196 int subtype = 0;
4185 int quoted = 0; 4197 int quoted = 0;
4186 static const char vstype[VSTYPE + 1][4] = {
4187 "", "}", "-", "+", "?", "=",
4188 "%", "%%", "#", "##"
4189 };
4190 4198
4191 nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); 4199 nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
4192 p = s; 4200 p = s;
@@ -5681,23 +5689,37 @@ static char *
5681scanleft(char *startp, char *rmesc, char *rmescend ATTRIBUTE_UNUSED, char *str, int quotes, 5689scanleft(char *startp, char *rmesc, char *rmescend ATTRIBUTE_UNUSED, char *str, int quotes,
5682 int zero) 5690 int zero)
5683{ 5691{
5684 char *loc; 5692 char *loc, *loc2, *full;
5685 char *loc2;
5686 char c; 5693 char c;
5687 5694
5688 loc = startp; 5695 loc = startp;
5689 loc2 = rmesc; 5696 loc2 = rmesc;
5690 do { 5697 do {
5691 int match; 5698 int match = strlen(str);
5692 const char *s = loc2; 5699 const char *s = loc2;
5700
5693 c = *loc2; 5701 c = *loc2;
5694 if (zero) { 5702 if (zero) {
5695 *loc2 = '\0'; 5703 *loc2 = '\0';
5696 s = rmesc; 5704 s = rmesc;
5697 } 5705 }
5698 match = pmatch(str, s); 5706
5707 // chop off end if its '*'
5708 full = strrchr(str, '*');
5709 if (full && full != str)
5710 match--;
5711
5712 // If str starts with '*' replace with s.
5713 if ((*str == '*') && strlen(s) >= match) {
5714 full = xstrdup(s);
5715 strncpy(full+strlen(s)-match+1, str+1, match-1);
5716 } else
5717 full = xstrndup(str, match);
5718 match = strncmp(s, full, strlen(full));
5719 free(full);
5720
5699 *loc2 = c; 5721 *loc2 = c;
5700 if (match) 5722 if (!match)
5701 return loc; 5723 return loc;
5702 if (quotes && *loc == CTLESC) 5724 if (quotes && *loc == CTLESC)
5703 loc++; 5725 loc++;
@@ -5760,18 +5782,98 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
5760 ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail); 5782 ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail);
5761} 5783}
5762 5784
5785#if ENABLE_ASH_BASH_COMPAT
5786static char *
5787parse_sub_pattern(char *arg, int inquotes)
5788{
5789 char *idx, *repl = NULL;
5790 unsigned char c;
5791
5792 for (idx = arg; *arg; arg++) {
5793 if (*arg == '/') {
5794 /* Only the first '/' seen is our seperator */
5795 if (!repl) {
5796 *idx++ = '\0';
5797 repl = idx;
5798 } else
5799 *idx++ = *arg;
5800 } else if (*arg != '\\') {
5801 *idx++ = *arg;
5802 } else {
5803 if (inquotes)
5804 arg++;
5805 else {
5806 if (*(arg + 1) != '\\')
5807 goto single_backslash;
5808 arg += 2;
5809 }
5810
5811 switch (*arg) {
5812 case 'n': c = '\n'; break;
5813 case 'r': c = '\r'; break;
5814 case 't': c = '\t'; break;
5815 case 'v': c = '\v'; break;
5816 case 'f': c = '\f'; break;
5817 case 'b': c = '\b'; break;
5818 case 'a': c = '\a'; break;
5819 case '\\':
5820 if (*(arg + 1) != '\\' && !inquotes)
5821 goto single_backslash;
5822 arg++;
5823 /* FALLTHROUGH */
5824 case '\0':
5825 /* Trailing backslash, just stuff one in the buffer
5826 * and backup arg so the loop will exit.
5827 */
5828 c = '\\';
5829 if (!*arg)
5830 arg--;
5831 break;
5832 default:
5833 c = *arg;
5834 if (isdigit(c)) {
5835 /* It's an octal number, parse it. */
5836 int i;
5837 c = 0;
5838
5839 for (i = 0; *arg && i < 3; arg++, i++) {
5840 if (*arg >= '8' || *arg < '0')
5841 ash_msg_and_raise_error("Invalid octal char in pattern");
5842// TODO: number() instead? It does error checking...
5843 c = (c << 3) + atoi(arg);
5844 }
5845 /* back off one (so outer loop can do it) */
5846 arg--;
5847 }
5848 }
5849 *idx++ = c;
5850 }
5851 }
5852 *idx = *arg;
5853
5854 return repl;
5855
5856 single_backslash:
5857 ash_msg_and_raise_error("single backslash unexpected");
5858 /* NOTREACHED */
5859}
5860#endif /* ENABLE_ASH_BASH_COMPAT */
5861
5763static const char * 5862static const char *
5764subevalvar(char *p, char *str, int strloc, int subtype, 5863subevalvar(char *p, char *str, int strloc, int subtype,
5765 int startloc, int varflags, int quotes, struct strlist *var_str_list) 5864 int startloc, int varflags, int quotes, struct strlist *var_str_list)
5766{ 5865{
5866 struct nodelist *saveargbackq = argbackq;
5767 char *startp; 5867 char *startp;
5768 char *loc; 5868 char *loc;
5769 int saveherefd = herefd;
5770 struct nodelist *saveargbackq = argbackq;
5771 int amount;
5772 char *rmesc, *rmescend; 5869 char *rmesc, *rmescend;
5870 USE_ASH_BASH_COMPAT(char *repl = NULL;)
5871 USE_ASH_BASH_COMPAT(char null = '\0';)
5872 USE_ASH_BASH_COMPAT(int pos, len, orig_len;)
5873 int saveherefd = herefd;
5874 int amount, workloc, resetloc;
5773 int zero; 5875 int zero;
5774 char *(*scan)(char *, char *, char *, char *, int , int); 5876 char *(*scan)(char*, char*, char*, char*, int, int);
5775 5877
5776 herefd = -1; 5878 herefd = -1;
5777 argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0, 5879 argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0,
@@ -5788,16 +5890,76 @@ subevalvar(char *p, char *str, int strloc, int subtype,
5788 STADJUST(amount, expdest); 5890 STADJUST(amount, expdest);
5789 return startp; 5891 return startp;
5790 5892
5893#if ENABLE_ASH_BASH_COMPAT
5894 case VSSUBSTR:
5895 loc = str = stackblock() + strloc;
5896// TODO: number() instead? It does error checking...
5897 pos = atoi(loc);
5898 len = str - startp - 1;
5899
5900 /* *loc != '\0', guaranteed by parser */
5901 if (quotes) {
5902 char *ptr;
5903
5904 /* We must adjust the length by the number of escapes we find. */
5905 for (ptr = startp; ptr < (str - 1); ptr++) {
5906 if(*ptr == CTLESC) {
5907 len--;
5908 ptr++;
5909 }
5910 }
5911 }
5912 orig_len = len;
5913
5914 if (*loc++ == ':') {
5915// TODO: number() instead? It does error checking...
5916 len = atoi(loc);
5917 } else {
5918 len = orig_len;
5919 while (*loc && *loc != ':')
5920 loc++;
5921 if (*loc++ == ':')
5922// TODO: number() instead? It does error checking...
5923 len = atoi(loc);
5924 }
5925 if (pos >= orig_len) {
5926 pos = 0;
5927 len = 0;
5928 }
5929 if (len > (orig_len - pos))
5930 len = orig_len - pos;
5931
5932 for (str = startp; pos; str++, pos--) {
5933 if (quotes && *str == CTLESC)
5934 str++;
5935 }
5936 for (loc = startp; len; len--) {
5937 if (quotes && *str == CTLESC)
5938 *loc++ = *str++;
5939 *loc++ = *str++;
5940 }
5941 *loc = '\0';
5942 amount = loc - expdest;
5943 STADJUST(amount, expdest);
5944 return loc;
5945#endif
5946
5791 case VSQUESTION: 5947 case VSQUESTION:
5792 varunset(p, str, startp, varflags); 5948 varunset(p, str, startp, varflags);
5793 /* NOTREACHED */ 5949 /* NOTREACHED */
5794 } 5950 }
5951 resetloc = expdest - (char *)stackblock();
5795 5952
5796 subtype -= VSTRIMRIGHT; 5953 /* We'll comeback here if we grow the stack while handling
5797#if DEBUG 5954 * a VSREPLACE or VSREPLACEALL, since our pointers into the
5798 if (subtype < 0 || subtype > 3) 5955 * stack will need rebasing, and we'll need to remove our work
5799 abort(); 5956 * areas each time
5800#endif 5957 */
5958 USE_ASH_BASH_COMPAT(restart:)
5959
5960 amount = expdest - ((char *)stackblock() + resetloc);
5961 STADJUST(-amount, expdest);
5962 startp = stackblock() + startloc;
5801 5963
5802 rmesc = startp; 5964 rmesc = startp;
5803 rmescend = stackblock() + strloc; 5965 rmescend = stackblock() + strloc;
@@ -5811,7 +5973,93 @@ subevalvar(char *p, char *str, int strloc, int subtype,
5811 rmescend--; 5973 rmescend--;
5812 str = stackblock() + strloc; 5974 str = stackblock() + strloc;
5813 preglob(str, varflags & VSQUOTE, 0); 5975 preglob(str, varflags & VSQUOTE, 0);
5976 workloc = expdest - (char *)stackblock();
5977
5978#if ENABLE_ASH_BASH_COMPAT
5979 if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
5980 char *idx, *end, *restart_detect;
5981
5982 if(!repl) {
5983 repl = parse_sub_pattern(str, varflags & VSQUOTE);
5984 if (!repl)
5985 repl = &null;
5986 }
5987
5988 /* If there's no pattern to match, return the expansion unmolested */
5989 if (*str == '\0')
5990 return 0;
5991
5992 len = 0;
5993 idx = startp;
5994 end = str - 1;
5995 while (idx < end) {
5996 loc = scanright(idx, rmesc, rmescend, str, quotes, 1);
5997 if (!loc) {
5998 /* No match, advance */
5999 restart_detect = stackblock();
6000 STPUTC(*idx, expdest);
6001 if (quotes && *idx == CTLESC) {
6002 idx++;
6003 len++;
6004 STPUTC(*idx, expdest);
6005 }
6006 if (stackblock() != restart_detect)
6007 goto restart;
6008 idx++;
6009 len++;
6010 rmesc++;
6011 continue;
6012 }
6013
6014 if (subtype == VSREPLACEALL) {
6015 while (idx < loc) {
6016 if (quotes && *idx == CTLESC)
6017 idx++;
6018 idx++;
6019 rmesc++;
6020 }
6021 } else
6022 idx = loc;
6023
6024 for (loc = repl; *loc; loc++) {
6025 restart_detect = stackblock();
6026 STPUTC(*loc, expdest);
6027 if (stackblock() != restart_detect)
6028 goto restart;
6029 len++;
6030 }
6031
6032 if (subtype == VSREPLACE) {
6033 while (*idx) {
6034 restart_detect = stackblock();
6035 STPUTC(*idx, expdest);
6036 if (stackblock() != restart_detect)
6037 goto restart;
6038 len++;
6039 idx++;
6040 }
6041 break;
6042 }
6043 }
5814 6044
6045 /* We've put the replaced text into a buffer at workloc, now
6046 * move it to the right place and adjust the stack.
6047 */
6048 startp = stackblock() + startloc;
6049 STPUTC('\0', expdest);
6050 memmove(startp, stackblock() + workloc, len);
6051 startp[len++] = '\0';
6052 amount = expdest - ((char *)stackblock() + startloc + len - 1);
6053 STADJUST(-amount, expdest);
6054 return startp;
6055 }
6056#endif /* ENABLE_ASH_BASH_COMPAT */
6057
6058 subtype -= VSTRIMRIGHT;
6059#if DEBUG
6060 if (subtype < 0 || subtype > 7)
6061 abort();
6062#endif
5815 /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ 6063 /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
5816 zero = subtype >> 1; 6064 zero = subtype >> 1;
5817 /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ 6065 /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
@@ -5925,6 +6173,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
5925 case '7': 6173 case '7':
5926 case '8': 6174 case '8':
5927 case '9': 6175 case '9':
6176// TODO: number() instead? It does error checking...
5928 num = atoi(name); 6177 num = atoi(name);
5929 if (num < 0 || num > shellparam.nparam) 6178 if (num < 0 || num > shellparam.nparam)
5930 return -1; 6179 return -1;
@@ -6063,6 +6312,11 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
6063 case VSTRIMLEFTMAX: 6312 case VSTRIMLEFTMAX:
6064 case VSTRIMRIGHT: 6313 case VSTRIMRIGHT:
6065 case VSTRIMRIGHTMAX: 6314 case VSTRIMRIGHTMAX:
6315#if ENABLE_ASH_BASH_COMPAT
6316 case VSSUBSTR:
6317 case VSREPLACE:
6318 case VSREPLACEALL:
6319#endif
6066 break; 6320 break;
6067 default: 6321 default:
6068 abort(); 6322 abort();
@@ -10459,8 +10713,15 @@ parsesub: {
10459 if (subtype == 0) { 10713 if (subtype == 0) {
10460 switch (c) { 10714 switch (c) {
10461 case ':': 10715 case ':':
10462 flags = VSNUL;
10463 c = pgetc(); 10716 c = pgetc();
10717#if ENABLE_ASH_BASH_COMPAT
10718 if (c == ':' || c == '$' || isdigit(c)) {
10719 pungetc();
10720 subtype = VSSUBSTR;
10721 break;
10722 }
10723#endif
10724 flags = VSNUL;
10464 /*FALLTHROUGH*/ 10725 /*FALLTHROUGH*/
10465 default: 10726 default:
10466 p = strchr(types, c); 10727 p = strchr(types, c);
@@ -10469,18 +10730,26 @@ parsesub: {
10469 subtype = p - types + VSNORMAL; 10730 subtype = p - types + VSNORMAL;
10470 break; 10731 break;
10471 case '%': 10732 case '%':
10472 case '#': 10733 case '#': {
10473 { 10734 int cc = c;
10474 int cc = c; 10735 subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT;
10475 subtype = c == '#' ? VSTRIMLEFT : 10736 c = pgetc();
10476 VSTRIMRIGHT; 10737 if (c == cc)
10477 c = pgetc(); 10738 subtype++;
10478 if (c == cc) 10739 else
10479 subtype++; 10740 pungetc();
10480 else 10741 break;
10481 pungetc(); 10742 }
10482 break; 10743#if ENABLE_ASH_BASH_COMPAT
10483 } 10744 case '/':
10745 subtype = VSREPLACE;
10746 c = pgetc();
10747 if (c == '/')
10748 subtype++; /* VSREPLACEALL */
10749 else
10750 pungetc();
10751 break;
10752#endif
10484 } 10753 }
10485 } else { 10754 } else {
10486 pungetc(); 10755 pungetc();
@@ -12621,7 +12890,7 @@ static const char op_tokens[] ALIGN1 = {
12621 0 12890 0
12622}; 12891};
12623/* ptr to ")" */ 12892/* ptr to ")" */
12624#define endexpression &op_tokens[sizeof(op_tokens)-7] 12893#define endexpression (&op_tokens[sizeof(op_tokens)-7])
12625 12894
12626static arith_t 12895static arith_t
12627arith(const char *expr, int *perrcode) 12896arith(const char *expr, int *perrcode)