使用sed / awk / tr / perl将字符串替换为小写的子字符串?

4

我有一个明文文件,其中包含多个模式实例$$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 来完成这个任务?

https://dev59.com/0sF4zogBFxS5KdRj9Z6j - Joseph Quinsey
https://dev59.com/3nRB5IYBdhLWcg3wNk53 - Joseph Quinsey
9个回答

3

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

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

确实是个不错的解决方案。但请注意,如果 * 包含换行符,则无法正常工作。 - mschilli

1

这个可以处理复杂的例子。

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

而对于更简单的例子:

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

中,\L表示小写(如果需要,使用\E停止)。

添加了 Perl 的可移植解决方案。 - Gilles Quénot
@anubhava - 它在OSX上不起作用,因为 \L 和 \E 是GNU sed的特性。这个答案适用于大多数Linux环境,但不具有可移植性。 - ghoti
1
我在FreeBSD环境中。 - user457586
@BlueJ774 - 我也是。OSX和FreeBSD使用相同的sed。 - ghoti
@sputnick:您更新的Perl解决方案与OP在半小时之前发布并接受的解决方案有何不同? - mschilli
显示剩余4条评论

1

很遗憾,使用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.

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

好代码。您介意评论一下我的解决方案吗?我认为我用非常少的(g)awk解决了这个问题。它甚至可以在*字符串内部使用换行符。但也许我做错了什么。如果是这样,我想从中学习。 :) - mschilli
问题中的示例输入没有产生预期的输出。 - Ed Morton
对我来说可以。你使用了GNU awk gawk吗?如果我没记错,POSIX awk不支持正则表达式(RE)记录分隔符(RS)。如果你使用gawk进行测试,你得到了什么输出结果,你使用的是哪个版本? - mschilli
是的,我使用 gawk 4.1.1 版本。输出的最后一行是 gibson gibson,没有终止换行符,而不是输出 gibson gibsongibson 并带有一个终止换行符。 - Ed Morton
感谢您的输入。由于最后一条记录的ORS为空,缺少终止换行符。因此,赋值语句被评估为false,未触发打印操作。我通过将赋值语句包装到一个无条件的动作中,并使用1习惯用语添加了一个无条件的print来修复这个问题。然而,对于我来说,$$DATABASE_GIBSON$$$$DATABASE_GIBSON$$部分已经像预期的那样转换为gibsongibson。您能否再次检查您的最新版本是否仍然存在这种情况?我使用的是gawk 4.0.2,所以可能自那时以来有所改变。我稍后会尝试使用最新的gawk。谢谢。 - mschilli
我的错,当我复制/粘贴输入时,我漏掉了最后一个 $。现在它可以工作了,看起来很好。 - Ed Morton

0

仅使用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
$ 

更新:

根据评论,似乎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 还不够好。输入:http://pastebin.com/Q6RvvdcD 输出:http://pastebin.com/66HLeqgt - user457586
这些示例不包含在您的问题中。我回答了发布的问题。 - ghoti
@BlueJ774 - 我已经根据您的新要求更新了我的答案。您可能需要在问题中更明确地表达,以避免混淆。 - ghoti
很好的回答,但即使是您更新后的版本也不能完成问题所要求的功能:它会删除所有输入,而不是将其输出为原样,以便进行小写转换。 - mschilli

0

你可以使用超酷的 cut 命令以相当可靠的方式完成这个任务 :)

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

0
这可能适用于您(GNU sed):
sed 's/$\$/\n/g;s/\nDATABASE_\([^\n]*\)\n/\L\1/g;s/\n/$$/g' file

0
awk '{gsub(/\$\$DATABASE_GIBSON\$\$/,"gibson")}1' file
gibson

test me gibson test me

gibson test gibson test

gibson gibsongibson

0

这是我能想到的最短的(GNU)awk解决方案,它可以完成OP请求的所有操作:

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

即使带星号(*)的字符串中包含一个或多个美元符号($)和/或换行符,此解决方案仍应有效。


-1

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

awk 会接受任何输入,这里是第一个参数,并使用 tolower 函数返回结果。

对于您的 bash 脚本,您可以像这样做并使用变量 DBLOWER

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

这并不是按照OP的要求将$$DATABASE_*$$替换为*。此外,它会将所有输入转换为小写。 - mschilli

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