【问题标题】:Extract words from a file, then list files along with line number that contain those words从文件中提取单词,然后列出文件以及包含这些单词的行号
【发布时间】:2012-10-31 02:16:56
【问题描述】:

我有一个名为 Strings.h 的文件,我用它来本地化我拥有的应用程序。我想搜索我所有的类文件,找出我是否以及在哪里使用每个字符串,并输出每个字符串的类和行号。

我的想法是使用 Python,但也许那是适合这项工作的错误工具。另外,我有一个基本算法,但我担心运行时间太长。你能写这个脚本来做我想做的事,或者只是建议一个更好的算法吗?

Strings.h 看起来像这样:

#import "NonLocalizedStrings.h"

#pragma mark Coordinate Behavior Strings
#define LATITUDE_WORD NSLocalizedString(@"Latitude", @"used in coordinate behaviors")
#define LONGITUDE_WORD NSLocalizedString(@"Longitude", @"used in coordinate behaviors")
#define DEGREES_WORD NSLocalizedString(@"Degrees", @"used in coordinate behaviors")
#define MINUTES_WORD NSLocalizedString(@"Minutes", @"Used in coordiante behaviors")
#define SECONDS_WORD NSLocalizedString(@"Seconds", @"Used in DMSBehavior.m")

...

脚本应获取以#define 开头的每一行,然后列出出现在#define 之后的单词(例如)LATITUDE_WORD

伪代码可能是:

file = strings.h
for line in file:
  extract word after #define
  search_words.push(word) 

print search_words
[LATITUDE_WORD, LONGITUDE_WORD, DEGREES_WORD, MINUTES_WORD, SECONDS WORD]

得到单词列表后,我的伪代码类似于:

found_words = {}
for word in words:
   found_words[word] = []

for file in files:
  for line in file:
    for word in search_words:
      if line contains word:
        found_words[word].push((filename, linenumber))   

print found_words

因此,找到的单词看起来像:

 {
   LATITUDE_WORD: [
                    (foo.m, 42),
                    (bar.m, 132) 
                  ],
   LONGITUDE_WORD: [
                    (baz.m, 22),
                    (bim.m, 112) 
                  ],

 }

【问题讨论】:

    标签: python bash


    【解决方案1】:

    这个 [in bash] 怎么样?

    $ pattern="\\<($(grep '^#define ' Strings.h | cut -d' ' -f2 | tr '\n' '|' | sed 's/|$//'))\\>"
    $ find project_dir -iname '*.m' -exec egrep -Hno "${pattern}" {} + > matches
    

    输出:

    project_dir/bar.m:132:LATITUDE_WORD
    project_dir/baz.m:22:LONGITUDE_WORD
    project_dir/bim.m:112:LONGITUDE_WORD
    project_dir/foo.m:42:LATITUDE_WORD
    

    编辑:我已经更改了上面的代码,将其输出重定向到文件matches,因此我们可以使用它来显示从未找到的单词:

    for word in $(grep '^#define ' Strings.h | cut -d' ' -f2)
    do
        if ! cut -d':' -f3 matches | grep -q "${word}"
        then
            echo "${word}"
        fi
    done
    

    【讨论】:

    • 你能否更新它以递归搜索目录,并输出任何从未找到的单词?我认为这可能是最好的解决方案,很抱歉更新要求!
    • 它已经递归搜索project_dir 以查找匹配*.m 的文件。考虑到它的工作方式,输出从未找到的单词会比较棘手。
    • 好的,编辑后也可以输出从未找到的单词。我花了一段时间才想通...
    • 很抱歉,我还有一个问题。当我运行 find 命令时,出现此错误: egrep: empty (sub)expression
    • 您是否使用 BSD grep 而不是 GNU grep,可能是因为您使用的是 Mac OS X?如果是这样,我已经对答案中的pattern="... 行进行了编辑,这可能会有所帮助(并且无论如何,都会产生更清晰的正则表达式)。
    【解决方案2】:

    看来您的想法是对的。以下是您所拥有的一些优点和缺点。

    优点:

    • 如果您使用 Python,您的伪代码几乎可以逐行翻译 直接添加到您的脚本中。
    • 您可以了解更多有关 Python 的知识(掌握此类事情的高超技能)。

    缺点:

    • 与已发布的其他一些基于 bash 的解决方案相比,Python 的运行速度会稍慢一些(如果您要搜索大量文件,这将是一个问题)。
    • 您的 Python 脚本会比这些其他解决方案长一点,但您的输出也可以更灵活一点。

    答案: 因为我熟悉 Python,而这正是您最初要求的,所以您可以使用更多代码:

    #!/usr/bin/env python
    
    # List the files you want to search here
    search_files = []
    word_file = open('<FILE_PATH_HERE>', 'r')
    
    # Allows for sorted output later.
    words = []
    
    #Contains all found instances.
    inst_dict = {}
    
    for line in word_file:
        if line[0:7] == "#define":
            w = line[7:].split()[0]
            words.append(w)
            inst_dict[w] = []
    
    for file_name in search_files:
        file_obj = open(file_name, 'r')
        line_num = 0
        for line in file_obj:
            for w in words:
                if w in line:
                    inst_dict[w].append((file_name,line_num))
            line_num += 1
    
    # Do whatever you want with 'words' and 'inst_dict'
    words.sort()
    for w in words:
        string = w + ":\n"
        for inst in inst_dict[w]:
            string += "\tFile: " + inst[0] + "\n"
            string += "\tLine: " + inst[1] + "\n"
        print string
    

    我没有测试代码的搜索部分,所以使用“原样”需要您自担风险。祝你好运,随时提出问题或根据需要扩充代码。您的请求非常简单,并且有很多解决方案,所以我希望您了解它是如何工作的。

    【讨论】:

      【解决方案3】:

      此解决方案使用awkglobstar(后者需要 Bash 4)。我认为可以进行进一步改进,但请考虑这是一种草稿。

      shopt -s globstar
      
      awk 'NR==FNR { if ($0 ~ /^#define/) found[$2]=""; next; } 
           {
             for (word in found){
               if ($0 ~ word) 
                 found[word]=found[word] "\t" FILENAME ":" FNR "\n";
             } 
           }
           END { for (word in found) print word ":\n" found[word]}
          ' Strings.h **/*.m  
      

      使用您发布的 Strings.h 的 sn-p,这是我得到的那种输出(我编写了一些测试文件)

      LATITUDE_WORD:
          lala1.m, 2
          lala3.m, 1
      
      DEGREES_WORD:
          lala2.m, 5
      
      SECONDS_WORD:
      
      MINUTES_WORD:
          lala3.m, 3
      
      LONGITUDE_WORD:
          lala3.m, 2
      

      p/s:没有用 globstar 测试过这个,因为我现在使用的 bash 是 v3(pfff!)

      【讨论】:

        【解决方案4】:

        这是一个 Python 程序。它可能可以减少并变得更简单,但它确实有效。

        import re
        l=filecontent.split('\n')
        for item in l:
          if item.startswith("#define"):
            print re.findall("#define .+? ", item)[0].split(' ')[1]
        

        【讨论】:

          【解决方案5】:

          你应该试试:

          grep -oP '^#define\s+\K\S+' strings.h
          

          如果您的 grep 缺少 -P 选项:

          perl -lne 'print $& if /^#define\s+\K\S+/' strings.h
          

          【讨论】:

          • 当我尝试使用 grep 命令时,grep 会列出使用规则: usage: grep [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZ] [-A num] [-B num] [-C[num]]
          【解决方案6】:
          #!/bin/bash
          # Assuming $files constains a list of your files
          word_list=( $(grep '^#define' "${files[@]}" | awk '{ print $2 }') )
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-11-05
            • 1970-01-01
            相关资源
            最近更新 更多