about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog8
-rw-r--r--Doc/Zsh/expn.yo22
-rw-r--r--Src/subst.c31
-rw-r--r--Test/D04parameter.ztst26
4 files changed, 73 insertions, 14 deletions
diff --git a/ChangeLog b/ChangeLog
index e24788548..ed70e59f5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2010-02-27  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* users/14905 (modified, see users/14096): Doc/Zsh/expn.yo,
+	Src/subst.c, Test/D04parameter.ztst: fix various problems
+	with :s modifier in parameters.
+
 2010-02-26  Peter Stephenson  <pws@csr.com>
 
 	* users/14902: Src/Modules/datetime.c: another go.
@@ -12843,5 +12849,5 @@
 
 *****************************************************
 * This is used by the shell to define $ZSH_PATCHLEVEL
-* $Revision: 1.4920 $
+* $Revision: 1.4921 $
 *****************************************************
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index 6b020a280..7e55ff419 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -273,6 +273,8 @@ The forms `tt(gs/)var(l)tt(/)var(r)' and `tt(s/)var(l)tt(/)var(r)tt(/:G)'
 perform global substitution, i.e. substitute every occurrence of var(r)
 for var(l).  Note that the tt(g) or tt(:G) must appear in exactly the
 position shown.
+
+See further notes on this form of substitution below.
 )
 item(tt(&))(
 Repeat the previous tt(s) substitution.  Like tt(s), may be preceded
@@ -293,18 +295,18 @@ parameter expansion.
 )
 enditem()
 
-The tt(s/l/r/) substitution works as follows.  By default the left-hand
-side of substitutions are not patterns, but character strings.  Any
-character can be used as the delimiter in place of `tt(/)'.  A
-backslash quotes the delimiter character.  The character `tt(&)', in
-the right-hand-side var(r), is replaced by the text from the
+The tt(s/)var(l)tt(/)var(r)tt(/) substitution works as follows.  By
+default the left-hand side of substitutions are not patterns, but
+character strings.  Any character can be used as the delimiter in place
+of `tt(/)'.  A backslash quotes the delimiter character.  The character
+`tt(&)', in the right-hand-side var(r), is replaced by the text from the
 left-hand-side var(l).  The `tt(&)' can be quoted with a backslash.  A
-null var(l) uses the previous string either from the previous var(l)
-or from the contextual scan string var(s) from `tt(!?)var(s)'.  You can
+null var(l) uses the previous string either from the previous var(l) or
+from the contextual scan string var(s) from `tt(!?)var(s)'.  You can
 omit the rightmost delimiter if a newline immediately follows var(r);
-the rightmost `tt(?)' in a context scan can similarly be omitted.
-Note the same record of the last var(l) and var(r) is maintained
-across all forms of expansion.
+the rightmost `tt(?)' in a context scan can similarly be omitted.  Note
+the same record of the last var(l) and var(r) is maintained across all
+forms of expansion.
 
 If the option tt(HIST_SUBST_PATTERN) is set, var(l) is treated as
 a pattern of the usual form described in
diff --git a/Src/subst.c b/Src/subst.c
index 462d0b3a0..a5ed289be 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -3282,6 +3282,13 @@ modify(char **str, char **ptr)
 		ptr1 += charlen;
 		for (ptr2 = ptr1, charlen = 0; *ptr2; ptr2 += charlen) {
 		    convchar_t del2;
+		    if ((*ptr2 == Bnull || *ptr2 == '\\') && ptr2[1]) {
+			/* in double quotes, the backslash isn't tokenized */
+			if (*ptr2 == '\\')
+			    *ptr2 = Bnull;
+			charlen = 2;
+			continue;
+		    }
 		    charlen = MB_METACHARLENCONV(ptr2, &del2);
 #ifdef MULTIBYTE_SUPPORT
 		    if (del2 == WEOF)
@@ -3301,6 +3308,13 @@ modify(char **str, char **ptr)
 		*ptr1end = '\0';
 		for (ptr3 = ptr2, charlen = 0; *ptr3; ptr3 += charlen) {
 		    convchar_t del3;
+		    if ((*ptr3 == Bnull || *ptr3 == '\\') && ptr3[1]) {
+			/* in double quotes, the backslash isn't tokenized */
+			if (*ptr3 == '\\')
+			    *ptr3 = Bnull;
+			charlen = 2;
+			continue;
+		    }
 		    charlen = MB_METACHARLENCONV(ptr3, &del3);
 #ifdef MULTIBYTE_SUPPORT
 		    if (del3 == WEOF)
@@ -3326,9 +3340,20 @@ modify(char **str, char **ptr)
 			chuck(tt--);
 		if (!isset(HISTSUBSTPATTERN))
 		    untokenize(hsubl);
-		for (tt = hsubr = ztrdup(ptr2); *tt; tt++)
-		    if (inull(*tt) && *tt != Bnullkeep)
-			chuck(tt--);
+		for (tt = hsubr = ztrdup(ptr2); *tt; tt++) {
+		    if (inull(*tt) && *tt != Bnullkeep) {
+			if (*tt == Bnull && (tt[1] == '&' || tt[1] == '\\')) {
+			    /*
+			     * The substitution will treat \& and \\
+			     * specially.  We need to leave real \'s
+			     * as the first character for this to work.
+			     */
+			    *tt = '\\';
+			} else {
+			    chuck(tt--);
+			}
+		    }
+		}
 		*ptr1end = sav1;
 		*ptr3 = sav;
 		*ptr = ptr3 - 1;
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 59fa3ac91..f81b7de1d 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -947,6 +947,32 @@
 >/
 >/
 
+  baz=foo/bar
+  zab=oof+rab
+  print ${baz:s/\//+/}
+  print "${baz:s/\//+/}"
+  print ${zab:s/+/\//}
+  print "${zab:s/+/\//}"
+0:Quoting of separator in substitution modifier
+>foo+bar
+>foo+bar
+>oof/rab
+>oof/rab
+
+  print -r ${${:-one/two}:s,/,X&Y,}
+  print -r ${${:-one/two}:s,/,X\&Y,}
+  print -r ${${:-one/two}:s,/,X\\&Y,}
+  print -r "${${:-one/two}:s,/,X&Y,}"
+  print -r "${${:-one/two}:s,/,X\&Y,}"
+  print -r "${${:-one/two}:s,/,X\\&Y,}"
+0:Quoting of ampersand in substitution modifier RHS
+>oneX/Ytwo
+>oneX&Ytwo
+>oneX\/Ytwo
+>oneX/Ytwo
+>oneX&Ytwo
+>oneX\/Ytwo
+
   nully=($'a\0c' $'a\0b\0b' $'a\0b\0a' $'a\0b\0' $'a\0b' $'a\0' $'a')
   for string in ${(o)nully}; do
     for (( i = 1; i <= ${#string}; i++ )); do