[PATCH] Fix saving and restoring of subscripted inline assignments

Philippe Altherr <[email protected]> Fri, 10 Jul 2026 13:44:53 +0200
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <CAGdYchsePaCCbZomQAwFWW5pZyDUSP+WQmk8gEW1CmNZi5jmAQ@mail.gmail.com>
When an external command is run with a subscripted inline assignment, the
assigned parameter is restored to its previous value after the command
returns:

% export var=12345
% var[2,4]=ABC printenv var
1ABC5
% typeset -p var
export var=12345

The same isn't true for builtins and user defined functions:

% myprintenv() { echo ${(P)1} }
% var[2,4]=ABC myprintenv var
1ABC5
% typeset -p var
export var=1ABC5

The former works because the parameter is only assigned in the forked
process used to run the external command. Thus the value of the parameter
remains unchanged in the main shell.

In the case of the latter, everything runs in the same process. There is
code to save
<https://github.com/zsh-users/zsh/blob/489436767786ec8c8e16436d00ff3c7c4ce0a380/Src/exec.c#L4467>
and restore
<https://github.com/zsh-users/zsh/blob/489436767786ec8c8e16436d00ff3c7c4ce0a380/Src/exec.c#L4521>
parameters modified by inline assignments but that code doesn't account for
the fact that the assigned parameters may be subscripted. In the example
above, it tries to save and restore the parameter var[2,4], instead of the
parameter var, which naturally fails. This looks more like a bug than a
feature. The patch below fixes it by taking into account the possible
presence of subscripts.

- Fix saving and restoring of subscripted inline assignments
<https://github.com/zsh-users/zsh/compare/master...paltherr:zsh:fix-subscripted-inline-assignments>

Philippe
fix-subscripted-inline-assignments.txt (text/plain, 1.5 KB)
diff --git a/Src/exec.c b/Src/exec.c
index 17899262d..20caf40d1 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -4467,14 +4467,16 @@ static void
 save_params(Estate state, Wordcode pc, LinkList *restore_p, LinkList *remove_p)
 {
     Param pm;
-    char *s;
     wordcode ac;
 
     *restore_p = newlinklist();
     *remove_p = newlinklist();
 
     while (wc_code(ac = *pc) == WC_ASSIGN) {
-	s = ecrawstr(state->prog, pc + 1, NULL);
+	char *s = ecrawstr(state->prog, pc + 1, NULL);
+	char *ss = itype_end(s, INAMESPC, 0);
+	int slen = *ss == '[' || *ss == Inbrack ? ss - s : strlen(s);
+	addlinknode(*remove_p, s = dupstring_wlen(s, slen));
 	if ((pm = (Param) paramtab->getnode(paramtab, s))) {
 	    Param tpm = NULL;
 	    if (pm->env)
@@ -4503,11 +4505,9 @@ save_params(Estate state, Wordcode pc, LinkList *restore_p, LinkList *remove_p)
 		tpm->node.nam = pm->node.nam;
 		copyparam(tpm, pm, 1);
 	    }
-	    addlinknode(*remove_p, dupstring(s));
 	    if (tpm)
 		addlinknode(*restore_p, tpm);
-	} else
-	    addlinknode(*remove_p, dupstring(s));
+	}
 
 	pc += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
 	       3 : WC_ASSIGN_NUM(ac) + 2);
diff --git a/Test/A06assign.ztst b/Test/A06assign.ztst
index d653d75b3..3cb73a3fe 100644
--- a/Test/A06assign.ztst
+++ b/Test/A06assign.ztst
@@ -539,12 +539,16 @@
   call
   HELLO=${HELLO}liness call
   call
+  HELLO[2,4]=oo call
+  call
   unset HELLO
 0:save and restore when using original value in temporary
 >world
 >universe
 >world
 >worldliness
+>world
+>wood
 >world
 
  (integer i n x