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 evalcan give you some insight as well, if the quoting thing is confusing to you.evalseems not reference about date.echo "$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)"'againexport variableif you want the$variablevariable of the current shell to be exported to the environment, for the newbashinstance you execute to import it again as its own$variablevariable.