Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: Loops and pipes
- X-seq: zsh-workers 55061
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx>
- Cc: Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: Re: Loops and pipes
- Date: Tue, 4 Aug 2026 17:11:25 +0200
- Arc-authentication-results: i=1; mx.google.com; arc=none
- Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=BuS6QElvE6MAD38v3aiXXAAmXap4HE7IT38oJlANbgo=; fh=vuDAGjptCPc/P1/HEZ3j98yPJEW1XjuEoEAmmwDS/+U=; b=fSUsXLy9ZOAAGOZrdkHKEruq6PeoU8/Ocg7wVkfsamcORR1i5tJ0Yv/ogLvHF525Xs wD33CQfgw9AsJxQE5DZNPdO8TYC+DpEoX15oGFxVP1FadROgQG64WWKtT2rN2797DzM9 89AwKBxWzItZsojZBhv6N0YFLD+l23+ZEZ5oz7uf43IzMg7N9KI0wkPYp+cWyILG6BzY +S1zy0VXc4ZJKSO3U+wbK1synFnrEB0vsmYGXlM0S7GtV9hrc4aIge22wGHc42del9Wf bw4Ia0ugf69+nAtw1peRbHl9XQidDr2a2dH12FZ0049p0acqvyolZ5xc2yQTOEX41dgM +MYQ==; darn=zsh.org
- Arc-seal: i=1; a=rsa-sha256; t=1785856298; cv=none; d=google.com; s=arc-20260327; b=YPvoWvfsp5jBjPeLAhTg5zYeboN6ocAZYhTibFw02l4rU4nWyab9jJydVx5eJLhJGR IUrDWgk7SXVRT4PkMlcxy3WJC6bU/YkwXOFblqsLS56EXiG7YbTckeyedSHcRnoldevi YM6aOm+bGvE7bAUPZovUc5OHBuO3KOuHcc80Haru9YLSBsy5Z4IdPlKs/EaNmxUVR6FE bs7xv7VAMnhcfOV+TtddflTj3QqYzjBCGEN4MVLGNiSMQUVEhkHjPzGL8MnMoQb1wSHx JY20EzJGhcjwCcvtQRIOiL/NuBJPOORV527ZYdwNYKMEEFl3REBGSShbOKnYyz82ifaJ Ki3Q==
- Archived-at: <https://zsh.org/workers/55061>
- In-reply-to: <CAH+w=7Zh8+dKT7hwyQquZgr=DYzYXKrLs40h_0WOyam+zAT-7Q@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAH+w=7aqY8wsSSczg_79gPzeA2zfQA=hLhKhXXnLLgNKK_0GKQ@mail.gmail.com> <CAH+w=7bj2yiU_o5jv9Q=J5FHxoCX9j3fn5OKTCBQhvk2qsr7pw@mail.gmail.com> <CAGdYchsAhEE5jRd3o9Kc=KBL4qdCbGs4N3n4bHH1wDnoaQrhEQ@mail.gmail.com> <CAH+w=7Zh8+dKT7hwyQquZgr=DYzYXKrLs40h_0WOyam+zAT-7Q@mail.gmail.com>
Making it an error is quite simple:
diff --git a/Src/exec.c b/Src/exec.c
index 5f849db63..2e6684c96 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1258,6 +1258,7 @@ entersubsh(int flags, struct entersubsh_ret *retp)
clearjobtab(monitor);
get_usage();
forklevel = locallevel;
+ loops = 0;
}
/* execute a string */
That works but for break/continue statements that are in a subshell of their first enclosing loop it produces rather confusing error messages stating that there are no loops. Here is a patch that avoids that:
And here is a further patch that also checks that the break/continue statements don't escape past the available loops:
The description of "continue n" states that it breaks out of n-1 loops and resumes the nth loop but if there are m<n loops then it behaves as "continue m", which is rather unexpected. The second patch avoids that by making "continue n" (as well as "break n") illegal if n is greater than the number of enclosing loops (started in the same subshell as the break/continue statement).
Philippe
diff --git a/Src/builtin.c b/Src/builtin.c
index e35d7fe2c..10c64d3c8 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5814,24 +5814,22 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
nump = 1;
}
- if (nump > 0 && (func == BIN_CONTINUE || func == BIN_BREAK) && num <= 0) {
- zerrnam(name, "argument is not positive: %d", num);
- return 1;
- }
-
switch (func) {
case BIN_CONTINUE:
- if (!loops) { /* continue is only permitted in loops */
- zerrnam(name, "not in while, until, select, or repeat loop");
+ case BIN_BREAK:
+ num = nump ? num : 1;
+ if (num <= 0) {
+ zerrnam(name, "argument is not positive: %d", num);
return 1;
}
- contflag = 1; /* FALLTHROUGH */
- case BIN_BREAK:
- if (!loops) { /* break is only permitted in loops */
- zerrnam(name, "not in while, until, select, or repeat loop");
+ if (!loops) { /* break/continue only permitted in loops */
+ zerrnam(name, ancestor_loops
+ ? "not in same subshell as first enclosing loop"
+ : "not in for, while, until, select, or repeat loop");
return 1;
}
- breaks = nump ? minimum(num,loops) : 1;
+ contflag = func == BIN_CONTINUE;
+ breaks = minimum(num, loops);
break;
case BIN_RETURN:
if ((isset(INTERACTIVE) && isset(SHINSTDIN))
diff --git a/Src/exec.c b/Src/exec.c
index 5f849db63..94f1b735c 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1258,6 +1258,8 @@ entersubsh(int flags, struct entersubsh_ret *retp)
clearjobtab(monitor);
get_usage();
forklevel = locallevel;
+ ancestor_loops += loops;
+ loops = 0;
}
/* execute a string */
diff --git a/Src/init.c b/Src/init.c
index d2b97daff..c5e915bb1 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -1325,7 +1325,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
}
#endif
- breaks = loops = 0;
+ breaks = contflag = loops = ancestor_loops = 0;
lastmailcheck = zmonotime(NULL);
locallevel = sourcelevel = 0;
sfcontext = SFC_NONE;
diff --git a/Src/loop.c b/Src/loop.c
index 351edde89..66dad05f8 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -30,18 +30,23 @@
#include "zsh.mdh"
#include "loop.pro"
-/* # of nested loops we are in */
-
+/* # of nested loops we are in started in ancestor subshells */
+
+/**/
+int ancestor_loops;
+
+/* # of nested loops we are in started in the current subshell */
+
/**/
int loops;
-
-/* # of continue levels */
-
+
+/* whether to continue instead of break on the last break level */
+
/**/
mod_export int contflag;
-
+
/* # of break levels */
-
+
/**/
mod_export volatile int breaks;
diff --git a/Test/A07control.ztst b/Test/A07control.ztst
index b1a248732..cf70d46fe 100644
--- a/Test/A07control.ztst
+++ b/Test/A07control.ztst
@@ -28,7 +28,18 @@
}
fn'
1:continue outside loop
-?fn:continue:1: not in while, until, select, or repeat loop
+?fn:continue:1: not in for, while, until, select, or repeat loop
+
+ for x in a b c; do
+ ( echo $x:before; continue 2>&1; echo $x:after )
+ done
+1:continue in subshell
+>a:before
+>(eval):continue:2: not in same subshell as first enclosing loop
+>b:before
+>(eval):continue:2: not in same subshell as first enclosing loop
+>c:before
+>(eval):continue:2: not in same subshell as first enclosing loop
for outer in 0 1 2 3; do
print outer $outer
diff --git a/Src/builtin.c b/Src/builtin.c
index 10c64d3c8..49effb22b 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5822,10 +5822,20 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
zerrnam(name, "argument is not positive: %d", num);
return 1;
}
- if (!loops) { /* break/continue only permitted in loops */
- zerrnam(name, ancestor_loops
- ? "not in same subshell as first enclosing loop"
- : "not in for, while, until, select, or repeat loop");
+ if (num > loops) { /* break/continue only permitted in loops */
+ if (!loops && !ancestor_loops)
+ zerrnam(name,
+ "not in for, while, until, select, or repeat loop");
+ else if (num > loops + ancestor_loops)
+ zerrnam(name,
+ "not in %d for, while, until, select, or repeat loops",
+ num);
+ else if (!loops)
+ zerrnam(name, "not in same subshell as first enclosing loop");
+ else
+ zerrnam(name,
+ "not in same subshell as first %d enclosing loops",
+ num);
return 1;
}
contflag = func == BIN_CONTINUE;
diff --git a/Test/A07control.ztst b/Test/A07control.ztst
index cf70d46fe..adb676f23 100644
--- a/Test/A07control.ztst
+++ b/Test/A07control.ztst
@@ -30,6 +30,21 @@
1:continue outside loop
?fn:continue:1: not in for, while, until, select, or repeat loop
+ $ZTST_testdir/../Src/zsh -fc '
+ for x in a b c; do
+ for y in d e f; do
+ echo "$x$y:before"
+ continue 3
+ echo "$x$y:after"
+ done
+ echo "$x-:after"
+ done
+ echo "--:after"
+ '
+1:continue beyond all enclosing loops
+>ad:before
+?zsh:continue:5: not in 3 for, while, until, select, or repeat loops
+
for x in a b c; do
( echo $x:before; continue 2>&1; echo $x:after )
done
@@ -41,6 +56,31 @@
>c:before
>(eval):continue:2: not in same subshell as first enclosing loop
+ for x in a b c; do
+ (
+ for y in d e f; do
+ echo "$x$y:before"
+ continue 2 2>&1
+ echo "$x$y:after"
+ done
+ echo "$x-:after1"
+ )
+ echo "$x-:after2"
+ done
+ echo "--:after"
+0:continue beyond all enclosing loops
+>ad:before
+>(eval):continue:5: not in same subshell as first 2 enclosing loops
+>a-:after2
+>bd:before
+>(eval):continue:5: not in same subshell as first 2 enclosing loops
+>b-:after2
+>cd:before
+>(eval):continue:5: not in same subshell as first 2 enclosing loops
+>c-:after2
+>--:after
+
+
for outer in 0 1 2 3; do
print outer $outer
for inner in 0 1 2 3; do
Messages sorted by:
Reverse Date,
Date,
Thread,
Author