我正在寻找一种使用脚本编辑文件的方法,该脚本将为 C 中的变量提供替代定义,并且此重新定义由使用我为其编写脚本的 POSIX 应用程序的编译器 D 标志控制。例如,假设令牌nice_var
应该重命名为cool_var
:
输入示例:
struct BigThing nice_var;
/* some lines later */
nice_var->thing = 1;
预期输出:
/* TODO temporary ifdef to facilitate renaming */
#ifdef RENAME_FEATURE_ENABLED
struct BigThing cool_var;
#else
struct BigThing nice_var;
#endif /* RENAME_FEATURE_ENABLED */
/* some lines later */
/* TODO temporary ifdef to facilitate renaming */
#ifdef RENAME_FEATURE_ENABLED
cool_var->thing = 1;
#else
nice_var->thing = 1;
#endif /* RENAME_FEATURE_ENABLED */
字符串的每次出现都应封装在所示的预处理器指令模式中,即使用法出现在连续行上也是如此。
是的,我可以编写一个 python/perl/javascript 脚本来执行此操作,但是是否有 POSIX 应用程序可以支持这种文件编辑?我期望ed
或ex
能够做到这一点,但我不知道如何循环ed
以解决文件中的所有出现情况,也不知道如何检测ex
搜索何时到达文件末尾。有关如何检测这一点的信息ex
可能就是我实现目标所需的全部信息。
如果不明显,我计划使用该脚本(我将其称为renamer
)来编辑工作区中出现“nice_var”的所有文件,如下所示:
#!/bin/bash -eux
for file in $(grep --files-with-matches --recursive 'nice_var'); do
renamer --feature-name RENAME_FEATURE_ENABLED --var-name 'nice_var' --replacement 'cool_var' "${file}"
done
无需处理答案中的命令行参数。我对在匹配的行之前添加内容以及复制该行的概念更感兴趣。
问题实际上归结为:“是否有一个 POSIX 应用程序支持重复搜索模式,然后在找到模式的行之前和之后添加行,其中添加的行之一与模式匹配,除了图案被替换了?”
也许awk
有多次通过?
答案1
这是一个“尽力而为”的解决方案,它提供了一种简单的替换,无需了解语言语法,无论文本是变量、注释行 ( /* don't change nice_var here */
)、字符串中的引号 ( char *p = "I've got nice_var here"
),甚至是另一个单词的一部分 ( int quitenice_vartoo
) 。您需要一个 C 语言解析器的近似值才能正确正确地实现这一点。
awk -v src=nice_var -v dst=cool_var -v macro=RENAME_FEATURE_ENABLED '
$0 ~ src {
this = that = $0;
gsub(src, dst, that);
printf "/* TODO temporary ifdef to facilitate renaming */\n#ifdef %s\n%s\n#else\n%s\n#endif /* %s */\n", macro, that, this, macro;
next
}
1
' sourcefile.c
您的示例的输出:
/* TODO temporary ifdef to facilitate renaming */
#ifdef RENAME_FEATURE_ENABLED
struct BigThing cool_var;
#else
struct BigThing nice_var;
#endif /* RENAME_FEATURE_ENABLED */
/* some lines later */
/* TODO temporary ifdef to facilitate renaming */
#ifdef RENAME_FEATURE_ENABLED
cool_var->thing = 1;
#else
nice_var->thing = 1;
#endif /* RENAME_FEATURE_ENABLED */
请注意,这实际上是用于匹配替换文本的src
正则表达式。awk
我的示例是字符串文字,但不一定如此。
答案2
使用sed
:
/nice_var/!b
h
i\
/* TODO temporary ifdef to facilitate renaming */\
#ifdef RENAME_FEATURE_ENABLED
s//cool_var/gp
i\
#else
g
a\
#endif /* RENAME_FEATURE_ENABLED */
忽略不匹配的行nice_var
(分支到每个此类行的脚本末尾)。对于匹配的行nice_var
,用 将该行存储在保留空间中h
,插入“前缀”( #ifdef ...
),更改nice_var
为cool_var
并打印修改后的行,插入“中缀”( #else
),从保留空间中取出原始行g
(这不实际上并不打印它,隐式打印在最后执行此操作),并附加“后缀”(#endif
)。
测试:
$ cat file.c
struct BigThing nice_var;
/* some lines later */
nice_var->thing = 1;
$ sed -f script file.c
/* TODO temporary ifdef to facilitate renaming */
#ifdef RENAME_FEATURE_ENABLED
struct BigThing cool_var;
#else
struct BigThing nice_var;
#endif /* RENAME_FEATURE_ENABLED */
/* some lines later */
/* TODO temporary ifdef to facilitate renaming */
#ifdef RENAME_FEATURE_ENABLED
cool_var->thing = 1;
#else
nice_var->thing = 1;
#endif /* RENAME_FEATURE_ENABLED */
要参数化sed
脚本,请使用此处文档。请注意,这要求第一个参数是适合用作sed
替换中的模式的 POSIX 基本正则表达式,第二个参数是适合替换命令的替换部分的文本字符串。第三个参数必须是有效的 C 预处理器宏名称。
src=$1
dst=$2
mac=$3
cat >script <<SED_SCRIPT
/$src/!b
h
i\\
/* TODO temporary ifdef to facilitate renaming */\\
#ifdef $mac
s//$dst/gp
i\\
#else
g
a\\
#endif /* $mac */
SED_SCRIPT
sed -f script
测试:
$ sh replacer nice cool HELLO <file.c
/* TODO temporary ifdef to facilitate renaming */
#ifdef HELLO
struct BigThing cool_var;
#else
struct BigThing nice_var;
#endif /* HELLO */
/* some lines later */
/* TODO temporary ifdef to facilitate renaming */
#ifdef HELLO
cool_var->thing = 1;
#else
nice_var->thing = 1;
#endif /* HELLO */
$ sh replacer 'nice_\(var\)' '\1iable' HELLO <file.c
/* TODO temporary ifdef to facilitate renaming */
#ifdef HELLO
struct BigThing variable;
#else
struct BigThing nice_var;
#endif /* HELLO */
/* some lines later */
/* TODO temporary ifdef to facilitate renaming */
#ifdef HELLO
variable->thing = 1;
#else
nice_var->thing = 1;
#endif /* HELLO */