0

Context: https://stackoverflow.com/a/37253784/15603477

cmd='printf "%s\n" "$(date -d "$variable" +%c)"'
echo ${variable}

returns 2010-09-08 12:34:56

echo "$cmd" returns printf "%s\n" $(date -d "$variable")
echo "${cmd}" returns printf "%s\n" $(date -d "$variable")

run the following command bash -c "$cmd" then as the answer shows:

Fri
Dec
24
00:00:00
IST
2021

Here is what found:

Read and execute commands from the first non-option argument command_string, then exit. If there are arguments after the command_string, the first argument is assigned to $0 and any remaining arguments are assigned to the positional parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.


My question is regarding last command bash -c "$cmd", The output: Today (as now is 2021-Dec-24) is come from where?

--update: I checked https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#eval, https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Bourne-Shell-Builtins, https://man7.org/linux/man-pages/man1/bash.1.html The following quote is what i found about eval

eval [arg ...] The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval. If th

5
  • Because you are not echo/printing the variable but use the content as the command? Not even sure what your doubt is.
    – Tom Yan
    Dec 24, 2021 at 7:20
  • Maybe help eval can give you some insight as well, if the quoting thing is confusing to you.
    – Tom Yan
    Dec 24, 2021 at 7:26
  • @TomYan, I already check manual section about eval seems not reference about date.
    – jian
    Dec 24, 2021 at 7:38
  • The output of echo "$cmd" reveals something different (missing 2 double quotes) from what you allegedly assigned it to above. Try after cmd='printf "%s\n" "$(date -d "$variable" +%c)"' again Dec 24, 2021 at 7:39
  • You'll also need export variable if you want the $variable variable of the current shell to be exported to the environment, for the new bash instance you execute to import it again as its own $variable variable. Dec 24, 2021 at 7:41

2 Answers 2

3

The bash -c "$cmd" shell code executes the bash command with 3 arguments:

  1. bash
  2. -c
  3. 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:

  1. date
  2. -d
  3. an empty argument
  4. +%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:

  1. printf
  2. %s\n
  3. Fri
  4. Dec
  5. 00:00:00
  6. IST
  7. 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

1
3

The command defined by the $cmd variable references a variable called $variable. Given that $cmd is passed to a new shell invocation in another process, at the point that $cmd is executed this variable is not set. The resulting invocation to get the date is therefore date -d "" +%c, which returns the current date.

From a coding perspective, storing commands in variables is bad practice. You will end up with a quoting nightmare sooner or later. Use variables for data and functions for code.

cmd() {
    local v=$1
    printf "%s\n" "$(date -d "$v" +%c)"'
}

variable="2010-09-08 12:34:56"
cmd "$variable"

Finally, unless you have good reason, always ensure your variables are inside double quotes when you use them. (echo $variable is poor; echo "$variable" is better; printf "%s\n" "$variable" is best.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .