好的,我确实找到了一种在 Perl 中执行此操作的方法,而且这并不是微不足道的;下面粘贴了一个转换测试字符串的示例(csv-agnostic)脚本。最终打印出来:
Orig string: "AO900-020","Hello","World","5000","0,00","5,25","stk","","1","0,00","Test 2","42.234,12","","","0,00","","","","5,25"
Conv string: "AO900-020","Hello","World","5000","0.00","5.25","stk","","1","0.00","Test 2","42234.12","","","0.00","","","","5.25"
...这基本上是我想要实现的;但这里可能存在不受欢迎的边缘情况。或许更好地使用类似 csvfix 或 csvtool 之类的工具,或者直接在代码中使用 Perl csv 库。
仍然是代码:
#!/usr/bin/env perl
use warnings;
use strict;
use locale;
use POSIX qw(setlocale locale_h LC_ALL);
use utf8;
use Number::Format qw(:subs); # sudo perl -MCPAN -e 'install Number::Format'
use Data::Dumper;
use Scalar::Util::Numeric qw(isint); # sudo perl -MCPAN -e 'install Scalar::Util::Numeric'
my $old_locale;
# query and save the old locale
$old_locale = setlocale(LC_ALL);
# list of (installed) locales: bash$ locale -a
setlocale(LC_ALL, "POSIX");
# localeconv() returns "a reference to a hash of locale-dependent info"
# dereference here:
#%posixlocalesettings = %{localeconv()};
#print Dumper(\%posixlocalesettings);
# or without dereference:
my $posixlocalesettings = localeconv();
# the $posixlocalesettings has only 'decimal_point' => '.';
# force also thousands_sep to '', else it will be comma later on, and grouping will be made regardless
$posixlocalesettings->{'thousands_sep'} = '';
print Dumper($posixlocalesettings);
#~ my $posixNumFormatter = new Number::Format %args;
# thankfully, Number::Format seems to accept as argument same kind of hash that localeconv() returns:
my $posixNumFormatter = new Number::Format(%{$posixlocalesettings});
print Dumper($posixNumFormatter);
setlocale(LC_ALL, "en_DK.utf8");
my $dklocalesettings = localeconv();
print Dumper($dklocalesettings);
# Get some of locale's numeric formatting parameters
my ($thousands_sep, $decimal_point, $grouping) =
# @{localeconv()}{'thousands_sep', 'decimal_point', 'grouping'};
@{$dklocalesettings}{'thousands_sep', 'decimal_point', 'grouping'};
# grouping and mon_grouping are packed lists
# of small integers (characters) telling the
# grouping (thousand_seps and mon_thousand_seps
# being the group dividers) of numbers and
# monetary quantities. The integers’ meanings:
# 255 means no more grouping, 0 means repeat
# the previous grouping, 1-254 means use that
# as the current grouping. Grouping goes from
# right to left (low to high digits). In the
# below we cheat slightly by never using anything
# else than the first grouping (whatever that is).
my @grouping = unpack("C*", $grouping);
print "en_DK.utf8: thousands_sep $thousands_sep; decimal_point $decimal_point; grouping " .join(", ", @grouping). "\n";
my $inputCSVString = '"AO900-020","Hello","World","5000","0,00","5,25","stk","","1","0,00","Test 2","42.234,12","","","0,00","","","","5,25"';
# Character set modifiers
# /d, /u , /a , and /l , available starting in 5.14, are called the character set modifiers;
# /l sets the character set to that of whatever Locale is in effect at the time of the execution of the pattern match.
while ($inputCSVString =~ m/[[:digit:]]+/gl) { # doesn't take locale in account
print "A Found '$&'. Next attempt at character " . (pos($inputCSVString)+1) . "\n";
}
print "----------\n";
#~ while ($inputCSVString =~ m/(\d{$grouping[0]}($|$thousands_sep))+/gl) {
#~ while ($inputCSVString =~ m/(\d)(\d{$grouping[0]}($|$thousands_sep))+/gl) {
# match a string that starts with digit, and contains only digits, thousands separators and decimal points
# note - it will NOT match negative numbers
while ($inputCSVString =~ m/\d[\d$thousands_sep$decimal_point]+/gl) {
my $numstrmatch = $&;
my $unnumstr = unformat_number($numstrmatch); # should unformat according to current locale ()
my $posixnumstr = $posixNumFormatter->format_number($unnumstr);
print "B Found '$numstrmatch' (unf: '$unnumstr', form: '$posixnumstr'). Next attempt at character " . (pos($inputCSVString)+1) . "\n";
}
sub convertNumStr{
my $numstrmatch = $_[0];
my $unnumstr = unformat_number($numstrmatch);
# if an integer, return as is so it doesn't change trailing zeroes, if the number is a label
if ( (isint $unnumstr) && ( $numstrmatch !~ m/$decimal_point_dk/) ) { return $numstrmatch; }
#~ print "--- $unnumstr\n";
# find the length of the string after the decimal point - the precision
my $precision_strlen = length( substr( $numstrmatch, index($numstrmatch, $decimal_point_dk)+1 ) );
# must manually spec precision and trailing zeroes here:
my $posixnumstr = $posixNumFormatter->format_number($unnumstr, $precision_strlen, 1);
return $posixnumstr;
}
# e modifier to evaluate perl Code
(my $replaceString = $inputCSVString) =~ s/(\d[\d$thousands_sep$decimal_point]+)/"".convertNumStr($1).""/gle;
print "Orig string: " . $inputCSVString . "\n";
print "Conv string: " . $replaceString . "\n";