【问题标题】:awk sed or regex insert substring and change caseawk sed 或正则表达式插入子字符串并更改大小写
【发布时间】:2011-08-13 05:04:04
【问题描述】:

我正在对一个制表符分隔的文件进行一些转换,其中一列包含这样的分层标识符:

VI.d5.5
VII.b2.1
VII.b2.2
VII.b2.3
VII.c1

我需要将其转换为如下所示,在第一个点组和第二个点组之间插入一个大写字母:

VI.D.d5.5
VII.B.b2.1
VII.B.b2.2
VII.B.b2.3
VII.C.c1

我知道sed 中的\U 标志,但我不知道如何只应用一次。例如,以下将插入的字母和原来的小写字母都大写:(不想要的)

echo 'VII.b1.1' | sed -e 's/\([a-h]\)/\U\1.\1/'
VII.B.B1.1

我欢迎任何允许我在制表符分隔文件中修改此列的 shell(sed、awk、perl 等)或 vim 解决方案。

【问题讨论】:

    标签: regex bash vim sed awk


    【解决方案1】:

    您是否尝试过\u 而不是\U?根据 sed 信息页面 (info sed):

    `\U'
         Turn the replacement to uppercase until a `\L' or `\E' is found,
    
    `\u'
         Turn the next character to uppercase,
    

    【讨论】:

    • standard sed中没有这种东西。
    • 我在“标准 sed”中也找不到\U,但是,鉴于问题的作者正在尝试使用它,我将假设他们正在使用 GNU sed,确实有\u
    • @photoionized:我找到了一个安装了ɢɴᴜ sed 的 Linux 系统,但无法让它在我的数据集上正常运行。在我展示的第二个数据集中,它一直没有对我的一封信进行大小写映射。
    • @tchrist:我不确定您指的是哪个数据集。我现在使用的盒子上的sed 似乎可以很好地处理 utf8 和标准拉丁字符。我查看了 GNU 源代码,看起来 \u 选项可能已添加到 sed >= 4.0 中,这应该是 2000 年之后的几乎所有 Linux OS 系统。难道你有一些旧版本的实用程序?
    • @photoionized:问题是\u 能够很好地更改小写“ç”和“ð”,但它完全忽略了“ß”。这告诉我它只能做 Unicode 所称的 simple casemapping,而不是 full casemapping。我不知道为什么会受到这样的限制;可能有人只是不知道更好。这是在“GNU sed 版本 4.2.1”下。我应该使用更新的版本吗?
    【解决方案2】:
    sed -e 's/\.[a-z]/\U&\E&/'
    

    Perl 也很好用:

    perl -pe 's/\.[a-z]/uc($&) . $&/e'
    

    【讨论】:

    • perl 中实际上不需要 s///e,因为它首先从 vi 中借用了大小写映射转义符。所以perl -pe 's/\.[a-z]/\U$&\E$&/' 的工作方式相同。顺便说一句,如果你将像这样匹配真实字母,比枚举集合[a-z] 更好的方法是使用\pL 快捷方式(它是\p{Letter} 的别名),它匹配任何字符字母属性。还有更高级的属性,如\p{Lower}\p{Cased}。如果你真的想要它们,甚至是\p{Changes_When_Uppercased}。取决于你真正想说的是什么。
    【解决方案3】:

    你不能在 standard sed(1) 中这样做,因为那里没有 \u\U 这样的东西。事实上,在我所有的系统(除了一个)上,它都失败了——而且默默地,唉!我在我的 Mac 笔记本电脑和我的 Mac 台式机上都尝试了 sed 版本,然后我在我们的 Solaris 服务器和我们的 OpenBSD 服务器上尝试了它。我也在单独的 AIX 机器上尝试过,当然它在那里不起作用。 :(

    但是,您应该能够以这种方式进行移植,这适用于我测试的那些系统:

    % cat sample
    VI.d5.5                                                                           
    VII.b2.1
    VII.b2.2
    VII.b2.3
    VII.c1
    
    % perl -wpe 's/([^.]+)\.(.)/$1.\u$2.$2/' /tmp/sample 
    VI.D.d5.5
    VII.B.b2.1
    VII.B.b2.2
    VII.B.b2.3
    VII.C.c1
    

    这不仅更便携,而且更容易。

    这应该适用于过去 20 年发布的任何 Perl 版本,包括 perl4。但是,如果您生活在最前沿,因此至少安装了 5.10,那么您可以改用这种方式:

    % perl -M5.10.0 -wpe 's/[^.]+\.\K(?=(.))/\u$1./' /tmp/sample
    VI.D.d5.5
    VII.B.b2.1
    VII.B.b2.2
    VII.B.b2.3
    VII.C.c1
    

    ‑M5.10.0 只是为了确保您确实拥有并加载了 5.10 功能集。

    Unicode 呢?

    现在假设您的示例数据中包含 Unicode:

    % cat /tmp/sample.utf8
    Ⅵ.ð5.5
    Ⅷ.ß2.3
    Ⅺ.ç1
    
    % uniquote /tmp/sample.utf8 
    \N{U+2165}.\N{U+F0}5.5
    \N{U+2167}.\N{U+DF}2.3
    \N{U+216A}.\N{U+E7}1
    
    % uniquote -v /tmp/sample.utf8
    \N{ROMAN NUMERAL SIX}.\N{LATIN SMALL LETTER ETH}5.5
    \N{ROMAN NUMERAL EIGHT}.\N{LATIN SMALL LETTER SHARP S}2.3
    \N{ROMAN NUMERAL ELEVEN}.\N{LATIN SMALL LETTER C WITH CEDILLA}1
    

    我可以向您保证,您不会找到在该数据上执行正确操作的 sed 版本。它会搞砸的。我去了我们牺牲的 Linux 机器,虽然他们在那里使用的 ɢɴᴜsed 可以处理您的示例数据,但它拒绝在我更高级的 Unicode 数据集中对其中一个字符进行映射,即使我已经正确设置了区域设置。但是perl 版本仍然做对了。

    但是使用 perl,只需添加 ‑CSD 命令行选项来告诉 perl 数据文件和 std{in,out,err} 都是 UTF-8 格式,然后运行相同的命令,你会看到真的 Qᴜɪᴛᴇ Iɴᴛᴇʀᴇsᴛɪɴɢ

    % perl -CSD -wpe 's/([^.]+)\.(.)/$1.\u$2.$2/' /tmp/sample.utf8
    Ⅵ.Ð.ð5.5
    Ⅷ.Ss.ß2.3
    Ⅺ.Ç.ç1
    
    % perl -CSD -wpe 's/[^.]+\.\K(?=(.))/\u$1./' /tmp/sample.utf8
    Ⅵ.Ð.ð5.5
    Ⅷ.Ss.ß2.3
    Ⅺ.Ç.ç1
    
    % perl -CSD -wpe 's/[^.]+\.\K(?=(.))/\U$1./' /tmp/sample.utf8
    Ⅵ.Ð.ð5.5
    Ⅷ.SS.ß2.3
    Ⅺ.Ç.ç1
    

    如您所见,\u 所做的 titlecasing\U 所做的 uppercasing 之间存在差异。这是因为小写字母“ß”在标题中是“Ss”,但在大写中是“SS”。离奇但真实!诚然,希腊字母比我们使用的拉丁字母更容易发生这种事情,但你仍然想把它做好。

    这里是all uniquoted,所以你可以看到我们正在谈论的代码点:

    % perl -CSD -wpe 's/[^.]+\.\K(?=(.))/\u$1./' /tmp/sample.utf8 | uniquote
    \N{U+2165}.\N{U+D0}.\N{U+F0}5.5
    \N{U+2167}.Ss.\N{U+DF}2.3
    \N{U+216A}.\N{U+C7}.\N{U+E7}1
    
    % perl -CSD -wpe 's/[^.]+\.\K(?=(.))/\u$1./' /tmp/sample.utf8 | uniquote -v
    \N{ROMAN NUMERAL SIX}.\N{LATIN CAPITAL LETTER ETH}.\N{LATIN SMALL LETTER ETH}5.5
    \N{ROMAN NUMERAL EIGHT}.Ss.\N{LATIN SMALL LETTER SHARP S}2.3
    \N{ROMAN NUMERAL ELEVEN}.\N{LATIN CAPITAL LETTER C WITH CEDILLA}.\N{LATIN SMALL LETTER C WITH CEDILLA}1
    

    这样是不是很酷?

    【讨论】:

    • 感谢您的详细分析。对我来说,这是一次性的数据转换,绝对不包含 unicode,但这是有用的信息。
    • @Michael:当然。使用手头的任何东西。我只是想为您提供更多替代方案,以便您可以稍后再查看,如果您需要更灵活的解决方案。我在我的 Mac 笔记本电脑和台式机以及我们的 ʙsᴅ 服务器上都尝试了sed,但它们都没有花哨的 ɢɴᴜ 版本,尽管它们都带有 perl 标准。我发现一个有ɢɴᴜsed 的Linux 系统,但发现Linux 有一个非常幼稚的Unicode 概念。供应商区域设置只是如此狡猾,你知道吗?永远不知道他们会对你做什么。
    • +1 以获得额外的信息来消化,但对手头的实际问题有点过时。 OT意味着它不遵循GIGO的故障排除规则。试图考虑超出 OP 帖子的“假设”情况,你会发疯的;)
    • @Crayon 我刚开始使用一个简单的perl 单线,因为我无法让他的sed 版本在我可以轻松使用的任何系统上工作——就像 5其中,没有一个上面有ɢɴᴜsed!然后我开始偷懒,结果出现了严重的漏尿症。发生这种情况时我就有了。 :)
    【解决方案4】:

    尝试使用 \u 而不是 \U 将下一个字符变为大写。但是如果你想使用 \U 那么你必须用 \E 或 \L 来停止大写,就像

    's/\([a-h]\)/\U\1\E.\1/'

    【讨论】:

    • 谢谢 - 我不知道\u
    • @Michael: \u\Unonstandard extensions to sed,尽管它们在 Perl 中是标准的。尝试在下面给出的我的数据集上运行sed 版本。它不能正常工作。
    • @tchrist:我同意这是一个非标准扩展,但是 OP 说 \U 正在“工作”,但不仅仅停留在一个字符上,这意味着他不知道 \ E 或 \u,但更重要的是,暗示他确实安装了适当的扩展名。因此,如果“可移植性”对他来说不是问题,那么使用 \u 或 \U 就可以了。
    【解决方案5】:
    sed -e 's/\([^.]\+\)\.\(.\)/\1.\u\2\.\2/'
    

    像这样:

    $ sed -e 's/\([^.]\+\)\.\(.\)/\1.\u\2\.\2/' <<<'VI.d5.5'
    VI.D.d5.5
    

    【讨论】:

    • 那行不通。 echo 'VI.d5.5' | sed -e 's/\([^.]\+\)\.\(.\)/\1.\u\2\.\2/' 产生 'VI.d5.5'。但是,echo 'VI.d5.5' | perl -pe 's/([^.]+)\.(.)/$1.\u$2\.$2/' 会产生 'VI.D.d5.5'
    • 可能sed不一样,我的例子是从shell复制过来的。
    【解决方案6】:

    这是awk 解决方案。不需要凌乱的正则表达式。基本思路:在点上拆分,获取第二个字段的第一个字符。然后使用 toupper() 函数改变它的大小写。最后,换回第二场。

    awk -F"." '{
        ch = toupper(substr($2,1,1))
        $2=ch"."$2
    }1' OFS="." file
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-13
      • 2021-07-31
      • 1970-01-01
      相关资源
      最近更新 更多