使用sed命令去除下划线并提升字符

7

我正在尝试将一些代码从旧的命名方案迁移到新的命名方案,旧的命名方案为:

int some_var_name;

新的变化是

int someVarName_:

我希望能有一种类似于sed/正则表达式的好办法来简化这个过程。基本上需要做的是:
查找包含下划线的小写单词,将下划线替换为无,并将下划线右侧的字符转换为大写。在此之后,将_附加到匹配项的末尾。

是否可以使用Sed和/或Awk和regex实现此操作?如果不行,为什么?

任何示例脚本都将不胜感激。

非常感谢您的任何帮助。

编辑:
为了更清晰地表述,重命名是针对许多文件编写错误的命名约定,需要将它们与其余代码库保持一致。不希望这样完美地替换所有内容,以便使其保持可编译状态。相反,该脚本将运行,然后手动检查任何异常情况。替换脚本纯粹是为了减轻手工纠正所有错误的负担,我相信您会同意这非常繁琐。


代码中除了变量名中的下划线外,还有其他下划线吗? - drfrogsplat
在编程中,例如SOME_CONSTANT这样的常量不应该受到任何更改的影响。 - radman
3个回答

5

sed -re 's,[a-z]+(_[a-z]+)+,&_,g' -e 's,_([a-z]),\u\1,g'

解释:

这是一个带有2个表达式的sed命令(每个在引号中,在-e后面)。s,,,g是全局替换。通常情况下,您会看到它是用斜杠而不是逗号,但我认为当您在模式中使用反斜杠(而没有逗号)时,这更容易阅读。尾随的g(代表“全局”)表示将此替换应用于每行上的所有匹配项,而不仅仅是第一个。

第一个表达式将在由小写单词([a-z]+)组成的每个令牌后附加一个下划线,后跟一些由下划线分隔的小写单词((_[a-z]+)+)组成的非零数量。我们将其替换为&_,其中&表示“与之匹配的所有内容”,_只是一个字面下划线。因此,总体而言,该表达式是要求在每个下划线分隔的小写标记末尾添加下划线。

第二个表达式匹配模式_([a-z])),其中括号之间的所有内容都是捕获组。这意味着我们可以稍后将其称为\1(因为它是第一个捕获组。如果有更多,则为\2\3等等)。因此,我们要求匹配下划线后面的小写字母,并记住该字母。

我们将其替换为\u\1,这是我们刚刚记住的字母,但通过\u变成大写。

此代码不会聪明地避免munging #include行或类似的内容;它将替换每个下划线后面的小写字母与其大写等效项。


顺便提一下,sed -i $filename 是你调用 sed 在原地编辑 $filename 的方式。所以你可以这样做,例如:"sed -i -r -e ... *.c" - Vineet
感谢你的回答,Vineet。你第一个提供了可行的解决方案,并且它完全按照要求工作。此外,对于Sed命令的功能清晰解释也值得赞扬。 - radman

3
几年前,我成功地将一个23年历史的老代码库(总代码量为300,000行)转换为驼峰命名法。这只花费了两天时间,但还有一些剩余问题需要几个月才能解决。这种做法是让你的同事程序员非常恼火的好方法。
我认为简单、笨拙、类似 sed 的方法有其优势。在我的认知范围内,基于 IDE 的工具等不能做到以下几点:
- 不能修改没有通过 #ifdef 编译的代码。 - 不能修改注释中的代码。
而且该遗留代码必须在多个编译器/操作系统平台上进行维护(涉及大量 #ifdef 语句)。
不过,这种笨拙的 sed 类方法的主要缺点在于可能会无意中更改字符串(如关键字)。并且我只尝试过用于 C 语言,C++ 可能是另外一回事。
大致分为五个阶段:
1) Generate a list of tokens that you wish to change, and manually edit.
2) For each token in that list, determine the new token.
3) Apply these changes to your code base.
4) Compile.
5) Double-check via a manual diff, and do a final clean-up.

第一步,生成您想更改的令牌列表。命令如下:
cat *.[ch] | sed 's/\([_A-Za-z0-9][_A-Za-z0-9]*\)/\nzzz \1\n/g' | grep -w zzz | sed 's/^zzz //' | grep '_[a-z]' | sort -u > list1

将会在list1中产生:

st_atime
time_t
...

在这个示例中,您真的不想改变这两个令牌,因此请手动编辑列表以将它们删除。但是您可能会错过一些,所以为了本例的缘故,假设您保留这些。
第二步是生成一个脚本来进行更改。例如,使用以下命令:
cat list1 | sed 's/\(.*\)/glob_sub "\\<\1\\>" xxxx_\1/;s/\(xxxx_.*\)_a/\1A/g;s/\(xxxx_.*\)_b/\1B/g;s/\(xxxx_.*\)_a/\1C/g;s/\(xxxx_.*\)_t/\1T/g' | sed 's/zzz //' > list2

将 _a、_b、_c 和 _t 更改为 A、B、C 和 T,从而产生:

glob_sub "\<st_atime\>" xxxx_stAtime
glob_sub "\<time_t\>" xxxx_timeT

您只需要扩展它以涵盖 d、e、f、...、x、y、z,

我假设您已经为您的开发环境编写了类似于“glob_sub”的函数。(如果没有,请放弃。)我的版本(csh,Cygwin)如下:

#!/bin/csh
foreach file (`grep -l "$1" */*.[ch] *.[ch]`)
  /bin/mv -f $file $file.bak
  /bin/sed "s/$1/$2/g" $file.bak > $file
end

我的一些sed不支持--in-place选项,所以我必须使用mv命令。

第三步是将list2中的脚本应用于您的代码库。例如,在csh中使用source list2命令。

第四步是编译。编译器(希望如此!)会抱怨xxxx_timeT。实际上,它可能会对只有timeT抱怨,但额外的xxx_可以增加保险。所以对于time_t,您犯了一个错误。可以使用以下命令撤消:

glob_sub "\<xxxx_timeT\>" time_t

第五步也是最后一步是使用您喜欢的差异工具进行手动检查更改,然后通过删除所有不需要的xxx_前缀来进行清理。使用grep命令查找"xxx_也有助于检查字符串中的标记。 (实际上,添加_xxx后缀可能是个好主意。)

+1 显示如何使用 sed 实际构建合适的解决方案。请注意,手动过滤此列表以取消所有您不想要替换的标识符可能比选择要替换的所有标识符更耗时。 - stinky472
@stinky472:感谢您的评论。我是从五年前回忆起来的。我意识到我遗漏了一个关键点。像time_t这样的问题是微不足道的——这是C语言,而不是BOOST。相反,它是用于消息传递的第三方头文件,每隔几个月就会更改。所以我们不能碰它们。但是我们运行了第一个脚本来识别不应更改的标记,然后使用uniq -u获取集合差异:cat a b b | sort | uniq -u给出a-b。您还可以将其应用于/usr/include/以摆脱time_t。 - Joseph Quinsey
如果您使用最新的gnu sed,在第二步中,而不是进行26次_a到A、_b到B等转换,您可以使用s/\\(xxxx_.*\\)_\\([a-z]\\)/\1\u\2/g将_x更改为X,其中x从a到z。 - Joseph Quinsey

3
考虑使用sed来搜索和替换所有类似这样的文本。如果没有C++分词器来识别标识符(特别是你的标识符而不是标准库中的标识符),你就会遇到麻烦。push_back被重命名为pushBack_,map::insert被重命名为map::insert_,map被重命名为map_,basic_string被重命名为basicString_,printf被重命名为printf_(如果你使用C库)等等。如果你不加区分地进行操作,你将会陷入困境。
我不知道是否存在任何现有工具可以自动将some_var_name重命名为someVarName_,而不会出现上述问题。人们可能会对这篇文章投反对票,因为他们不理解我在这里的意思。我并不是说sed做不到,我只是说直接使用它不能给你想要的结果。解析器需要上下文信息才能正确地执行此操作,否则它将替换更多不应该替换的内容。
如果它能识别哪些标记是标识符(特别是你的标识符),那么编写一个能够执行此操作的解析器是可能的(例如使用sed),但我怀疑是否有一个专门用于你想要做的事情的工具,可以直接完成这个任务而不需要手动修改(尽管我可能是错的)。以这种方式对所有文本进行简单的搜索和替换本质上是有问题的。
然而,Visual AssistX(可以选择替换文档中的实例)或任何其他能够智能重命名标识符的重构工具,可以在每个实例中至少减轻重构代码的负担。如果您有一个名为some_var_name的符号,并且它在系统中的一千个不同位置被引用,使用VAssistX,您只需使用一个重命名函数就可以智能地重命名所有引用(这不仅仅是文本搜索和替换)。查看Visual Assist X的重构功能

使用VAX以这种方式重构100个变量可能需要15分钟到半小时(如果您使用快捷键则更快),但肯定比使用像其他答案中描述的sed的文本搜索和替换,并替换所有不应该被替换的代码要好得多。

[主观]顺便说一句:如果你问我,下划线在驼峰命名法中仍然不合适。小驼峰命名约定应该使用小驼峰命名法。有很多有趣的论文讨论了这个问题,但至少你的约定是一致的。如果一致,那么与像fooBar_Baz这样的荒唐程序员写的需要特殊例外规则的东西相比,这是一个巨大的优势。[/subjective]


为了澄清所示的命名约定是针对成员变量的,末尾的下划线是为了将它们标识为这样的变量。我更喜欢这种方式而不是m_varName或_varName。此外,我已经使用QT Creator具备了重构能力,但我仍然不喜欢手动更改100个左右的变量。 - radman
不幸的是,这是我所知道的使用现有工具进行此操作的唯一可靠方法。您不能仅仅使用sed或任何其他通用正则表达式解析器随意搜索和替换源文件,否则会替换更多不想替换的内容,这通常比使用像VAX这样的重构工具有选择地重命名所有内容更耗时。 - stinky472
+1 我同意你的观点,sed 是很危险的。需要付出更多的努力。 - Joseph Quinsey
@radman 啊,谢谢,我很高兴你找到了一个可行的解决方案!我想这取决于代码库。在我的特定情况下,我以前已经根据不断变化的编码标准重构过代码,但我们的系统由数千个源文件和大量外部库组成:平台特定库、OpenGL库(包括glew、FBX、OpenImageIO、boost、C++标准库、C标准库等)。对于我们的情况,误报的数量将会非常巨大,因此使用VAX进行有选择性的重构更加安全和少繁琐。 - stinky472
@stinky472:我看到原帖中只提到了“大约100个变量”。如果是这样的话,使用重构工具手动更改这些变量只需要两个小时,每分钟一个更改速度。或者,使用sed然后修复错误将会是“相对轻松”的。无论如何,我的“两天”解决方案都是无用的。 - Joseph Quinsey
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接