【问题标题】:What's the difference between these Bash indirection methods? [duplicate]这些 Bash 间接方法有什么区别? [复制]
【发布时间】:2018-11-04 07:48:18
【问题描述】:

当我偶然发现这种不一致时,我正在探索在 Bash 中进行间接变量访问的不同方法(macOS 上的 4.4.19 通过 MacPorts):

为什么IFS设置为':'时,下面的第二个循环只循环一次?

注意:IFS 的影响在这里很明显!

#!/usr/bin/env bash
PATH="foo:bar:baz"
var=PATH
IFS=':'

echo ${!var}
# returns foo bar baz
echo $(eval echo \$$var)
# also returns foo bar baz

for V in ${!var}; do
    echo $V
done
# returns foo\nbar\nbaz\n

for V in $(eval echo \$$var); do
    echo $V
done
# returns foo bar baz

如果您用空格分隔的项目替换 PATH 内容并删除 IFS,它在这两种情况下都可以正常工作。

【问题讨论】:

  • 如果输入无效,其中一些案例可能存在安全漏洞。有些没有。
  • 比较var='PATH$(touch /tmp/evil)' 时的行为——某些版本(特别是基于eval 的版本)将创建/tmp/evil(并且可以代替删除您的主目录或执行任何其他攻击者的操作能够设置var 可以选择)。其他人会抛出错误。在这种情况下,哪个更安全是很清楚的。
  • 顺便说一句,这里有很多基于引用的错误。通过shellcheck.net 运行您的代码会找到它们;另见BashPitfalls #14
  • ...相比之下,在for V in ${!var} 中,没有echo 将您的:s 转换为空格,因此当它们将字符串拆分为多个片段时,它们仍然是冒号for 循环遍历,所以循环执行多次。
  • 但是如果您将for V in $(eval echo \$$var); do 更改为for V in $(eval "echo \"\$$var\""); do,则行为与其他形式相同,但存在额外的安全漏洞。你在这里问的是纯粹引用差异,没有别的。

标签: bash shell


【解决方案1】:

代码特定的演示

IFS=:
var=path
path=foo:bar:baz

printf '%s\n' "Example 1: Eval Running Unquoted Echo"
printf ' - %s\n' $(eval echo \$$var)

printf '%s\n' '' "Example 2: Eval Running Quoted Echo"
printf ' - %s\n' $(eval "echo \"\$$var\"")

printf '%s\n' '' "Example 3: Indirect Expansion Syntax With Unquoted Echo"
printf ' - %s\n' $(echo ${!var})

printf '%s\n' '' "Example 4: Indirect Expansion Syntax Without Unquoted Expansions"
printf ' - %s\n' ${!var}

...输出:

Example 1: Eval Running Unquoted Echo
 - foo bar baz

Example 2: Eval Running Quoted Echo
 - foo
 - bar
 - baz

Example 3: Indirect Expansion Syntax With Unquoted Echo
 - foo bar baz

Example 4: Indirect Expansion Syntax Without Unquoted Expansions
 - foo
 - bar
 - baz

工作示例和损坏示例之间的区别与是否使用eval 无关;它只有与是否存在不带引号的echo 子进程有关。


通用答案

解释差异:不带引号的命令替换

给出的案例之间的输出差异与间接无关,而与未引用的扩展有关;见BashPitfalls #14

考虑下面这个完全不使用间接的例子:

var='foo
bar
baz'

echo "Correct version:"
echo "$var"
echo
echo "Incorrect version:"
echo $var

...它的输出是:

Correct version:
foo
bar
baz

Incorrect version:
foo bar baz

这里根本没有间接性——唯一的区别是echo $fooecho "$foo" 之间的区别。

以完全相同的方式,echo $(...) 删除换行符,echo "$(...)" 将保留它们。


那么为什么会有多种命令替换机制呢?

因为使用eval 的那些是不安全的。真的,很危险,永远不要使用这种不安全的东西。

考虑:

## THIS IS INSECURE; NEVER DO THIS
varname='foo$(touch /tmp/evil)'
foo="Value"
eval "echo \"\$$varname\""

当它运行时,它会回显Value——但也会创建/tmp/evil。它可以代替运行任何其他攻击者选择的命令。

比较:

varname='foo$(touch /tmp/evil)'
foo="Value"
echo "${!foo}"

...实际运行命令替换。在几乎任何可以利用 shellshock 的情况下(即攻击者可以设置任意环境变量值),使用 eval 方法会破坏您的安全性;使用间接方法是安全的(r)。

【讨论】:

  • 感谢您详细的解释和警告,但可能是因为太晚了,我不太明白它与我的问题有什么关系。我的主要困惑是,在我的示例中,最初的两个“回声”返回相同的东西,所以我被引导相信两种间接方法的输出是相同的。显然,我没有看到一些扩展会影响它在循环情况下的行为。你能澄清一下吗?
  • 试试这个:echo $(echo "first line"; echo "second line"),并将其与echo "$(echo "first line"; echo "second line")" 进行比较——这是否清楚?缺少引号导致命令替换的输出被分词并作为一系列单独的参数传递给echo,然后echo 与空格连接(忽略原始换行符)。
  • ...所以,两种间接方法都正确地尊重换行符,但是错误的引用会丢弃这些换行符。
猜你喜欢
  • 2014-04-01
  • 1970-01-01
  • 2021-04-19
  • 1970-01-01
  • 1970-01-01
  • 2015-12-14
  • 2011-06-29
  • 2018-12-17
  • 1970-01-01
相关资源
最近更新 更多