尝试将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
命令的结果,但为空。
答案 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[@]}"