前几天遇到了一个问题,需要处理一些文本,处理要求如下:
文本每一行中都是相同的两个部分,现在想把第一个部分格式由原来的CamelWord的格式更改为CAMEL_WORD的形式。第二部分不变。如下:
处理前的文档:
HelloWorld HelloWorld
DoSomethingSpecial DoSomethingSpecial
EnglishConditionAction EnglishConditionAction
ThisIsASentenceForTesting ThisIsASentenceForTesting
处理后的文档:
HELLO_WORLD HelloWorld
DO_SOMETHING_SPECIAL DoSomethingSpecial
ENGLISH_CONDITION_ACTION EnglishConditionAction
THIS_IS_A_SENTENCE_FOR_TESTING ThisIsASentenceForTesting
一开始,我是使用vim来做的,不过实在没找到太好的方法,只好先分成两部分。将每一行的第一个部分单独拿到一个文档A当中,然后单独处理那个文档A,再将处理结果合并到只含第二部分的文档B当中。 后来觉着这个方法太费力,所以去求助了CU shell版的大神们,得到了一些比较不错的方法,此处做个记录,以备后查。同时感谢所有提供答案的网友。【此处留待补充更好的vim解决方案】
感谢jason680提供perl解决方法,其命令如下:
perl -lape '$F[0]=~s/([A-Z][a-z]*)/_\U${1}/g;$_="@F";s/^_//' FILE
命令的四个参数先不管,只看表达式里面的部分。整个表达式以";"为界分为3部分(并以第一行的处理为例给出处理前后的文本示例):
第1部分: ’$F[0]=~s/([A-Z][a-z]*)/_\U${1}/g‘ 我的理解是将每一行的第一部分使用s命令替换,将每个大写字母前面加一个下划线(_),并将整个第一部分所有的字母转换为大写。第一行文本处理后 $F[0] 的内容为:_HELLO_WORLD
第2部分:$_="@F" 将数组的内容赋给$_(Perl的默认输入/输出), $_的内容为:_HELLO_WORLD HelloWorld
第3部分: s/^_// 将开头的下划线(_)删除(因为没有指定对谁做替换处理,所以默认为$_), 之后$_的内容为 HELLO_WORLD HelloWorld
有多位网友提供sed的解决方法,分别如下:
a. 感谢yinyuemi 提供,命令如下:
sed -r ':a;s/([[:upper:]][[:lower:]]*)([[:upper:] ].+)\1/\1\n\2\1/;ta;:b;s/([^\n ]+)\n(.+)/\U\1_\E\2/;tb;s/_ / /' file
我暂时还没有仔细分析这个命令表达式,会在之后补充说明。
b. 感谢liion631818提供,命令如下:
sed -r 's/ /\n/; h;s/[A-Z]/_&/g;s/.*/\U&/;s/^_//g;G;s/\n.*\n/ /' file
对每一行(以第一行为示例):
将空格替换为'\n', (HelloWorld\nHelloWorld)
将替换后的内容存储到hold space当中
将pattern space当中的所有大写字母前添加下划线(_) (_Hello_World\n_Hello_World)
将pattern space当中的所有字符替换为大写;(_HELLO_WORLD\n_HELLO_WORLD)
之后将hold space当中的内容追加到pattern space当中(_HELLO_WORLD\n_HELLO_WORLD\nHelloWorld\nHelloWorld)
将开头的下划线删除(HELLO_WORLD\n_HELLO_WORLD\nHelloWorld\nHelloWorld)
将第一个‘\n'到最后一个'\n'之间的内容替换为空格(HELLO_WORLD HelloWorld)
同样有多位网友提供awk的解决方法,分别如下:
a. 感谢聆雨淋夜提供,命令如下:
awk '{$1=gensub(/[[:upper:]]/,"_&","g",$1);$1=toupper($1);sub(/_/,"")}1' file
将第一个字段中的所有大写字母前加上下划线,并重新赋给$1, 即(_Hello_World HelloWorld)
将$1的内容全部转为大写,即(_HELLO_WORLD HelloWorld)
删除$0的第一个下划线,即(HELLO_WORLD HelloWorld)
b. 感谢zxy877298415提供,命令如下:
awk '{split($1,a,"");printf a[1];for(i=2;i<=length(a);i++){if(a[i]~/[[:upper:]]/) {printf "_"a[i]} else {printf toupper(a[i])}}print " "$2}' file
将第一个字段分割为单个字符组成的数组a
打印a数组的第一个元素
从a数组的第二个元素开始,如果是大写字母,那么在其前面加前导下划线后输出;如果不是大写字母,将转为大写字母输出
循环完成后,打印空格和第二个字段
c.感谢 LikeLx提供,命令如下:
awk '{gsub(/\B[[:upper:]]/,"_&",$1);print toupper($1),$2}' file
命令大致说明:
跟a方法当中方法基本一致,但是此处使用\B这个来界定第一个字段当中仅仅那些前面有字符的大写字母我们才给其加上下划线,行首的字符即使是大写字符,但是由于不满足\B这个条件,所以不再添加下划线。这样就避免了,我们全部添加下划线后,在使用sub去删除的问题。
【2015.01.10 16:13更新】
终于想到了一个vim解决方案,虽然应该也还是不完美,但是可以实现了,不过需要两个命令来实现,这个应该可以录制宏或者使用vim脚本的形式来完成。
%s:\(\w\)\@<=\(\([A-Z][a-z]*\)\+ \)\@=:_:g
%s:.* \ze.*:\U&:
第一条命令:
匹配符合条件的位置: 前面有字符,后面是大写字母,并在该位置添加下划线(_), 另外为保证仅仅匹配空格前的部分,对于第二部分不影响,所以在
\(\([A-Z][a-z]*\)\+ \)
处"+"号后面有一个空格
第二条命令,是将空格前面的全都转为大写字母。