【问题标题】:Optimal way to recursively find files that match one or more patterns递归查找匹配一个或多个模式的文件的最佳方法
【发布时间】:2018-07-26 13:04:50
【问题描述】:

我必须优化一个shell脚本,但是一周后,我没有成功优化它。

我必须递归搜索目录中的 .c .h 和 .cpp 文件,并检查是否存在这样的单词: "float short unsigned continue for signed void default goto sizeof volatile do if static while"

words=$(echo $@ | sed 's/ /\\|/g')

files=$(find $dir -name '*.cpp' -o -name '*.c' -o -name '*.h' )

for file in  $files; do
(
        test=$(grep -woh "$words" "$file" | sort -u | awk '{print}' ORS=' ')
        if [ "$test" != "" ] ; then
          echo "$(realpath $file) contains : $test"
        fi
)&
done
wait

我尝试过使用 xargs 和 -exec,但没有结果,我必须保持这种结果格式:

/usr/include/c++/6/bits/stl_set.h 包含:默认为 if void

也许你可以帮助我(优化它)..

编辑:我必须保持每个单词出现一次 是的:虽然,对于,易变的...... NOPE: while, for, for, volatile...

【问题讨论】:

  • 我用egrep而不是grep尝试了这个,它奏效了。 grep -E 应该也能正常工作
  • 它正在工作,但我必须对其进行更多优化.. :/ 它需要大约 1.5 秒,它必须是 0.6 秒 -.-
  • 如果性能是一个很大的问题,那么也许 shell 脚本不是解决问题的方法。您尝试提供的答案了吗?

标签: bash grep find


【解决方案1】:

如果您有兴趣查找至少与您的任何模式匹配的所有文件,您可以使用globstar

shopt -s globstar
oldIFS=$IFS; IFS='|'; patterns="$*"; IFS=$oldIFS  # make a | delimited string from arguments
grep -lwE "$patterns" **/*.c **/*.h **/*.cpp       # list files with matching patterns

全球之星

如果设置,则在文件名扩展上下文中使用模式“**” 将匹配所有文件和零个或多个目录和子目录。 如果模式后跟“/”,则只有目录和 子目录匹配。

【讨论】:

  • shopt not found ..我们还没有看到,所以我认为,这不是我们必须使用的:') 但是谢谢你的回答!
  • 你使用的是什么版本的 bash?
  • (学校)GNU bash,版本 4.4.12(1)-release (x86_64-pc-linux-gnu)
【解决方案2】:

这是一种方法,可以在保持所需格式的同时消除使用 find 和 bash 循环:

words='float|short|unsigned|continue|for|signed|void|default|goto|sizeof|volatile|do|if|static|while'
grep  -rwoE --include '*.[ch]' --include '*.cpp' "$words" path | awk -F: '$1!=last{printf "%s%s: contains %s",r,$1,$2; last=$1; r=ORS; delete a; a[$2]} $1==last && !($2 in a){printf " %s",$2; a[$2]} END{print""}'

工作原理

  • grep -rwoE --include '*.[ch]' --include '*.cpp' "$words"路径

    这会递归搜索以path 开头的目录,只查找名称与全局匹配*.[ch]*.cpp 匹配的文件。

  • awk -F: '$1!=last{printf "%s%s: contains %s",r,$1,$2; last=$1; r=ORS; delete a; a[$2]} $1==last{printf " %s",$2} END{print""}'

    这个 awk 命令重新格式化grep 的输出以匹配您想要的输出。该脚本使用变量last 和数组alast 跟踪我们所在的文件,a 包含到目前为止看到的单词列表。更详细:

    • -F:

      这告诉 awk 使用 : 作为字段分隔符。这样,第一个字段是文件名,第二个字段是找到的单词。 (限制:不支持包含: 的文件名。)

    • '$1!=last{printf "%s%s: 包含 %s",r,$1,$2;最后=$1; r=ORS;删除一个;一个[$2]}

      每当文件名$1 与变量last 不匹配时,我们都会开始输出一个新文件。然后,我们更新last 以包含这个新文件的名称。然后我们删除数组a,然后将键$2分配给一个新数组a

    • $1==last && !($2 in a){printf " %s",$2; a[$2]}

      如果当前文件名与前一个相同并且当前单词之前没有见过,我们打印出找到的新单词。我们还添加了这个词 $2 作为数组 a 的键。

    • END{print""}

      这会打印出最后一个换行符(记录分隔符)。

多行版本的代码

对于那些喜欢将代码分散在多行中的人:

grep  -rwoE \
    --include '*.[ch]' \
    --include '*.cpp' \
    "$words" path | 
    awk -F: '
        $1!=last{
            printf "%s%s: contains %s",r,$1,$2
            last=$1
            r=ORS
            delete a
            a[$2]
        }
        $1==last && !($2 in a){
            printf " %s",$2; a[$2]
        }
        END{
            print""
        }'

【讨论】:

  • 哇!结果差不多了,但是如何让每个单词只出现一次呢?
  • @HectoFR 好的。我刚刚更新了代码以保持只显示一次。
  • Uuu,它的工作,但真的很慢 .. :/ (10.7s)
【解决方案3】:

您应该能够使用单个 grep 命令完成大部分操作:

grep -Rw $dir --include \*.c --include \*.h --include \*.cpp -oe "$words"

这会将其放入 file:word 格式,因此剩下的就是更改它以生成您想要的输出。

echo $output | sed 's/:/ /g' | awk '{print $1 " contains : " $2}'

然后您可以添加| sort -u 以获取每个文件中每个单词仅出现一次。


#!/bin/bash

#dir=.
words=$(echo $@ | sed 's/ /\\|/g')

grep -Rw $dir --include \*.c --include \*.h --include \*.cpp -oe "$words" \
    | sort -u \
    | sed 's/:/ /g' \
    | awk '{print $1 " contains : " $2}'

【讨论】:

  • 我已经尝试过类似的方法,但是,除非我弄错了,否则这将保留每个单词的每次出现,我只想一个字一个字地打印一个字,我必须有每个文件一行
  • 感谢您的帮助,但它逐行打印,但我每个文件必须有一行(文件:暂时...),它比我的版本慢“一点点”( 19s)
  • 好的。我建议然后将该输出规范添加到您的 OP 中;从代码本身来看,这并不是很明显。我会继续努力使输出正确,但正如你所说,它显然没有优化,所以我将放弃进一步的输入。
  • 完成,感谢您的尝试:)
猜你喜欢
  • 1970-01-01
  • 2015-02-25
  • 1970-01-01
  • 1970-01-01
  • 2014-04-16
  • 2021-08-05
  • 2013-03-10
  • 1970-01-01
  • 2016-08-18
相关资源
最近更新 更多