Skip to content

Command-substitution variables clobber global variables. #6480

@0ion9

Description

@0ion9
set v (seq 10)
echo 'Before: v = '"$v"
set foobar (for v in $v; echo $v; end)
echo "After: v = $v"
echo 'Set sez:'
set -S v

Output:

Before: v = 1 2 3 4 5 6 7 8 9 10
After: v = 10
Set sez:
$v: not set in local scope
$v: set in global scope, unexported, with 1 elements
$v[1]: length=2 value=|10|
$v: not set in universal scope

Okay, maybe 'correct behaviour' is a little ambiguous here. Maybe the user wants to know the inner value of v?

But here's the real problem:

set v (seq 10); echo 'Before: v = '"$v";function f; set foobar (for v in $v; echo $v; end); end; f; echo "After: v = $v";echo 'Set sez:'; set -S v

In other words, if you set a variable in your shell and then call a function which uses a variable of the same name in a subshell, your variable gets clobbered. That means if you want to ensure your variables stay intact, you cannot use the most convenient short variable names (a,b, ..), since any function might use a subshell that happens to clobber them.

This is subshell specific. 'v' is not clobbered in the following case:

set v (seq 10); echo 'Before: v = '"$v";function f; for v in $v; echo -n "$v "; end; end; f; echo "After: v = $v";echo 'Set sez:'; set -S v

It is not specific to 'for' loops. 'while' loops in a subshell clobber in a different way:

set v (seq 10); echo 'Before: v = '"$v";function f; set foobar (printf %s\n $v | while read v; echo $v; end); echo $foobar; end; f; echo "After: v = $v";echo 'Set sez:'; set -S v

The result of the above is that, instead of v being set to 10, it is set to nothing (ie set -g v)

Two more cases. No loop, just set:

set v (seq 10); echo 'Before: v = '"$v";function f; set foobar (set v (string join '' $v)); end; f; echo "After: v = $v";echo 'Set sez:'; set -S v

(v's value is now 12345678910)

No loop, just set -l

set v (seq 10); echo 'Before: v = '"$v";function f; set foobar (set -l v (string join '' $v)); end; f; echo "After: v = $v";echo 'Set sez:'; set -S v

This final case produces 'correct' results (v is not clobbered)

Expected behaviour:

At least within a function, a subshell should not be able to damage variables in the global scope without explicitly stating that it is intended (set -g). In the cases of for VARNAME and while VARNAME within a subshell, VARNAME should either be set locally to the subshell, or locally to the function.

fish --version: fish, version 3.0.2-2058-gd0edd984d
uname -a: Linux cooler 5.4.6-arch3-1 #1 SMP PREEMPT Tue, 24 Dec 2019 04:36:53 +0000 x86_64 GNU/Linux
$TERM: xterm-256color
Problem occurs in clean fish install: Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething that's not working as intendedregressionSomething that used to work, but was broken, especially between releases

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions