正确地允许bash

时间:2016-06-20 20:19:52

标签: bash shell

我编写,维护和使用健康数量的bash脚本。我认为自己是一个重击黑客,并努力有朝一日成为一个bash忍者(首先需要了解更多awk)。 bash要理解的最重要的特征/挫折之一是引用和后续参数扩展如何工作。这是well documented,并且有充分的理由,在引用参数扩展和分词的神秘世界中存在许多陷阱,错误和新手陷阱。出于这个原因,建议是"双重引用一切,"但是如果我想要分词呢?

multiple style guides中,我找不到在命令替换后安全正确使用分词的示例。

使用不带引号的命令替换的正确方式是什么?

实施例

我不需要帮助让这个命令工作,但它似乎违反了已建立的模式,如果您想对此命令提供反馈,请将其保留在评论中< /强>

docker stats $(docker ps | awk '{print $NF}' | grep -v NAMES)

命令替换返回输出,例如:

container-1 container-3 excitable-newton

这个单行使用命令替换来吐出每个正在运行的docker容器的名称,并使用单词拆分将它们作为单独的输入添加到docker stats命令,该命令采用任意长度列表容器名称并提供有关它们的一些信息。

如果我用过:

docker stats "$(docker ps | awk '{print $NF}' | grep -v NAMES)"

将一行换行符分隔的容器名称传递给docker stats

这似乎是我想要分词时的完美例子,但shellcheck不同意,这有点不安全吗?是否存在在扩展或替换后使用分词的既定模式?

2 个答案:

答案 0 :(得分:7)

从一个命令捕获输出并将其传递给另一个命令的安全方法是临时捕获数组中的输出。这允许在任意分隔符上进行拆分,并防止无意中拆分或通配,同时捕获输出作为多个字符串传递给另一个命令。

如果要将以空格分隔的字符串读入数组,请使用read -a

read -r -a names < <(docker ps | awk '{print $NF}' | grep -v NAMES)
printf 'Found name: %s\n' "${names[@]}"

与不带引号扩展的方法不同,这并不能扩展整体。因此,foo[bar]不能替换为名为foob的文件系统条目,或者如果不存在此类文件系统条目则使用空字符串替换,并设置nullglob shell选项。 (同样,*将不再被当前目录中的文件列表替换。

详细了解行为:read -r -a读取作为-d之后的选项参数的第一个字符(如果给定)传递的分隔符,或者如果该选项参数为0字节则读取NUL ,并根据IFS中的字符将结果拆分为字段 - 默认情况下,该集合包含换行符,制表符和空格;然后它将这些拆分结果分配给一个数组。

除了IFS之外,这种行为并没有根据shell本地配置而有意义地变化,IFS可以作为单一命令的范围进行修改。

mapfile -treadarray -t在行为上具有相似的一致性,如果可移植性限制不能阻止它们的使用,同样建议使用。

相比之下,array=( $string )更依赖于shell的配置和设置,如果shell的配置保留为默认值,则会表现不佳:

  • 使用array=( $string )时,如果未设置set -f,则通过拆分$string创建的每个单词将被评估为glob,并根据{{1}进行进一步的差异行为} settings shopt(这将导致没有扩展到任何内容以导致空集的模式,而不是默认扩展到glob表达式本身), nullglob(这会导致没有扩展到任何内容导致失败的模式),failglobextglob和其他。< / LI>
  • 使用dotglob时,用于拆分操作的IFS值不能以作用于此单个操作的方式轻松可靠地更改。相比之下,可以运行array=( $string )强制IFS=: read仅在read上拆分而不修改IFS值超出该单个值范围的值;在没有存储和重新设置IFS的情况下,没有:的等价物(这是一个容易出错的操作;一些常见的习惯用法[例如赋值给array=( $string )或类似的变量名称]与共同意图相反场景,例如无法在临时修改要应用的块的末尾重现未设置或空的IFS。

答案 1 :(得分:2)

感谢@I'L'I's指向“引用一切”规则的example of a valid exception,我的代码似乎确实是规则的例外。

在我的特定用例中,使用docker容器名称,由于容器名称的限制,意外通配或扩展的风险很低。然而,@Charles Duffy提供了一个绝对安全的方法,通过使用bash内置public int something() { return 1; } public static void main() { int returnValue = something(); System.out.println(returnValue); //Prints 1 } 将第一个输出读入数组,然后在将其输入下一个命令之前分割一个命令输出。(我发现{{}} readarray 3}}更适合我的情况)。

System.out.print(something);

此模式允许将第一个命令的输出作为正确拆分的单独参数提供给第二个命令,同时避免不必要的通配或拆分。不幸的是,我的光滑单行将会有利于安全。