与xargs+find

与xargs+find

我正在使用下面的 while 循环来读取文件。

while read file
do
    FileFound="`find $DataDir -name $file -print 2>/dev/null`"
    if [ -n "$FileFound" ]; then
        echo $FileFound >> ${runDir}/st_$Region
        else
            echo $file >> ${APP_HOME}/${Region}_filesnotfound_$date.txt
        fi
done<${Region}_${date}.txt

该 while 循环读取文件名并在 datadir 中进行比较以查找是否有任何匹配项可用。如果可用,它将把整个路径放入文件中。如果不可用,则会将其放入不同的文件中。然而,此脚本需要 2 天才能读取 8000 条记录。有没有办法优化它?

答案1

如果您使用的是现代 Linux 桌面,您可能有一个文件索引工具,例如mlocate已经安装并在后台索引文件。如果是这样,你可以使用它:

while read file
do
    locate "$file" >> "${runDir}/st_$Region" || echo "$file" >> "${APP_HOME}/${Region}_filesnotfound_$date.txt"
done<"${Region}_${date}.txt"

如果您要查找的文件经常更新,您可以首先手动强制数据库更新updatedb或任何适合您的版本的内容locate

答案2

xargs+find

一种解决方案是使用xargs构建非常长的find命令来一次搜索数千个文件:

sed -e 's/^/-o -name /' "${Region}_${date}.txt" \
| xargs find "$DataDir" -false \
> "${runDir}/st_$Region"

第一个命令将每个文件名转换为将附加到该命令sed的表达式。然后执行它构建的命令。结果直接存储到文件中。-o -name filenamexargsfindxargsfindst_$Region

美好的。但是我们如何构建${Region}_filesnotfound_$date.txt未找到的文件列表呢?只需将完整的原始列表与找到的文件列表相交即可:

comm -3 \
    <(sort -u "${Region}_${date}.txt") \
    <(xargs -L1 basename < "${runDir}/st_$Region" | sort -u) \
    > "${Region}_filesnotfound_$date.txt"

comm -3抑制两个文件之间的公共行。这些实际上是伪文件。第二个文件是basename对找到的每个文件应用命令的结果。两个文件均已排序。

find+grep

另一个解决方案是grepfind.提供了(通过选项)搜索文件中存储的一系列模式的grep可能性。-f我们在一个文件中有一系列文件名。让我们将其设为模式列表并将其提供给grep

find "$DataDir" \
| grep -f <(sed 's|.*|/&$|' "${Region}_${date}.txt") \
> "${runDir}/st_$Region"

sed命令是强制性的:它将要搜索的文件名锚定在路径末尾。

至于丢失文件列表,它将以与其他解决方案相同的方式构建。

这个解决方案的问题是文件名可能包含可以被grep: .*[等解释的字符。我们必须用sed(我把它作为练习留给读者)来转义它们。这就是为什么第一个解决方案是首选的恕我直言。

最后,请注意,我在这里使用了一些bash主义(例如过程替换<(...))。不要指望我的任何解决方案都符合 POSIX 标准。

答案3

该脚本仅适用于特定文件的 1 次出现。所以如果不同目录下有两个同名文件,则只会报告一个。尚未经过测试。

declare -a arr
tmp1=$$tmp1

while read file
do
    base=$(basename "$file")
    echo "$base" >> "$tmp1"
    arr["$base"]="$file"
done <(find "$DataDir")

cat "$tmp1" | sort | uniq > "$tmp1"
tmp2=$$tmp2
cat "${Region}_${date}.txt" | sort | uniq > "$tmp2"

for file in "$(join <(cat "$tmp1") <(cat "$tmp2"))"
do
    echo "${arr["$file"]}" >> ${runDir}/st_$Region
done

for file in "$(cat "$tmp1" "$tmp2" | sort | uniq -u)"
do
    echo "$file" >> ${APP_HOME}/${Region}_filesnotfound_$date.txt
done

rm "$tmp1"
rm "$tmp2"

答案4

此脚本的慢速部分是find搜索整个文件$DataDir以查找匹配项。通过将此组件的大部分移出循环,您应该能够节省大量时间:

ftmp=$(mktemp -t)
find "$DataDir" >"$ftmp" 2>/dev/null

while IFS= read -r file
do
    if grep -Fx -q "$file" "$ftmp"    # No RE patterns. Match full line
    then
        echo "$file" >>"$runDir/st_$Region"
    else
        echo "$file" >>"${APP_HOME}/${Region}_filesnotfound_$date.txt"
    fi
done <"${Region}_${date}.txt"

rm -f "$ftmp"

如果您的文件列表${Region}_${date}.txt非常大,您可以通过将整个文件传递给grep然后使用comm从完整列表和匹配集中识别不匹配的条目来进一步节省成本。这里的缺点是,因为comm需要排序列表,所以输出结果列表也会排序:

fdata=$(mktemp -t)
fmatch=$(mktemp -t)
find "$DataDir" >"$fdata" 2>/dev/null

# No RE patterns. Match full line
grep -Fx -f "${Region}_${date}.txt" "$fdata" |
    tee -a "$runDir/st_$Region" |
    sort >"$fmatch"

# Pick out the filenames that didn't match
sort "${Region}_${date}.txt" |
    comm -23 - "$fmatch" >>"${APP_HOME}/${Region}_filesnotfound_$date.txt"

rm -f "$fdata" "$fmatch"

相关内容