我收到超出我控制范围的输入文件,其中某些列中包含前导单引号,例如
'foo|'012|that's nice|bar
我想从每个字段中删除所有前导单引号,以获得预期的输出:
foo|012|that's nice|bar
使用 awk,我假设 gsub 正则表达式元字符像^
每列一样工作,但它似乎只在行的开头工作:
$ echo "'foo|'012|that's nice|bar" | awk -F'|' '{gsub(/^'\''/,"")}1'
foo|'012|that's nice|bar
如何从每列中删除前导单引号?
答案1
不需要awk
,sed
可以这样做:
sed -E "s/(^|\|)'/\1/g"
-E
切换到扩展正则表达式的选项将出现在 POSIX 标准的下一版本中,但大多数实现已经支持sed
。或者您可以使用perl
which 取代sed
and awk
:
perl -pe 's/(^|\|)'\''/$1/g'
或者:
perl -pe "s/(^|\|)\K'//g"
(\K
标记K
比赛的开始)。
或者:
perl -pe "s/(?<![^|])'//g"
('
只要前面没有除 以外的字符即可进行替换|
)。
或者用它的awk
模式:
perl -F'\|' -pe 's/^'\''// for @F; $_ = join "|", @F'
使用awk -F'|'
,您需要将替换应用于每个字段,如上面的perl
'sawk
模式:
awk -F'|' -v OFS='|' '
{
for (i = 1; i <= NF; i++) sub(/^'\''/, "", $ i)
print
}'
对于awk
,当字段分隔符为单个字符时,作为特殊情况,它不会被视为正则表达式,因此不需要转义|
。
$
inawk
是一个一元运算符,它需要一个数字,如果它是 1 和 之间的数字,则返回相应的字段NF
;如果该数字为 0,则返回整个记录,否则返回空字符串。
sub()
并且gsub()
可以采用 2 或 3 个参数,如果未提供第三个参数((唯一的)替换主题),则它默认为整个记录 ( $0
)。与 不同的方式与与不同的方式gsub()
相同。仅替换第一次出现的模式,而替换所有出现的模式。sub()
s/x/y/g
s/x/y/
sed
sub()
gsub()
这里的正则表达式只能匹配一次,因为它是在开始时锚定的,所以sub()
和gsub()
不会产生任何影响。
IOW,gsub()
不是在每个字段中进行一次替换,而是在一字符串,默认情况下该字符串是整个未分割的记录。
¹ 从技术上讲,它们被视为数字字符串。也就是说,如果它们看起来像数字,否则将被视为数字。空字符串被视为字符串。
答案2
您必须迭代这些字段并替换每个字段中的引号字符。 Stéphane 展示了如何使用awk
在他们的回答中。
$ mlr --csv --fs pipe -N put 'for (k,v in $*) { $[k] = sub(v, "^\047", "") }' file
foo|012|that's nice|bar
这使用米勒 ( mlr
)将输入读取为无标头 CSV 数据集(使用管道作为字段分隔符)。对于每条记录,put
表达式会迭代所有字段,并删除其中的第一个字符(如果它是单引号(八进制为 047))。
Miller 的另一种方法是apply()
应用一个从字段中删除初始单引号的函数。该函数应用于每条记录中的每个字段。
$ mlr --csv --fs pipe -N put '$* = apply($*, func(k,v) { return { k: sub(v, "^\047", "") } })' file
foo|012|that's nice|bar
答案3
使用 GNU awk 进行gensub()
(其他一些 awk 也支持,但 POSIX 尚未要求):
$ awk '{$0=gensub(/(^|\|)\047/,"\\1","g")} 1' file
foo|012|that's nice|bar
我可以在上面完成,print gensub(...)
这会更有效率一点,但我正在分配$0
并打印1
为了与下面的其他答案保持一致,并且以防万一您实际上需要在替换后对字段执行某些操作。
或者,使用 GNU awk 进行RT
:
$ awk -v RS='|' '{ORS=RT; sub(/^\047/,"")} 1' file
foo|012|that's nice|bar
或使用任何 awk:
$ awk '{gsub(/\|\047/,"|"); sub(/^\047/,"")} 1' file
foo|012|that's nice|bar
或者也使用任何 awk:
$ awk -F "[|]'" -v OFS='|' '{$1=$1; sub(/^\047/,"")} 1' file
foo|012|that's nice|bar
看http://awk.freeshell.org/PrintASingleQuote为什么我用\047
来代表'
.
所有 awk字符串操作函数(*sub()
、match()
、*split()
、index()
、substr()
、length()
等),对您作为参数提供的任何字符串进行操作,对于那些不需要字符串参数的字符串,默认情况是$0
没有提供字符串作为参数。在对字符串进行操作之前,它们不会将字符串分成字段或其他任何内容,因此,如果您出于某种原因确实想一次更改一个字段,那么您需要编写一个循环来sub()
一次调用每个字段:
$ awk 'BEGIN{FS=OFS="|"} {for (i=1; i<=NF; i++) sub(/^\047/,"",$i)} 1' file
foo|012|that's nice|bar
但这比使用其中任何一个的效率都要低:
awk '{$0=gensub(/(^|\|)\047/,"\\1","g")} 1' file
awk '{gsub(/\|\047/,"|"); sub(/^\047/,"")} 1' file
自从:
- 每行调用
*sub()
NF 次,而不仅仅是 1 或 2 次。 - 它指的是
$i
强制 awk 进行字段拆分的字段,而如果您在脚本中没有引用字段(如上面的后续 2 个脚本),那么大多数 awks 不会花时间将每个记录拆分为字段。 $0
每次调用sub()
更改字段时,它都会强制 awk 从字段进行重建,即每行最多 NF 次。
答案4
使用乐(以前称为 Perl_6)
使用<(…)>
捕获标记:
~$ raku -pe "s:g/ [ ^ | \| ] <( \' )> //;" file
#OR
~$ raku -pe 's:g/ [ ^ | \| ] <( \c[APOSTROPHE] )> //;' file
或者使用<?after … >
正向回顾:
~$ raku -pe "s:g/ <?after ^ | \| > \' //;" file
#OR
~$ raku -pe 's:g/ <?after ^ | \| > \c[APOSTROPHE] //;' file
或者<!after … >
将负向后查找与<-[…]>
负自定义字符类结合使用:
~$ raku -pe "s:g/ <!after <-[|]> > \' //;" file
#OR
~$ raku -pe 's:g/ <!after <-[|]> > \c[APOSTROPHE] //;' file
或者使用split
/ join
:
~$ raku -ne ".split(/ [ ^ | \| ] \'? /)[1..*].join('|').put" file
#OR
~$ raku -ne '.split(/ [ ^ | \| ] \c[APOSTROPHE]? /)[1..*].join("|").put' file
请注意在必要时使用上面的方括号分组(Raku 中的方括号不会捕获到$0
、$1
等)。
示例输入(也测试空列):
'foo|'012|that's nice|bar||baz
示例输出(所有示例):
foo|012|that's nice|bar||baz
1 感谢 @StéphaneChazelas 提供的 Perl 示例。