The bash -c "$cmd"
shell code executes the bash
command with 3 arguments:
bash
-c
- the contents of the
$cmd
variable
Invoking a shell like that is the standard way (every shell does that, not just bash
¹) to have it interpret the code in the third argument.
Here, you say that $cmd
was initialised as:
cmd='printf "%s\n" "$(date -d "$variable" +%c)"'
But the output of your echo "$cmd"
reveals it was rather assigned as:
cmd='printf "%s\n" $(date -d "$variable" +%c)'
So the shell code that that new bash
invocation will interpret is:
printf "%s\n" $(date -d "$variable" +%c)
Now, since it's a new bash
interpreter being started, that $variable
will not have been assigned there. If you had ran bash -uc
(same as bash -o nounset -c
) instead of -c
, you'd have gotten:
bash: line 1: variable: unbound variable
Without the nounset
option, unset variables are expanded as if they were empty, so the date
command is called with these arguments:
date
-d
- an empty argument
+%c
-d
is not a standard date
option. In the case of GNU date
, it is used to specify an input date. When that input date is empty, that's equivalent to date -d 0
or date -d 00:00:00
, that is today at 00:00:00 in the morning.
Now, because $(date...)
is not quoted and is in list context (in arguments to a printf
command), it is subject to split+glob. The splitting part breaks the output of date on the characters of $IFS
which by default contains space, tab and newline in bash, so you see one separate argument being generated for printf
for each blank-separated word in the output of date
(the globbing part doesn't do anything here as there's no wildcard character in that output of date
).
So with date
outputting: Fri Dec 24 00:00:00 IST 2021
, that results in printf
being invoked with these arguments:
printf
%s\n
Fri
Dec
00:00:00
IST
2021
And printf
will reuse the %s\n
format as many times as possible to consume all the arguments, ending up printing all of them on separate lines.
Now, if you want that new bash
instance to inherit the $variable
of the shell that invokes it, you can export
that variable to the environment, either for all future invocations of every command with:
export variable
bash -c "$cmd"
(printenv variable
, perl -E 'say $ENV{variable}'
... will also see it).
Or just for that one invocation of bash
³ with:
(export variable; exec bash -c "$cmd")
Or:
variable=$variable bash -c "$cmd"
In
eval "$cmd"
It is the current shell that is told to interpret the code in $cmd
, and as that's the shell invocation in which you had assigned $variable
, it will be able to access its contents, so you don't need to export it to the environment.
¹ that's also the case of some interpreters of some other languages that can't really be seen as shell languages, like python
. While some others use different options like sed -e 'sed code'
, perl -e 'perl code'
, php -r 'php code'
... or no option at all: awk 'awk code'
, sed 'sed code'
² and with $0
set to bash
, and with the list of positional parameters empty here.
³ and the commands that that bash
instance will execute itself
help eval
can give you some insight as well, if the quoting thing is confusing to you. – Tom Yan Dec 24 '21 at 07:26eval
seems not reference about date. – jian Dec 24 '21 at 07:38echo "$cmd"
reveals something different (missing 2 double quotes) from what you allegedly assigned it to above. Try aftercmd='printf "%s\n" "$(date -d "$variable" +%c)"'
again – Stéphane Chazelas Dec 24 '21 at 07:39export variable
if you want the$variable
variable of the current shell to be exported to the environment, for the newbash
instance you execute to import it again as its own$variable
variable. – Stéphane Chazelas Dec 24 '21 at 07:41