首页 > 解决方案 > 为什么 shell 对我的“查找”命令的内联解释与执行的脚本不同?

问题描述

我正在编写一个用 shell 编写的部署脚本,以便在 Linux 服务器上执行。部分要求是源端的某些文件夹聚合在目标端的单个文件夹中。这些不会因项目的历史而改变,因此我为它们创建了一个关联数组,如下所示:

declare -A DIRSET
DIRSET["Content_A"]='/contents'
DIRSET["Content_B"]='/contents'
DIRSET["Templates"]='/templates'
DIRSET["Other Content"]='/templates'

此关联数组用于将源目录映射到目标目录。稍后在我的脚本中,我使用类似于以下的循环遍历这些:

for key in "${!DIRSET[@]}"
do
    SUBDIR=${DIRSET[$key]}
    find . -type d -name "'$key'" | while read line; do
        if ls -1qA "${line}/" | grep -q
        then
        # to strip away './'
        OLDDIR=${line:2:${#line}}
        SUBPATH="${PROJ_ROOT}/AggregateFolder/${SUBDIR}"
        rsync -rvic -e "ssh some/.ssh/dir" "${OLDDIR}/" "${SUBPATH}"
        fi
done

此循环使用“find”查找要聚合的目录,然后将它们输入“rsync”命令。不知何故,“模板”中的内容可以完成,而“其他内容”中的内容却没有。即使目录存在,“查找”命令似乎也没有返回任何结果。

奇怪的是,当我打开我的 shell,内联声明变量,并执行与上面循环段中相同的“查找”时,我得到了结果。我尝试从“key”周围删除单引号并将它们放在关联数组键定义周围,如下所示:

DIRSET["'Other Content'"]='/templates'
...
find . -type d -name "$key"

但这也不会从“查找”中产生任何结果。我将命令放在脚本中的 echo 中,以确保变量按预期传递到命令中:

echo "find . -type d -name $key"
find . -type d -name "$key"

但是输出最终是这样的,其中循环中的任何 find 命令都不起作用:

...
find . -type d -name Templates
find . -type d -name 'Other Content'

请注意,如果我在关联数组键或其“$key”引用周围没有单引号,则会在 shell 尝试将我的字符串解释为一系列以空格分隔的命令时出现错误。

有什么理由为什么“查找”命令会以一种内联方式工作但在执行的脚本中以不同的方式工作?感觉就像我正在寻找就在我鼻子前面的东西。一些外部观点将不胜感激。

注意:源系统和目标系统都有 Bash 4 版本(分别为 4.4 和 4.3),因此应该允许关联数组。

标签: shellsh

解决方案


我认为根本的误解是在 shell 命令字符串和程序调用之间。

像这样的 shell 命令字符串:

find . -type d -name 'Other Content'

将变成一个参数列表并find像这样传递给:

find, ., -type, d, -name,Other Content

正是这个参数列表决定了做什么find。这是真理的来源。导致此参数列表的任何命令字符串都将以find您想要的方式运行。任何导致不同参数列表的命令字符串都将执行其他操作。因此,它正在构建您应该努力的参数列表。

尝试echo打印 shell 命令字符串没有什么价值,就像您不会通过努力拥有console.log(..)print(..)显示要运行的语句来尝试编写 JS 或 Python 一样。

为了更准确地了解结果参数,您可以使用printf

$ printf 'Argument: <%s>\n' find . -type d -name 'Other Content'
Argument: <find>
Argument: <.>
Argument: <-type>
Argument: <d>
Argument: <-name>
Argument: <Other Content>

如果您想从变量的内容中获得相同的结果,您应该确保该命令打印相同的内容。在你的情况下,它不是:

$ printf 'Argument: <%s>\n' find . -type d -name "'$key'"
Argument: <find>
Argument: <.>
Argument: <-type>
Argument: <d>
Argument: <-name>
Argument: <'Other Content'>  # Different argument, so different result

同样的技术显然也证明了你的其他尝试是如何失败的:

$ key="'Other Content'"
$ printf 'Argument: <%s>\n' ... -name $key
Argument: <...>
Argument: <-name>
Argument: <'Other>      # Both extra apostrophes
Argument: <Content'>    # and bad splitting

当这两个printf语句产生相同的参数列表时(只需"$key"在您的情况下使用),您可以删除该printf '..'位并find直接运行您的命令。


推荐阅读