git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 4/4] t/check-non-portable-shell: detect "FOO=bar shell_func"

From
Eric Sunshine <sunshine@sunshineco.com>
Date
Jul 13, 2018, 05:52 UTC
Message-ID
<20180713055205.32351-5-sunshine@sunshineco.com>
In-Reply-To
<20180713055205.32351-1-sunshine@sunshineco.com>

One-shot environment variable assignments, such as 'FOO' in "FOO=bar cmd", exist only during the invocation of 'cmd'. However, if 'cmd' happens to be a shell function, then 'FOO' is assigned in the executing shell itself, and that assignment remains until the process exits (unless explicitly unset). Since this side-effect of "FOO=bar shell_func" is unlikely to be intentional, detect and report such usage.

To distinguish shell functions from other commands, perform a pre-scan of shell scripts named as input, gleaning a list of function names by recognizing lines of the form (loosely matching whitespace):

    shell_func () {

and later report suspect lines of the form (loosely matching quoted values):

    FOO=bar [BAR=foo ...] shell_func

Also take care to stitch together incomplete lines (those ending with "\") since suspect invocations may be split over multiple lines:

    FOO=bar BAR=foo \
    shell_func
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 t/check-non-portable-shell.pl | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl
index f6dbe28b19..d5823f71d8 100755
--- a/t/check-non-portable-shell.pl
+++ b/t/check-non-portable-shell.pl
@@ -7,17 +7,34 @@
 use warnings;
 
 my $exit_code=0;
+my %func;
 
 sub err {
 	my $msg = shift;
 	s/^\s+//;
 	s/\s+$//;
+	s/\s+/ /g;
 	print "$ARGV:$.: error: $msg: $_\n";
 	$exit_code = 1;
 }
 
+# glean names of shell functions
+for my $i (@ARGV) {
+	open(my $f, '<', $i) or die "$0: $i: $!\n";
+	while (<$f>) {
+		$func{$1} = 1 if /^\s*(\w+)\s*\(\)\s*{\s*$/;
+	}
+	close $f;
+}
+
 while (<>) {
 	chomp;
+	# stitch together incomplete lines (those ending with "\")
+	while (s/\\$//) {
+		$_ .= readline;
+		chomp;
+	}
+
 	/\bsed\s+-i/ and err 'sed -i is not portable';
 	/\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)';
 	/^\s*declare\s+/ and err 'arrays/declare not portable';
@@ -25,6 +42,8 @@ sub err {
 	/\btest\s+[^=]*==/ and err '"test a == b" is not portable (use =)';
 	/\bwc -l.*"\s*=/ and err '`"$(wc -l)"` is not portable (use test_line_count)';
 	/\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)';
+	/^\s*([A-Z0-9_]+=(\w+|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
+		err '"FOO=bar shell_func" assignment extends beyond "shell_func"';
 	# this resets our $. for each file
 	close ARGV if eof;
 }
-- 
2.18.0.233.g985f88cf7e
Previous: Eric Sunshine
Message 5 of 5 in “test-lint: detect one-shot "FOO=bar shell_func"”
  1. 0/4 test-lint: detect one-shot "FOO=bar shell_func"Eric Sunshine, Jul 13, 2018
  2. 1/4 t6046/t9833: fix use of "VAR=VAL cmd" with a shell functionEric Sunshine, Jul 13, 2018
  3. 2/4 t/check-non-portable-shell: stop being so politeEric Sunshine, Jul 13, 2018
  4. 3/4 t/check-non-portable-shell: make error messages more compactEric Sunshine, Jul 13, 2018
  5. 4/4 t/check-non-portable-shell: detect "FOO=bar shell_func"Eric Sunshine, Jul 13, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.