Repository: mvdan/sh
Pull request #509 (2020-02-17 21:42:06)

Fix an issue not propagating interpreter parameters changes from sourced scripts.
Currently sourced scripts can't changes interpreter parameters, the following script should display `arg1 arg2`:

```
set --
cat > /tmp/source <<EOF
set -- arg1 arg2
EOF
source /tmp/source
echo $@
```



Patch:
diff --git a/interp/builtin.go b/interp/builtin.go
index ba8ea7ab..2c8f2f71 100644
--- a/interp/builtin.go
+++ b/interp/builtin.go
@@ -261,13 +261,25 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
 			r.errf("source: %v\n", err)
 			return 1
 		}
+		// pass source parameters if any or current parameters.
 		oldParams := r.Params
-		r.Params = args[1:]
+		sourceParams := len(args[1:]) > 0
+		if sourceParams {
+			r.Params = args[1:]
+		} else {
+			r.Params = oldParams
+		}
+		oldKeepParams := r.keepParams
+		r.keepParams = true
 		oldInSource := r.inSource
 		r.inSource = true
 		r.stmts(ctx, file.Stmts)
-
-		r.Params = oldParams
+		// restore old parameters if source parameters have been passed
+		// and current parameters wasn't modified by a set call.
+		if r.keepParams && sourceParams {
+			r.Params = oldParams
+		}
+		r.keepParams = oldKeepParams
 		r.inSource = oldInSource
 		if code, ok := r.err.(returnStatus); ok {
 			r.err = nil
diff --git a/interp/interp.go b/interp/interp.go
index 10146ecc..f29d3f90 100644
--- a/interp/interp.go
+++ b/interp/interp.go
@@ -320,6 +320,9 @@ func Params(args ...string) RunnerOption {
 			// If "--" wasn't given and there were zero arguments,
 			// we don't want to override the current parameters.
 			r.Params = args
+			// If parameters are set from a sourced script, don't keep
+			// current parameters to not restore them from source builtin.
+			r.keepParams = !r.inSource
 		}
 		return nil
 	}
@@ -434,6 +437,8 @@ type Runner struct {
 	inSource  bool
 	noErrExit bool
 
+	keepParams bool // track parameters changes (set builtin) while sourcing
+
 	err       error // current shell exit code or fatal error
 	exitShell bool  // whether the shell needs to exit
 


Test patch:
diff --git a/interp/interp_test.go b/interp/interp_test.go
index 9e3d55b0..8ad41de9 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -1843,6 +1843,60 @@ set +o pipefail
 		"bar\n",
 	},
 
+	// source with set and shift
+	{
+		"echo 'set -- d e f' >a; source a; echo $@",
+		"d e f\n",
+	},
+	{
+		"echo 'echo $@' >a; set -- b c; source a; echo $@",
+		"b c\nb c\n",
+	},
+	{
+		"echo 'echo $@' >a; set -- b c; source a d e; echo $@",
+		"d e\nb c\n",
+	},
+	{
+		"echo 'shift; echo $@' >a; set -- b c; source a d e; echo $@",
+		"e\nb c\n",
+	},
+	{
+		"echo 'shift' >a; set -- b c; source a; echo $@",
+		"c\n",
+	},
+	{
+		"echo 'shift; set -- $@' >a; set -- b c; source a d e; echo $@",
+		"e\n",
+	},
+	{
+		"echo 'set -- g f'>b; echo 'set -- d e f; echo $@; source b;' >a; source a; echo $@",
+		"d e f\ng f\n",
+	},
+	{
+		"echo 'set -- g f'>b; echo 'echo $@; set -- d e f; source b;' >a; source a b c; echo $@",
+		"b c\ng f\n",
+	},
+	{
+		"echo 'shift; echo $@' >b; echo 'shift; echo $@; source b' >a; source a b c d; echo $@",
+		"c d\nd\n\n",
+	},
+	{
+		"echo 'set -- b c d' >b; echo 'source b' >a; set -- a; source a; echo $@",
+		"b c d\n",
+	},
+	{
+		"echo 'echo $@' >b; echo 'set -- b c d; source b' >a; set -- a; source a; echo $@",
+		"b c d\nb c d\n",
+	},
+	{
+		"echo 'shift; echo $@' >b; echo 'shift; echo $@; source b c d' >a; set -- a b; source a; echo $@",
+		"b\nd\nb\n",
+	},
+	{
+		"echo 'set -- a b c' >b; echo 'echo $@; source b; echo $@' >a; source a; echo $@",
+		"\na b c\na b c\n",
+	},
+
 	// indexed arrays
 	{
 		"a=foo; echo ${a[0]} ${a[@]} ${a[x]}; echo ${a[1]}",