Skip to content

Commit e3c5ce0

Browse files
committed
syntax,interp: fix "for i; do ..." loops
They aren't no-op loops like "for i in; do ..."; they range over the shell's positional paramters. That is, it's a short version of: for i in "$@"; do ... Fixes mvdan#341.
1 parent b4690b0 commit e3c5ce0

File tree

7 files changed

+30
-5
lines changed

7 files changed

+30
-5
lines changed

interp/interp.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,11 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) {
820820
switch y := x.Loop.(type) {
821821
case *syntax.WordIter:
822822
name := y.Name.Value
823-
for _, field := range r.fields(y.Items...) {
823+
items := r.Params // for i; do ...
824+
if y.InPos.IsValid() {
825+
items = r.fields(y.Items...) // for i in ...; do ...
826+
}
827+
for _, field := range items {
824828
r.setVarString(name, field)
825829
if r.loopStmtsBroken(ctx, x.Do) {
826830
break

interp/interp_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,14 @@ var fileCases = []struct {
551551
"for ((i=0; i<3; i++)); do :; done; echo $i",
552552
"3\n",
553553
},
554+
{
555+
"set -- a 'b c'; for i in; do echo $i; done",
556+
"",
557+
},
558+
{
559+
"set -- a 'b c'; for i; do echo $i; done",
560+
"a\nb c\n",
561+
},
554562

555563
// block
556564
{

syntax/filetests_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4384,6 +4384,9 @@ func clearPosRecurse(tb testing.TB, src string, v interface{}) {
43844384
recurse(x.Do)
43854385
case *WordIter:
43864386
recurse(x.Name)
4387+
if x.InPos.IsValid() {
4388+
setPos(&x.InPos, "in")
4389+
}
43874390
recurse(x.Items)
43884391
case *CStyleLoop:
43894392
setPos(&x.Lparen, "((")

syntax/nodes.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,14 +373,21 @@ func (*WordIter) loopNode() {}
373373
func (*CStyleLoop) loopNode() {}
374374

375375
// WordIter represents the iteration of a variable over a series of words in a
376-
// for clause.
376+
// for clause. If InPos is an invalid position, the "in" token was missing, so
377+
// the iteration is over the shell's positional parameters.
377378
type WordIter struct {
378379
Name *Lit
380+
InPos Pos // position of "in"
379381
Items []*Word
380382
}
381383

382384
func (w *WordIter) Pos() Pos { return w.Name.Pos() }
383-
func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.Items)) }
385+
func (w *WordIter) End() Pos {
386+
if len(w.Items) > 0 {
387+
return wordLastEnd(w.Items)
388+
}
389+
return posMax(w.Name.End(), posAddCol(w.InPos, 2))
390+
}
384391

385392
// CStyleLoop represents the behaviour of a for clause similar to the C
386393
// language.

syntax/parser.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2017,7 +2017,8 @@ func (p *Parser) wordIter(ftok string, fpos Pos) *WordIter {
20172017
return wi
20182018
}
20192019
p.got(_Newl)
2020-
if _, ok := p.gotRsrv("in"); ok {
2020+
if pos, ok := p.gotRsrv("in"); ok {
2021+
wi.InPos = pos
20212022
for !stopToken(p.tok) {
20222023
if w := p.getWord(); w == nil {
20232024
p.curErr("word list can only contain words")

syntax/printer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ func (p *Printer) loop(loop Loop) {
620620
switch x := loop.(type) {
621621
case *WordIter:
622622
p.WriteString(x.Name.Value)
623-
if len(x.Items) > 0 {
623+
if x.InPos.IsValid() {
624624
p.spacedString(" in", Pos{})
625625
p.wordJoin(x.Items)
626626
}

syntax/printer_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,8 @@ var printTests = []printCase{
462462
"if foo # inline\nthen\n\tbar\nfi",
463463
"if foo; then # inline\n\tbar\nfi",
464464
},
465+
samePrint("for i; do echo $i; done"),
466+
samePrint("for i in; do echo $i; done"),
465467
{
466468
"for foo in a b # inline\ndo\n\tbar\ndone",
467469
"for foo in a b; do # inline\n\tbar\ndone",

0 commit comments

Comments
 (0)