将查找命令的输出追加到Bash脚本中的变量

时间:2019-02-08 17:12:31

标签: linux bash find

尝试将find命令的输出附加到Bash脚本中的变量

可以将find命令的输出附加到日志文件ok,但是不能将其附加到变量即

这行得通:

find $DIR -type d -name "*" >> $DIRS_REMOVED_LOG

但这不会:

FILES_TO_EVAL=find $DIR -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \)

ENV=`basename $PS_CFG_HOME | tr "[:lower:]" "[:upper:]"`

FILE_TYPES=(*.log *.xml *.txt *.sh)
DIRS_TO_CLEAR="$PS_CFG_HOME/data/files   $PS_CFG_HOME/appserv/prcs/$ENV/files   $PS_CFG_HOME/appserv/prcs/$ENV/files/CQ"

FILES_REMOVED_LOG=$PS_CFG_HOME/files_removed.log
DIRS_REMOVED_LOG=$PS_CFG_HOME/dirs_removed.log

##Cycle through directories
##Below for files_removed_log works ok but can't get the find into a variable.
for DIR in `echo $DIRS_TO_CLEAR`
do
        echo "Searching $DIR for files:"
        FILES_TO_EVAL=find $DIR -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \)

        find $DIR -type d -name "*" >> $DIRS_REMOVED_LOG
done

预期FILES_TO_EVAL将填充find命令的结果,但为空。

2 个答案:

答案 0 :(得分:3)

通过ShellCheck运行脚本。它会发现很多常见错误,就像编译器会发现的一样。

FILES_TO_EVAL=find $DIR -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \)
  

SC2209:使用var=$(command)分配输出(或引号分配字符串)。

答案 1 :(得分:1)

除了shellcheck.net会指出的问题之外,还有许多微妙的问题。

一方面,您正在使用全大写字母的变量名。这很危险,因为有很多全大写变量对Shell和/或其他工具具有特殊含义,并且如果您不小心使用其中的一种,可能会产生奇怪的效果。小写或混合大小写的变量更安全(除非您特别想想要特殊含义)。

此外,您几乎应该始终在变量引用周围加上双引号(例如find "$dir" ...而不是find $dir ...)。没有它们,变量将受到单词拆分和通配符扩展的影响,这可能会带来各种意外的后果。在某些情况下,您需要对变量的值进行 拆分和/或通配符扩展,但通常不完全是shell的方式;在这种情况下,您应该寻找一种更好的方法来完成这项工作。

在失败的行中,

FILES_TO_EVAL=find $DIR -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \)

当前的问题是您需要使用$(find ...)来捕获find命令的输出。但这仍然很危险,因为它只是存储以换行符分隔的文件路径列表,并且扩展此文件的标准方法(仅使用未引用的变量引用)具有我上面提到的所有问题。在这种情况下,如果任何文件名包含空格或通配符(这在文件名中完全合法),将导致麻烦。如果您处于可控制的环境中,可以保证不会发生这种情况,那么您会摆脱它的束缚……但这并不是最好的主意。

正确处理find中的文件路径列表有点复杂,但是有很多方法可以做到。 BashFAQ #20: "How can I find and safely handle file names containing newlines, spaces or both?"中有很多很好的信息,我将在下面总结一些常见的选项:

如果您不需要存储列表,只需在单个文件上运行命令,则可以使用find -exec

find "$dir" -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \) -exec somecommand {} \;

如果需要运行更复杂的内容,可以使用find -print0以明确的形式输出列表,然后使用read -d ''来读取它们。这里有很多潜在的陷阱,所以这是我用来避免所有麻烦点的版本:

while IFS= read -r -d '' filepath <&3; do
    dosomethingwith "$filepath"
done 3< <(find "$dir" -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \) -print0)

请注意,<(command)语法(称为进程替换)是仅bash的功能,因此请在脚本上使用显式的bash shebang(#!/bin/bash#!/usr/bin/env bash),然后不能通过使用sh运行脚本来覆盖它。

如果您确实确实需要存储路径列表以备后用,请将其存储为数组:

files_to_eval=()
while IFS= read -r -d '' filepath; do
    files_to_eval+=("$filepath")
done < <(find "$dir" -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \) -print0)

..或者,如果您具有bash v4.4或更高版本,则更容易使用readarray(又名mapfile):

readarray -td '' files_to_eval < <(find "$dir" -type f \( -name '*.sh' -or -name '*.txt' -or -name '*.xml' -or -name '*.log' \) -print0)

在任何一种情况下,您都应该使用"${files_to_eval[@]}"扩展数组以获取所有元素,而无需对其进行分词和通配符扩展。

其他一些问题。在这一行:

FILE_TYPES=(*.log *.xml *.txt *.sh)

在这种情况下,通配符将立即扩展为当前导演中的匹配项列表。您应该引用它们来防止这种情况:

file_types=("*.log" "*.xml" "*.txt" "*.sh")

在这些行中:

DIRS_TO_CLEAR="$PS_CFG_HOME/data/files   $PS_CFG_HOME/appserv/prcs/$ENV/files   $PS_CFG_HOME/appserv/prcs/$ENV/files/CQ"
...
for DIR in `echo $DIRS_TO_CLEAR`

您正在将列表存储为单个字符串,且条目之间用空格分隔,这会遇到我一直在讨论的所有单词拆分和通配符问题。同样,这里的echo并没有任何用处,实际上使通配符问题更糟。使用数组,避免所有混乱:

dirs_to_clear=("$ps_cfg_home/data/files" "$ps_cfg_home/appserv/prcs/$env/files" "$ps_cfg_home/appserv/prcs/$env/files/CQ")
...
for dir in "${dirs_to_clear[@]}"