【问题标题】:Why does shell interpret my "find" command differently in-line than by executed script?为什么 shell 以不同的方式解释我的“查找”命令而不是执行脚本?
【发布时间】:2019-09-02 15:04:04
【问题描述】:

我正在编写一个用 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),因此应该允许关联数组。

【问题讨论】:

  • -name "'$key'" 可能不符合您的要求。请改用-name "$key"。另外,如果您 (a) 提供足够的信息以使其可重现,并且 (b) 运行一些测试并确定代码的哪一部分没有执行您想要的操作并将问题集中在那部分。
  • @John1024 谢谢!从关联数组键中删除单引号以及 "$key" 对我有用!我不确定需要多少上下文来描述足够抽象的问题,下次我会进一步精简。

标签: shell sh


【解决方案1】:

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

这样的shell命令字符串:

find . -type d -name 'Other Content'

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

find.-typed-nameOther 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 命令。

【讨论】:

  • 非常好的分析。 +1。值得一提的是,这里的一些意外行为的原因是称为 分词引号删除 的 shell 扩展的结果,这些扩展记录在 man bash 中。
  • 如果 John1024 的评论没有准确回答我的问题,这将是一种比较我的脚本执行情况并找出错误的明确方法。从现在开始我将使用它。感谢分享!
猜你喜欢
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 2019-11-25
  • 2023-03-06
  • 1970-01-01
相关资源
最近更新 更多