我有"|"
分隔文本数据,并且想要转换列值
$ cat infile
Mark|father
Jason|SOn
Jose|son
Steffy|daugHter
我想不敏感地搜索(父亲|儿子|女儿)的情况,并将任何父亲的情况替换为父亲,任何儿子的情况替换为儿子,任何女儿的情况替换为女儿
所以输出文件应该是这样的
$ cat outfile
Mark Father
Jason Son
Jose Son
Steffy Daughter
我正在尝试 IGNORECASE 与 sub 或 gsub 的不同组合,但它会按 infile 中的方式打印所有条目
答案1
这是一个尝试回答问题的原始版本。此后,要求发生了变化。
这是 GNU 实现所sed
擅长的一件事:
$ sed -E 's/(^|\s)(son|daughter|father)(\s|$)/\1\L\u\2\3/i' < file
Mark Father
Jason Son
Jose Son
Steffy Daughter
正则表达式匹配这 3 个单词中的任何一个,但前提是它们前面或后面都没有非空格。
\L
将整个单词转为小写,\u
仅将第一个字符转为大写(这些来自ex
70vi
年代,但不幸的是没有达到标准sed
)。
同样可以使用perl -pe
而不是sed -E
(使它可能更便携,因为更多的系统perl
比 GNU sed
),尽管perl
你可以将其简化为:
perl -pe 's/(?<!\S)(son|daughter|father)(?!\S)/\L\u$&/i'
也就是说,使用负环视运算符来确保这些字符串不是较长的空格分隔单词的一部分(例如Jason
在您的输入中)。另请参见sed 中的\b
inperl
和字边界运算符,但这些运算符更像是将孙子变成孙子,因为不是单词组成字符。\<
\>
(?!\w)
-
每行最多只能替换一次。要替换所有出现的情况,您可以将g
标志添加到上面的标志中perl
。将其添加到sed
one 可能会丢失一些,因为在 a 上Mark son SON sOn
,第一个匹配项将替换" son "
为" Son "
,然后sed
将在 处继续搜索"SON sOn"
,因此不会找到\s
之前的匹配项SON
。这可以通过预先将所有空白字符加倍并随后恢复来解决:
sed -E 's/\s/&&/g
s/(^|\s)(son|daughter|father)(\s|$)/\1\L\u\2\3/ig
s/(\s)\1/\1/g'
尽管这开始有点太复杂了。
答案2
为了提高效率和鲁棒性,我会使用哈希查找而不是正则表达式比较和 *sub() (如果您决定使用包含正则表达式元字符或反向引用的字符串,或者可以是其他字符串的子字符串):
$ cat tst.awk
BEGIN {
FS = "|"
split("Father|Son|Daughter",tmp)
for (i in tmp) {
map[tolower(tmp[i])] = tmp[i]
}
}
{ lc = tolower($2) }
lc in map {
$2 = map[lc]
}
{ print }
$ awk -f tst.awk file
Mark Father
Jason Son
Jose Son
Steffy Daughter
答案3
一种方法(适用于所有 awk 实现)是将第二列小写,但将唯一的第一个字符大写;然后检查这些内容是否匹配,然后使用保存在中的转换后的内容更新第二列的值tmp。
$ awk -F'|' '{ tmp=toupper(substr($2,1,1)) tolower(substr($2,2)) }
tmp ~ /^(Father|Son|Daughter)$/ { $2=tmp }1' infile
Mark Father
Jason Son
Jose Son
Steffy Daughter
请注意,当使用IGNORECASE
(GNU awk 特定)时,这只适用于您想要执行的所有匹配处理(字符串/正则表达式),而不是在替换时执行。
答案4
使用 Raku(以前称为 Perl_6)
raku -pe 's:i:g/ «father» | «daughter» | «son» /{$/.tclc}/;'
或者
raku -pe 's:i:g/ «father» | «daughter» | «son» /{$/.wordcase}/;'
正则表达式副词在 Raku(缩写):ignorecase
中进行不区分大小写的匹配。:i
左右单词边界确保仅匹配整个单词(例如,没有可能导致类似输出的虚假匹配«
)。请注意,您可以对左词边界使用代替,对右词边界使用代替。»
JaSon
<<
«
>>
»
为了改变大小写,Raku 有一个漂亮的wordcase
例程(你猜对了),它接受单词并将第一个字母大写,同时将所有非首字母转换为小写。 [Raku 函数tclc
(字面意思是“titlecase-lowercase”)在默认情况下执行相同的操作,但选项较少]。
输入示例:
Mark|father
Jason|SOn
Jose|son
Steffy|daugHter
Agnes|moTHer
示例输出:
Mark|Father
Jason|Son
Jose|Son
Steffy|Daughter
Agnes|moTHer
如果OP想要分割一个分隔符,例如|
,只需调用以下Raku单行符前或者后上面的代码:
raku -ne '.split("|").put;'
示例输出:
Mark Father
Jason Son
Jose Son
Steffy Daughter
Agnes moTHer
附录:
@Stéphane Chazelas 在评论中指出,对于上面的代码(例如),连字符的单词将获得内部大写(例如god-son
to god-Son
)。下面的代码使用三个文字匹配来避免此问题:
raku -ne '.wordcase(:where({ $_.fc eq "father" | "daughter" | "son"})).put;'
或者
raku -pe '.=wordcase(:where({ $_.fc eq "father" | "daughter" | "son"}));'