使用SUBSTR或SPLIT拆分字符串?

时间:2012-06-19 10:34:07

标签: perl

我很茫然,希望能在这里寻求帮助。我想要完成的是以下内容: 我有一个包含8列的.csv文件。第三列包含格式如下的电话号码:

+45 23455678
+45 12314425
+45 43631678
+45 12345678
(goes on for a while) 

我想要的是:

+45 2345 5678
+45 1231 4425
+45 4363 1678
+45 1234 5678
(etc)

所以只是在第8个位置之后的空白(包括+和空白)。我尝试了各种各样的东西,但它没有用。首先,我尝试使用substr,但无法使其工作。然后看了拆分功能。然后我感到困惑!我是perl的新手,所以我不确定我在寻找什么,但我已经尝试了一切。有一个条件,所有的数字都以(比方说)+45开头,然后是一个空格和一个数字块。但并非所有数字都具有相同的长度,有些数字超过10位。我想要它做的是取第一位“+45 1234”(/ + 43 \ s {1} \ d {4} /),然后是第二位,无论它有多少位数。我认为将LIMIT设置为1所以它只是添加最后一位,无论它的4位数还是8位数。

我已阅读http://www.perlmonks.org/?node_id=591988,但“使用拆分与正则表达式”这一部分让我感到困惑。

我已经尝试了3天而没有到达任何地方。我想它应该很简单,但我现在才开始了解perl的基础知识。我确实理解正则表达式,但我不知道用于某个任务的语句。这是我的代码:

@ARGV or die "Usage: $0  input-file output-file\n";

$inputfile=$ARGV[0];
$outputfile=$ARGV[1];

open(INFILE,$inputfile) || die "Bestand niet gevonden :$!\n";
open(OUTFILE,">$outputfile") || die "Bestand niet gevonden :$!\n";

$i = 0;

@infile=<INFILE>;

foreach ( @infile ) {
    $infile[$i] =~ s/"//g;                            
    @elements = split(/;/,$infile[$i]);         

    @split = split(/\+43\s{1}\d{4}/, $elements[2], 1);

    @split = join ???

    @elements = join(";",@elements);            # Add ';' to all elements
    print OUTFILE "@elements";
    $i = $i+1;
}

close(INFILE);
close(OUTFILE);

6 个答案:

答案 0 :(得分:3)

您的代码存在一些问题,但要解决有关如何在字符串中的第8个位置后添加空格的问题,我将假设您已将电话号码存储在数组@phone_numbers中。这是一个非常适合正则表达式的任务:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

my @phone_numbers = (
    '+45 23455678',
    '+45 12314425',
    '+45 43631678',
    '+45 12345678'
);

s/^(.{8})/$1 / for @phone_numbers;

print Dumper \@phone_numbers;

输出:

$VAR1 = [
      '+45 2345 5678',
      '+45 1231 4425',
      '+45 4363 1678',
      '+45 1234 5678'
    ];

要将模式应用于您的脚本,只需添加:

$elements[2] =~ s/^(.{8})/$1 /;

或者

my @chars = split//, $elements[2];
splice @chars, 8, 0, ' ';
$elements[2] = join"", @chars;

更改foreach循环中的电话号码。

答案 1 :(得分:2)

这是您的程序的更惯用的版本。

use strict;
use warnings;

my $inputfile  = shift || die "Need input and output file names!\n";
my $outputfile = shift || die "Need an output file name!\n";

open my $INFILE,  '<', $inputfile   or die "Bestand niet gevonden :$!\n";
open my $OUTFILE, '>', $outputfile  or die "Bestand niet gevonden :$!\n";

my $i = 0;

while (<$INFILE>) {
    # print; # for debugging
    s/"//g;
    my @elements = split /;/, $_;
    print join "%", @elements;
    $elements[2] =~ s/^(.{8})/$1 /;
    my $output_line = join(";", @elements);
    print $OUTFILE $output_line;
    $i = $i+1;
}

close $INFILE;
close $OUTFILE;

exit 0;

答案 2 :(得分:0)

在左侧使用substr

use strict;
use warnings;

while (<DATA>) {
    my @elements = split /;/, $_;
    substr($elements[2], 8, 0) = ' ';
    print join(";", @elements);
}

__DATA__
col1;col2;+45 23455678
col1;col2;+45 12314425
col1;col2;+45 43631678
col1;col2;+45 12345678

<强>输出:

col1;col2;+45 2345 5678
col1;col2;+45 1231 4425
col1;col2;+45 4363 1678
col1;col2;+45 1234 5678

答案 3 :(得分:0)

Perl one liner,你也可以用于多个.csv文件。

perl -0777 -i -F/;/ -a -pe "s/(\+45\s\d{4})(\d+.*?)/$1 $2/ for @F;$_=join ';',@F;" s_infile.csv

答案 4 :(得分:0)

这是它如何完成的基本要点。数字字符串的“前缀”是\+45,它是硬编码的,您可以根据需要进行更改。 \pN表示数字,{4}表示4。

use strict;
use warnings;

while (<DATA>) {
    s/^\+45 \pN{4}\K/ /;
    print;
}

__DATA__
+45 234556780
+45 12314425
+45 436316781
+45 12345678

您的代码还有许多其他问题:

您不使用use strict; use warnings;。这是一个巨大的错误。这就像骑摩托车,戴上眼罩代替头盔来保护头部。通常情况下,这是一个容易被忽视的建议,因为它的解释非常简短,所以为了说明一点,我比我必须更加冗长:这是最重要的错误。如果你错过了其余的所有错误,那么比错过这部分要好。


您的open语句是两个参数,并且您不以任何方式验证您的参数。这是非常危险的,因为它允许人们执行任意命令。使用带有词法文件句柄的三参数open和open的显式MODE:

open my $in, "<", $inputfile or die $!;

您将文件篡改为数组:@infile=<INFILE>读取文件的惯用方法是:

while (<$in>) {  # read line by line
    ...
}

更糟糕的是,你使用foreach (@infile)循环,但引用$infile[$i]并保持变量在循环中向上计数。这是混合两种风格的循环,即使它“有效”,它肯定看起来很糟糕。循环遍历数组:

for my $line ( @infile ) {  # foreach style
    $line =~ s/"//g;
    ...
}

for my $index ( 0 .. $#infile ) { # array index style
    $infile[$index] =~ ....
}

但是这两个循环都不是你应该使用的,因为上面的while循环是更受欢迎的。此外,您实际上根本不必使用此方法。 * nix方式是提供输入文件名或STDIN,并在需要时重定向STDOUT:

perl script.pl inputfile > outputfile

或者,使用STDIN

some_command | perl script.pl > outputfile

要实现此目的,只需删除所有open命令并使用

while (<>) {  # diamond operator, open STDIN or ARGV as needed
    ...
}

但是,在这种情况下,由于您使用的是CSV数据,因此您应该使用CSV模块来解析文件:

use strict;
use warnings;
use ARGV::readonly;  # safer usage of @ARGV file reading

use Text::CSV;

my $csv = Text::CSV->new({
        sep_char    => ";",
        eol     => $/,
        binary      => 1,
        });

while (my $row = $csv->getline(*DATA)) {  # read input line by line
    if (defined $row->[1]) {              # don't process empty rows
        $row->[1] =~ s/^\+45 *\pN{4}\K/ /;
    }
    $csv->print(*STDOUT, $row);
}

__DATA__
fooo;+45 234556780;bar
1231;+45 12314425;
oh captain, my captain;+45 436316781;zssdasd
"foo;bar;baz";+45 12345678;barbarbar

在上面的脚本中,您可以将DATA文件句柄(使用内联数据)替换为ARGV,它将使用所有脚本参数作为输入文件名。为此,我添加了ARGV::readonly,这将强制您的脚本只能以安全的方式打开文件。

正如您所看到的,我的示例脚本包含引用的分号,split很难处理。特定的print语句将对输出强制执行某些CSV规则,例如添加引号。有关详细信息,请参阅documentation

答案 5 :(得分:0)

要在字符串的第八个字符后添加空格,您可以使用substr的第四个参数。

substr $string, 8, 0, ' ';

使用单个空格替换从偏移量8开始的零长度子字符串。

您可能认为使用正则表达式更安全,以便只更改预期格式的数据

$string =~ s/^(\+\d{2} \d{4})/$1 /;

$str =~ s/^\+\d{2} \d{4}\K/ /;

将实现相同的目标,但如果数字看起来不像预先的那样,它将不会做任何事情。

这是您的计划的改造。最重要的是,您应该在程序开始时use strictuse warnings,并在首次使用时使用my声明变量。还使用open和词法文件句柄的三参数形式。最后,当while循环允许您一次处理一行时,最好避免将整个文件读入数组。

use strict;
use warnings;

@ARGV == 2 or die "Usage: $0 input-file output-file\n";

my ($inputfile, $outputfile) = @ARGV;

open my $in, '<', $inputfile or die "Bestand niet gevonden: $!";
open my $out, '>', $outputfile or die "Bestand niet gevonden: $!";

while (<$in>) {
  tr/"//d;                            
  my @elements = split /;/;
  substr $elements[2], 8, 0, ' ';
  print $out join ';', @elements;
}