使用Perl正则表达式替换引号封装字符串中的引号

时间:2019-02-06 21:56:01

标签: regex perl replace

我试图替换用竖线分隔并引用封装文件的引号,而不替换提供封装的引号。

我尝试使用下面的Perl行用反斜杠`替换引号,但不确定如何只替换引号而不替换整个1。

样本数据(test.txt):

"1"|"Text"|"a"\n
"2"|""Text in quotes""|"ab"\n
"3"|"Text "around" quotes"|"abc"\n

perl -pi.bak -e 's/(?<=\|")(.*)(?="\|)/\1`/' test.txt

这是正在发生的事情:

"1"|"`"|"a"\n
"2"|"`"|"ab"\n
"3"|"`"|"abc"\n

这是我想要实现的目标:

"1"|"Text"|"a"\n
"2"|"`Text in quotes`"|"ab"\n
"3"|"Text `around` quotes"|"abc"\n

4 个答案:

答案 0 :(得分:3)

在Perl 5.14和更高版本中,您可以使用

perl -pi.bak -e 's/(?:^|\|)(")?\K(.*?)(?=\1(?:$|\|))/$2=~s#"|(`)#`$1#gr/ge' test.txt

请参见regex demoonline demo

这里的要点是您将字段与第一个正则表达式匹配,然后使用在匹配部分运行的第二个正则表达式处理双引号和反引号。

详细信息

  • (?:^|\|)-匹配字符串或|的开头
  • (")?-与"匹配的可选第1组
  • \K-匹配重置运算符会丢弃当前匹配缓冲区中的所有文本
  • (.*?)-第2组:除换行符外的任何0+个字符
  • (?=\1(?:$|\|))-一个正向超前查询,可确保与组1中的值相同,然后确保字符串的末尾或|的位置紧靠当前位置的右侧。

因此,第2组是单元格内容,没有用双引号引起来。 $2=~s#"|()#$1#gr"替换所有`,并复制第2组值中所有找到的文字反引号(请参见this regex demo)。 "|(`)模式与"或反引号匹配(将后者捕获到组1中),而`$1则将匹配替换为反引号和组1的内容。

答案 1 :(得分:2)

已更新是为了澄清已经出现的反引号应加倍


一种方法是在split上的|上加上引号,使其余的正则表达式变得简单,然后重新组装字符串。与单个正则表达式相比,这可能会失去一些效率,但维护起来要简单得多

perl -F"\|" -wlanE'
    say join "\|", 
        map { s/^"|"$//g; s/`/``/g; s/"([^"]+)"/`$1`/g; qq("$_") } @F
' data.txt

-a选项使其“自动分割”每一行,因此在程序中,行标记在@F中可用,而-F指定要分割的模式(默认值除外) )。 -l处理换行符。参见Command switches in perlrun

map中,封闭的"被删除,现有的反引号也加倍;然后全局更改"模式周围的内容。然后将引号放回去,并编辑返回的列表join|中的join被转义,以便通过shell进入Perl程序;如果这是脚本(而不是单行代码),我将始终建议将\|更改为|

我不知道有关报价的典型数据和可能出现的边缘情况,但是如果报价中可能存在松散(单个,不成对)的报价,则会出现问题,并可能产生错误的输出,并且会安静地输出;就像任何期望使用双引号的过程一样,无需进行非常详细的分析。

简单地将所有"(而不是封闭的)替换为

可能整体上更安全
map { s/^"|"$//g; s/`/``/g; s/"/`/g; qq("$_") }

(或使用tr代替正则表达式s///g)。这也增加了一些效率指标。


获取数据“实质”的另一种方法是使用Text::CSV,它允许使用除(缺省)逗号之外的定界符并吸收引号。在字段中使用引号被认为是CSV格式不正确,但是该模块也可以使用以下选项对其进行解析。

use warnings;
use strict;
use feature 'say';

use Text::CSV;

my $file = shift || 'data.txt';
my $outfile = 'new_' . $file;

my $csv = Text::CSV->new( { binary => 1, sep_char => '|', 
    allow_loose_quotes => 1, escape_char => '',     # quotes inside fields
    always_quote => 1                               # output as desired
} ) or die "Can't do CSV: ", Text::CSV->error_diag;

open my $fh,     '<', $file    or die "Can't open $file: $!";
open my $out_fh, '>', $outfile or die "Can't open $outfile: $!";

while (my $row = $csv->getline($fh)) {
    s/`/``/g for @$row;
    tr/"/`/  for @$row;
    $csv->say($out_fh, $row);
}

要在字段escape_char中使用引号,quote_char必须与''不同;我只是在这里将其设置为always_quote。输出也由模块处理,并且class _HomePageState extends State<HomePage> { List<Todo> _dataList; final FirebaseDatabase _database = FirebaseDatabase.instance; StreamSubscription<Event> _onTodoAddedSubscription; Query _todoQuery; @override void initState() { super.initState(); _dataList = List(); _todoQuery = _database.reference().child(widget.userId); _onTodoAddedSubscription = _todoQuery.onChildAdded.listen(_onEntryAdded); } @override void dispose() { _onTodoAddedSubscription.cancel(); super.dispose(); } _onEntryAdded(Event event) { setState(() { _dataList.add(Todo.fromSnapshot(event.snapshot)); }); } Widget _showTodoList() { if (_dataList.length > 0) { return ListView.builder( shrinkWrap: true, itemCount: _dataList.length, itemBuilder: (BuildContext context, int index) { String todoId = _dataList[index].key; bool status = _dataList[index].status; int datetime = _dataList[index].datetime; double current = _dataList[index].current; double voltage = _dataList[index].voltage; }); } } @override Widget build(BuildContext context) { return Scaffold( body: _showTodoList(), ); } } 属性用于该属性(引用所有字段,无论是否需要)。请参阅文档。

当然,使用此模块可以完成更多工作。

如果问题的目的恰好是清理一种文件格式,其中对字段和字段内部都使用相同的引号,则建议对模块进行全部处理。这种方法使人们可以干净,一致地设置用于输入和输出的各种选项,并且是可维护的。


几个问题

  • 那里有什么样的数据,有可能出现流浪报价?那呢这可能甚至影响最佳方法的选择,因为它可能需要详细的分析。

  • 如果此处的任务是拉直CSV样式的数据,那么为什么不对字段中的引号(如CSV中常见的和正确的)加倍,而不是替换它们(并可能损害其文本含义)?例如,请参阅模块的文档。

答案 2 :(得分:1)

Perl使用$ 1作为正则表达式替换部分中的第一个捕获组的占位符,而不是\ 1(用于正则表达式的匹配部分)。您的正则表达式与内引号不匹配,并且可能与管道分隔数据的第一个或最后一个字段不匹配。您的替换也未能在被捕获的组之前添加引号字符。

尝试:

perl -pi.bak -e 's/(?<=(?:^|\|)")"([^"]*)"(?="(?:$|\|))/`$1´/' test.txt

答案 3 :(得分:0)

另一个Perl。按数组@F拆分后,检查“不在元素开头/结尾的位置。”

 perl -F"\|"  -lane   ' for(@F) { s/(?<!^)"(?!$)/`/g }; print join("|",@F) ' 

具有给定的输入

$ cat grasshopper.txt
"1"|"Text"|"a"
"2"|""Text in quotes""|"ab"
"3"|"Text "around" quotes"|"abc"
$  perl -F"\|"  -lane   ' for(@F) { s/(?<!^)"(?!$)/`/g }; print join("|",@F) ' grasshopper.txt
"1"|"Text"|"a"
"2"|"`Text in quotes`"|"ab"
"3"|"Text `around` quotes"|"abc"
$