Reputation: 1
Normal command chaining methods work normally, for instance:
$ (SCRATCH='heya'; echo $SCRATCH)
heya
Or using the logic operator && yields predictable results:
$ (SCRATCH='heya' && echo $SCRATCH)
heya
But if we chain the commands by using a space then things get weird:
$ (SCRATCH='heya' echo $SCRATCH)
<<just a blank line prints here>>
Despite the above, the variable actually WAS set, and I can prove it with this line:
$ (DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus" grdctl status)
RDP:
Status: enabled
TLS certificate: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.crt
TLS key: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.key
View-only: no
Username: (hidden)
Password: (hidden)
The above has NO SEMICOLON separating the two commands, and weirdly enough it actually somehow reads the variable and works.
More weirdly, I can break it by inserting the normal requisite semicolon ; which really ought to be there, like this:
$ (DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus"; grdctl status)
Failed to lookup legacy VNC password schema: Cannot autolaunch D-Bus without X11 $DISPLAY
Failed to lookup RDP credentials: Cannot autolaunch D-Bus without X11 $DISPLAY
Next, leaving the offending semicolon in place... I can bring it back to life by exporting the variable, which makes me think that the grdctl command punts itself into a subshell:
$ (export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus"; grdctl status)
RDP:
Status: enabled
TLS certificate: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.crt
TLS key: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.key
View-only: no
Username: (hidden)
Password: (hidden)
And yet, if we directly run the following space separated commands directly, then the variable disappears into the ether without a trace, not even any errors!
$ SCRATCH='heya' echo $SCRATCH
<<just a blank line prints here>>
And if I export the variable, then it gets worse and doesn't even print a blank line!
$ export SCRATCH='heya' echo $SCRATCH
<<nothing shows here, not even a blank line now>>
#And again for good measure, same result:
$ export SCRATCH='heya' echo $SCRATCH
<<still nothing, same as before>>
#But yeah, the variable did export to my shell because:
$ echo $SCRATCH
heya
So not only does the semicolon send make the variable unreachable by echo, but it also prevents echo from seeing the parent environment!
And it's not just the built-in echo command that's hosed, printf is sick too:
$ EPHEMERAL='blink' printf "$EPHEMERAL\n"
<<again, a suffocated blank line here>>
But the grdctl command, which is a binary file (neither a script nor a built-in) is perfectly happy to ingest my variable sans semicolon, and output correctly:
$ unset DBUS_SESSION_BUS_ADDRESS
$ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus" grdctl status
RDP:
Status: enabled
TLS certificate: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.crt
TLS key: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.key
View-only: no
Username: (hidden)
Password: (hidden)
The above seems extremely inconsistent behavior, in that the echo command behaves one way, while the grdctl behaves another way.
I've googled incessantly and consulted with AI's but nobody can explain the chaining of commands with spaces, which is definitely "a thing" because it often works.
There are tens of thousands of explanations on the Internet about how ; or && or || can be used to chain commands, but not a peep about the space character.
-------------- MY FINAL UPDATE --------------
Okay based on all the good discussions below, here is workaround #1:
$ YUCK=pid; YUCK=stu sh -c 'echo -n $YUCK';echo $YUCK
And here is workaround #2:
$ echo 'printf $SOMEVAR"\n"' > myscript.sh; chmod u+x myscript.sh
$ SOMEVAR='blink' ./myscript.sh
Clearly some commands require workarounds while others don't.
Upvotes: 0
Views: 209
Reputation: 413
The fact is that bash is allowing an exported temporary env var to be defined "on the fly" before a command to be executed.
SOME_VAR_TO_BE_EXPORTED="value only for the command" some_command
Example:
echo 'printf "MY_ENV_VAR=%s" $MY_ENV_VAR\n' > myscript.sh
chmod a+x ./myscript.sh
Now let's execute it with a prefixed env var definition
# just to be sure
unset MY_ENV_VAR
MY_ENV_VAR=value ./myscript.sh
will output:
MY_ENV_VAR=value
but $MY_ENV_VAR
scope only exists during the call (assuming it wasn't set before)
echo $MY_ENV_VAR
should be empty
Some explanations here:
https://www.baeldung.com/linux/set-env-variables-bash-command
Official documentation here
https://www.gnu.org/software/bash/manual/html_node/Environment.html
The paragraph:
The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described in Shell Parameters. These assignment statements affect only the environment seen by that command.
So yes it's a confusing syntax. The missing semi-colon is on purpose here for this specific syntax, that just work for envvar assignment.
A semi-colon would produce a distinct behavior: 2 instructions. So defining the variable, not necessary also exporting it, then executing the command (eventually expanding the variable before the second instruction is executed)
What about EPHEMERAL='blink' printf "$EPHEMERAL\n"
...prints nothing?
Well, temporary envvar export could be explained as the following pseudo-code:
old_EPHEMERAL=$EPHEMERAL
export EPHEMERAL='blink'
# assuming `printf` is any command or function to call here
printf "$old_EPHEMERAL\n"
if [[ -n $old_EPHEMERAL ]]
then
EPHEMERAL=$old_EPHEMERAL
fi
# the old value, was only mentioned for the pseudo-code
unset old_EPHEMERAL
but all-in-one!
that mean the export EPHEMERAL='blink'
is not yet available when the all-in-one is evaluated.
So when the printf "$EPHEMERAL\n"
is evaluated (not called yet) value is whatever it would before the export will happen. Then, the command is called with the argument and also the envvar becomes available as $EPHEMERAL
(with value blink
during the call)
One was missing and I was curious about it, and I finally got it:
What about export SCRATCH='heya' echo $SCRATCH
prints nothing even if $SCRATCH
has an old value.
export
is a built-in command, that takes as argument some variable(s) name and change their export status. It can take multiple argument with and without assignment. It outputs nothing and mostly always returns $?
0 (success) even on assignment with errors v=$(false)
. It only returns failure status on rare use-case where the assignment is failing: like invalid variable name, system failure like out of memory, or readonly variable assignment, etc. See: SC2155
So export SCRATCH='heya' echo $SCRATCH
is decomposing in the following pseudo-code, still all-in-one. ⚠️ Note: that this is not recognized as an augmented-temporarily-by-prefixing-it-with-parameter as it's evaluated as a command.
# pseudo code decomposition:
# export and assign $SCRATCH
export SCRATCH='heya'
# export a variable named $echo
export echo
# export a variable from the expansion of the old value of $SCRATCH
export $SCRATCH
tricky enough? 😉
You can watch the result with this extra command:
export -p | grep -E 'echo|SCRATCH|heya'
# outputs
declare -x SCRATCH="heya"
declare -x echo
What will it output if you run export SCRATCH='heya' echo $SCRATCH
twice? (left as an exercise for the reader)
tested with bash version 5.1.16 (See also bash link above ⬆️ for official manual section: 3.7.4 Environment)
Hope that helps, keep reading the doc, 😉 and may be look at shellcheck, you would probably learn many more trick.
Want to become a command line warrior? may this site is still contributed
https://www.commandlinefu.com/commands/browse/sort-by-votes
Upvotes: 1