【问题标题】:Replace string with substring in lowercase using sed / awk / tr / perl?使用sed / awk / tr / perl将字符串替换为小写的子字符串?
【发布时间】:2012-10-15 22:33:36
【问题描述】:

我有一个包含多个$$DATABASE_*$$ 模式实例的纯文本文件,星号可以是任何字符串。我想用星号部分中的任何内容替换整个实例,但要小写。

这是一个测试文件:

$$DATABASE_GIBSON$$

test me $$DATABASE_GIBSON$$ test me

$$DATABASE_GIBSON$$ test $$DATABASE_GIBSON$$ test

$$DATABASE_GIBSON$$ $$DATABASE_GIBSON$$$$DATABASE_GIBSON$$

这是所需的输出:

gibson

test me gibson test me

gibson test gibson test

gibson gibsongibson

如何使用 sed/awk/tr/perl 做到这一点?

【问题讨论】:

标签: perl sed awk tr


【解决方案1】:

这个适用于复杂的例子。

perl -ple 's/\$\$DATABASE_(.*?)\$\$/lc($1)/eg' filename.txt

还有更简单的例子:

echo '$$DATABASE_GIBSON$$' | sed 's@$$DATABASE_\(.*\)\$\$@\L\1@'

中,\L 表示小写(\E 在需要时停止)

【讨论】:

  • 不完全。我正在使用这个测试文件:pastebin.com/Q6RvvdcD 输出如下所示:pastebin.com/CBe0Mehb
  • 添加了 perl 便携解决方案。
  • 使用与上面相同的输入文件,使用 perl,我得到了这个:pastebin.com/y2uFq1Xk 那一个严重搞乱了格式化和删除的东西。
  • @anubhava - 它在 OSX 上不起作用,因为 \L 和 \E 是 GNU sed 主义。此答案适用于大多数 Linux 环境,但不可移植。
  • 仅供参考,我在 FreeBSD 环境中。
【解决方案2】:

echo $$DATABASE_WOOLY$$ | awk '{print tolower($0)}'

awk 将接受任何输入,在本例中为第一个参数,并使用 tolower 函数并返回结果。

对于您的 bash 脚本,您可以执行类似的操作并使用变量 DBLOWER

DBLOWER=$(echo $$DATABASE_WOOLY$$ | awk '{print tolower($0)}');

【讨论】:

  • 这不是按照 OP 的要求将 $$DATABASE_*$$ 替换为 *。它还将 all 输入转换为小写。
【解决方案3】:

单独使用 awk:

> echo '$$DATABASE_AWESOME$$' | awk '{sub(/.*_/,"");sub(/\$\$$/,"");print tolower($0);}'
awesome

请注意,我使用的是 FreeBSD,所以这不是 GNU awk。

但这可以单独使用 bash 来完成:

[ghoti@pc ~]$ foo='$$DATABASE_AWESOME$$'
[ghoti@pc ~]$ foo=${foo##*_}
[ghoti@pc ~]$ foo=${foo%\$\$}
[ghoti@pc ~]$ foo=${foo,,}
[ghoti@pc ~]$ echo $foo
awesome

在上述替换中,除了最后一个 (${foo,,}) 之外的所有替换都将在标准 Bourne shell 中工作。如果您没有 bash,则可以在此步骤中使用 tr

$ echo $foo
AWESOME
$ foo=$(echo "$foo" | tr '[:upper:]' '[:lower:]')
$ echo $foo
awesome
$ 

更新

每个 cmets,似乎 OP 真正想要的是从 任何包含它的文本中去除子字符串——也就是说,我们的解决方案需要考虑在他在问题中提供的字符串之前或之后出现前导或尾随空格的可能性。

> echo 'foo $$DATABASE_KITTENS$$ bar' | sed -nE '/\$\$[^$]+\$\$/{;s/.*\$\$DATABASE_//;s/\$\$.*//;p;}' | tr '[:upper:]' '[:lower:]'
kittens

如果您的路径上碰巧有pcregrep(来自devel/pcre FreeBSD 端口),您可以使用它来代替,并带有前瞻:

> echo 'foo $$DATABASE_KITTENS$$ bar' | pcregrep -o '(?!\$\$DATABASE_)[A-Z]+(?=\$\$)' | tr '[:upper:]' '[:lower:]'
kittens

(对于阅读本文的 Linux 用户:这相当于使用 grep -P。)

在纯 bash 中:

$ shopt -s extglob
$ foo='foo $$DATABASE_KITTENS$$ bar'
$ foo=${foo##*(?)\$\$DATABASE_}
$ foo=${foo%%\$\$*(?)}
$ foo=${foo,,}
$ echo $foo
kittens

请注意,这三个更新的解决方案中没有一个可以处理同一行输入中存在多个标记数据库名称的情况。这也不是问题中的要求,但我只是说......

【讨论】:

  • 关闭,但不完全使用 awk。输入:pastebin.com/Q6RvvdcD 输出:pastebin.com/66HLeqgt
  • 您的问题中不包含这些示例。我回答了发布的问题。
  • @BlueJ774 - 根据您的新要求更新了我的答案。您可能想要更明确的in your question 以避免混淆。
  • 不错的答案,但即使您的更新版本也不能满足问题的要求(当前版本):它将删除所有输入 not 以转换为小写而不是按原样输出。
【解决方案4】:

这是我最终使用的 perl 版本。

perl -p -i.bak -e 's/\$\$DATABASE_(.*?)\$\$/lc($1)/eg' inputFile

【讨论】:

  • 确实不错的解决方案。但是请注意,如果 * 包含换行符,它将不起作用。
【解决方案5】:

不幸的是,使用 awk 没有简单、万无一失的方法,但这里有一种方法:

$ cat tst.awk
{
   gsub(/[$][$]/,"\n")

   head = ""
   tail = $0

   while ( match(tail, "\nDATABASE_[^\n]+\n") ) {
      head = head substr(tail,1,RSTART-1)
      trgt = substr(tail,RSTART,RLENGTH)
      tail = substr(tail,RSTART+RLENGTH)

      gsub(/\n(DATABASE_)?/,"",trgt)

      head = head tolower(trgt)

   }

   $0 = head tail

   gsub("\n","$$")

   print
}

$ cat file
The quick brown $$DATABASE_FOX$$ jumped over the lazy $$DATABASE_DOG$$s back.
The grey $$DATABASE_SQUIRREL$$ ate $$DATABASE_NUT$$s under a $$DATABASE_TREE$$.
Put a dollar $$DATABASE_DOL$LAR$$ in the $$ string.

$ awk -f tst.awk file
The quick brown fox jumped over the lazy dogs back.
The grey squirrel ate nuts under a tree.
Put a dollar dol$lar in the $$ string.

注意将 $$ 转换为换行符的技巧,这样我们就可以在 match(RE) 中否定该字符,没有那个(即,如果我们使用“.+”而不是“[^\n]+”)然后由于贪婪的 RE 匹配,如果相同的模式在一个输入行上出现两次,匹配字符串将从第一个模式的开头延伸到第二个模式的结尾。

【讨论】:

  • 不错的代码。你介意评论my solution吗?我想我用很少 (g)awk 解决了这个问题。它甚至应该与 * 字符串中的换行符一起使用。但也许我做错了什么。在这种情况下,我想从中吸取教训。 :)
  • 它不会从问题中的示例输入中产生预期的输出。
  • 对我来说确实如此。你用过 GNU awk gawk 吗? IIRC,POSIX awk 不支持正则表达式 (RE) 记录分隔符 (RS)。如果您使用gawk 进行测试,您得到的输出是什么,您使用的是哪个版本?
  • 是的,我使用 gawk 4.1.1。输出的最后一行是gibson gibson,没有终止换行符,而不是gibson gibsongibson,有终止换行符。
  • 感谢您的意见。由于在最后一条记录的情况下ORS 为空,因此缺少终止换行符。因此,分配评估为假,不触发打印。我通过将分配包装到一个无条件的 action 并使用1 成语添加一个无条件的print 来解决这个问题。但是,$$DATABASE_GIBSON$$$$DATABASE_GIBSON$$ 部分按照我的预期被转换为gibsongibson。您能否再次检查我的最新版本是否仍然不适合您?我在gawk 4.0.2 上,所以也许从那以后某事发生了变化。我将在今天晚些时候尝试最近的gawk。谢谢。
【解决方案6】:

您可以使用 supercool 命令 cut 以一种非常简单的方式做到这一点 :)

echo '$$DATABASE_AWESOME$$' | cut -d'$' -f3 | cut -d_ -f2 | tr 'A-Z' 'a-z'

【讨论】:

    【解决方案7】:

    这可能对你有用(GNU sed):

    sed 's/$\$/\n/g;s/\nDATABASE_\([^\n]*\)\n/\L\1/g;s/\n/$$/g' file
    

    【讨论】:

      【解决方案8】:

      这是我能想到的最短 (GNU) awk 解决方案,它可以满足 OP 的要求:

      awk -vRS='[$][$]DATABASE_([^$]+[$])+[$]' '{ORS=tolower(substr(RT,12,length(RT)-13))}1' 
      

      即使用星号 (*) 指示的字符串包含一个或多个单个美元符号 ($) 和/或换行符,这个灵魂仍然应该有效。

      【讨论】:

        【解决方案9】:
        awk '{gsub(/\$\$DATABASE_GIBSON\$\$/,"gibson")}1' file
        gibson
        
        test me gibson test me
        
        gibson test gibson test
        
        gibson gibsongibson
        

        【讨论】:

          猜你喜欢
          • 2017-02-08
          • 2015-01-13
          • 1970-01-01
          • 1970-01-01
          • 2020-04-10
          • 2020-02-18
          • 2019-08-07
          • 2014-06-20
          • 2012-12-21
          相关资源
          最近更新 更多