替换列开头的单引号

替换列开头的单引号

我收到超出我控制范围的输入文件,其中某些列中包含前导单引号,例如

'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

不需要awksed可以这样做:

sed -E "s/(^|\|)'/\1/g"

-E切换到扩展正则表达式的选项将出现在 POSIX 标准的下一版本中,但大多数实现已经支持sed。或者您可以使用perlwhich 取代sedand 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/gs/x/y/sedsub()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

自从:

  1. 每行调用*sub()NF 次,而不仅仅是 1 或 2 次。
  2. 它指的是$i强制 awk 进行字段拆分的字段,而如果您在脚本中没有引用字段(如上面的后续 2 个脚本),那么大多数 awks 不会花时间将每个记录拆分为字段。
  3. $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 示例。

https://docs.raku.org/language/regexes
https://raku.org

相关内容