I thought that this should be straight forward. Generate a temp file and output it. Apparently, there's some strangeness happening behind the scenes.
function x {
cat $2 > /tmp/$BASHPID.$$;
cat /tmp/$BASHPID.$$; # < FAILS because $BASHPID has changed???
};
echo a>/tmp/junk;
x $$ /tmp/junk &
This works however:
function x {
local tmp=/tmp/$BASHPID.$$;
cat $2 > $tmp;
cat $tmp; # < WORKS!
};
echo a>/tmp/junk;
x $$ /tmp/junk &
What exactly is $BASHPID? I thought it is basically like $$ except if executed in a subprocess, it will get the subprocesses PID. When executing cat, is it actually getting cat's PID?
This is a particular instance of a general issue with subshells in bash. You don't need $BASHPID to see it.
The bash manual is quite clear that redirections and assignments are expanded after all other words have been expanded:
When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.
- The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
- The words that are not variable assignments or redirections are expanded (see Shell Expansions). If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments. …
Consequently, the following should not be surprising:
$ unset tmp; a=$tmp eval "echo ${tmp:=foo} \$a"
foo foo
Here, ${tmp:=foo} is expanded before a=$tmp with the result that a=foo is passed into the environment for the evaluation of echo foo $a.
From here, things get murkier. The manual says that redirections are expanded before assignments (actually points 3 and 4; I can't get markdown to co-operate):
- Redirections are performed as described above (see Redirections).
- The text after the ‘=’ in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.
But as we see here, assignments seem to be expanded first:
$ unset tmp; a=$tmp eval <${tmp:=foo} 'echo :$a:'
::
$ unset tmp; <${tmp:=foo} a=$tmp eval 'echo :$a:'
::
And while the manual is explicit that once bash determines that a command is an external utility:
the shell executes the named program in a separate execution environment (from the next section of the manual.)
it's not explicit how much of the word expansion in assignments and redirects is done in the command's execution environment and how much is done in the original environment. And, indeed, the behaviour is not easily predictable.
From experimentation (with bash 4.2), we can see that:
$ unset tmp; a=${tmp:=foo} cat </dev/null; echo tmp=$tmp
tmp=foo
$ unset tmp; >${tmp:=foo} cat </dev/null; echo tmp=$tmp
tmp=
$ unset tmp; >${tmp:=foo} echo </dev/null; echo tmp=$tmp
tmp=foo
All of the above is, at least, reasonable, although the order of assignments and redirections should, IMHO, be documented more accurately. However, the following -- where the expansion in a stdin redirection is evaluated twice if the file doesn't exist -- is certainly a bug:
$ tmp=0; /bin/echo .. <$((tmp+=2)); echo $tmp
bash: 4: No such file or directory
0
# As expected, with a builtin tmp is altered in the parent environment
$ tmp=0; echo .. <$((tmp+=2)); echo $tmp
bash: 4: No such file or directory
4
Since $BASHPID is always the pid of the process in which $BASHPID is actually expanded, all of these various issues also affect its value. In general, I think, the only safe rule is:
$BASHPID anywhere other than:
As a side note, the best way to generate a name for a temporary file is with the mktemp utility.
The reason is that cat isn't a shell builtin. As such, the shell needs to fork and exec.
This causes the BASHPID in the following line:
cat $2 > /tmp/$BASHPID.$$;
to be the PID of the child process. Note that the special variable $$, on the other hand, is expanded before forking. As such, you could think of it as saying:
cat $2 > /tmp/$BASHPID.xxxxx
where xxxxx refers to the shell process executing the function.
A simpler way to observe that would be by saying:
touch $BASHPID; cat $BASHPID > $BASHPID
You'd observe that the filename would be different from the contents of the file. Essentially implying that the BASHPID changed.
On the other hand, saying:
touch $BASHPID; echo $BASHPID > $BASHPID
would result in the file contents being same as it's name.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With