为什么 `bash -c "$cmd"` 在这里返回当前日期?

为什么 `bash -c "$cmd"` 在这里返回当前日期?

语境:https://stackoverflow.com/a/372​​53784/15603477

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

回报2010-09-08 12:34:56

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

然后运行以下命令bash -c "$cmd" ,如答案所示:

Fri
Dec
24
00:00:00
IST
2021

这是发现的结果:

从第一个非选项参数 command_string 读取并执行命令,然后退出。如果 command_string 之后有参数,则第一个参数将分配给 $0,任何剩余参数将分配给位置参数。对 $0 的赋值设置了 shell 的名称,该名称用于警告和错误消息。


我的问题是关于最后一个命令bash -c "$cmd",输出:今天(现在是2021年12月24日)来自哪里?

--更新:我检查过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 以下引用是我发现的eval

eval [arg ...] 读取参数并将其连接在一起形成单个命令。然后 shell 读取并执行该命令,其退出状态作为 eval 的值返回。如果

答案1

shell代码bash -c "$cmd"执行bash带有 3 个参数的命令:

  1. bash
  2. -c
  3. $cmd变量的内容

bash调用这样的 shell 是让它解释第三个参数中的代码的标准方法(每个 shell 都会这样做,而不仅仅是1)。

在这里,你说它$cmd被初始化为:

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

但你的输出echo "$cmd"显示它被分配为:

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

因此,新调用将解释的 shell 代码bash是:

printf "%s\n" $(date -d "$variable" +%c)

现在,由于正在bash启动一个新的解释器,因此$variable不会将其分配给那里。如果您运行bash -uc(与 相同bash -o nounset -c)而不是-c,您将得到:

bash: line 1: variable: unbound variable

如果没有该nounset选项,未设置的变量将像空一样展开,因此date使用以下参数调用该命令:

  1. date
  2. -d
  3. 空论
  4. +%c

-d不是标准date选项。对于 GNU date,它用于指定输入日期。当该输入日期为空时,相当于date -d 0date -d 00:00:00,即今天早上 00:00:00。

现在,因为$(date...)没有引用并且位于列表上下文中(在printf命令的参数中),所以它受到 split+glob 的影响。分割部分破坏了$IFSbash 中默认包含空格、制表符和换行符的日期输出,因此您会看到printf为输出中的每个空格分隔的单词生成一个单独的参数date(通配部分不此处执行任何操作,因为 ) 的输出中没有通配符date

因此,通过date输出:Fri Dec 24 00:00:00 IST 2021,将导致printf使用这些参数进行调用:

  1. printf
  2. %s\n
  3. Fri
  4. Dec
  5. 00:00:00
  6. IST
  7. 2021

并将尽可能多次地printf重用该%s\n格式来消耗所有参数,最终将所有参数打印在单独的行上。

现在,如果您希望该新bash实例继承调用它的shell$variable的 ,您可以export将该变量添加到环境中,以便将来调用每个命令:

export variable
bash -c "$cmd"

printenv variableperl -E 'say $ENV{variable}'...也会看到它)。

或者只是为了对bash³ 进行一次调用:

(export variable; exec bash -c "$cmd")

或者:

variable=$variable bash -c "$cmd"

eval "$cmd"

当前 shell 被告知解释 中的代码$cmd,并且由于这是您在其中分配的 shell 调用$variable,因此它将能够访问其内容,因此您不需要将其导出到环境中。


¹ 这也是一些其他语言的解释器的情况,这些语言不能真正被视为 shell 语言,例如python.而其他一些人则使用不同的选项,例如sed -e 'sed code', perl -e 'perl code', php -r 'php code'... 或根本不使用选项:awk 'awk code',sed 'sed code'

² 并$0设置为bash,并且此处位置参数列表为空。

bash³ 以及该实例将自行执行的命令

答案2

该变量定义的命令$cmd引用了一个名为 的变量$variable。鉴于它$cmd被传递给另一个进程中的新 shell 调用,因此在$cmd执行时未设置此变量。因此,获取日期的结果调用是date -d "" +%c,它返回当前日期。

从编码的角度来看,将命令存储在变量中是不好的做法。你将要迟早会陷入一场引用噩梦。使用变量作为数据,使用函数作为代码。

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

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

最后,除非您有充分的理由,否则在使用变量时始终确保它们位于双引号内。 (echo $variable很差;echo "$variable"更好;printf "%s\n" "$variable"最好。)

相关内容