如何从 /begin/ 到 /end/ 读取文件(如果两者可能位于同一行)

如何从 /begin/ 到 /end/ 读取文件(如果两者可能位于同一行)

我想通过一个大项目的源代码来阅读C函数的原型。

我知道函数名称及其返回类型,并且其原型将在文件中定义*.h

我会用grep(1),但我希望能够读取多行原型,所以它被丢弃了。

所以我通常做的事情是:

  • 项目:glibc
  • 返回类型:int
  • 函数名称:cacheflush
syscall='cacheflush';
find glibc/ -name '*.h' \
|xargs sed -n "/^[a-z ]*int ${syscall}[ ]*(/,/^$/p";

但这会在我想要的行之后打印一些不需要的行:

$ find glibc/ -name '*.h' \
  |xargs sed -n "/^[a-z ]*int ${syscall}[ ]*(/,/^$/p";
extern int cacheflush (void *__addr, int __nbytes, int __op) __THROW;
#endif

extern int cacheflush (void *__addr, const int __nbytes,
               const int __op) __THROW;
#endif

extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;
#endif
extern int _flush_cache (char *__addr, const int __nbytes, const int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;
#endif
extern int _flush_cache (char *__addr, const int __nbytes, const int __op) __THROW;


我希望能够替换结束模式/^$/-> /;/,但是只有当函数原型跨越多行时它才有效。是否可以告诉sed(1)结束模式可能与开始模式在同一行,因此输出如下?:

$ find glibc/ -name '*.h' | xargs sed magic;
extern int cacheflush (void *__addr, int __nbytes, int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes,
               const int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;


答案1

您可以使用pcregrep的多行模式:

$ pcregrep --include='\.h$' -rM '(?s)^\s*(\w+\s+)*int cacheflush\s*\(.*?;' glibc
glibc/sysdeps/unix/sysv/linux/mips/sys/cachectl.h:extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;
glibc/sysdeps/unix/sysv/linux/csky/sys/cachectl.h:extern int cacheflush (void *__addr, const int __nbytes,
                       const int __op) __THROW;
glibc/sysdeps/unix/sysv/linux/nios2/sys/cachectl.h:extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;

通过 PCRE,您可以访问 Perl 的大多数高级正则表达式运算符。在这里,我们使用:

  • \w,以及\s单词和空白字符。
  • (?s):使s标志.也能够匹配换行符。
  • *?: 的非贪婪版本*。所以它匹配第一的的发生;,而不是像贪婪版本那样最后一次发生。

有关详细信息,请参阅pcrepattern(3)手册页。

答案2

不需要调用sed两次,只需在输入范围之前检查开始/结束是否恰好在同一行。

$ find glibc/ -name '*.h' \
|xargs sed \
    -e "/${pattern}.*;\$/b"  \
    -e "/${pattern}/,/;\$/p" \
    -e 'd' ;

find请注意,如果您限制该实用程序仅查找常规文件,那就太好了,否则在sed对名称以 a 结尾的目录进行操作时,您可能会看到警告.h

答案3

我想出了一个(丑陋的)解决方案:

  1. 读取从/begin//^$/(空行),重复该模式的第一行,以便sed(1)可以在后续步骤中对其采取行动。

  2. 使用sed(1)查找从/begin//end/。在此步骤保留空行以便能够使用uniq(1)在下一步中正确删除我们在步骤 1 中重复的行。

  3. 使用uniq(1)删除重复的行。

$ syscall=cacheflush;
$ return=int;
$ pattern="^[a-z ]*${return} ${syscall}[ ]*(";
$ find glibc/ -name '*.h' \
  |xargs sed -n -e "/${pattern}/p" -e "/${pattern}/,/^$/p" \
  |sed -n -e "/${pattern}/,/;/p" -e '/^$/p' \
  |uniq;
extern int cacheflush (void *__addr, int __nbytes, int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes,
               const int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;

extern int cacheflush (void *__addr, const int __nbytes, const int __op) __THROW;

请提供一个更简单的解决方案:)

相关内容