J'ai un fichier d'entrée délimité avec des virgules (,
). Certains champs sont conçus dans des citations doubles qui ont une virgule dans elles. Voici l'exemple de ligne
123,"ABC, DEV 23",345,534.202,NAME
J'ai besoin de supprimer toutes les virgules se produisant à l'intérieur des citations doubles et des citations doubles. De sorte que la ligne ci-dessus devrait être analysée comme indiqué ci-dessous
123,ABC DEV 23,345,534.202,NAME
J'ai essayé les suivants en utilisant sed
mais ne pas donner des résultats escomptés.
sed -e 's/\(".*\),\(".*\)/\1 \2/g'
Des astuces rapides avec sed
, awk
ou tout autre utilitaire UNIX s'il vous plaît?
Si les devis sont équilibrés, vous voudrez supprimer des virgules entre tous les autres devis, cela peut être exprimé en awk
comme ceci:
awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile
Sortir:
123,ABC DEV 23,345,534.202,NAME
explication
Les -F"
Donne l'AWK séparez la ligne aux signes à double citation, ce qui signifie que tous les autres champs seront le texte inter-devis. La boucle fonctionne gsub
, court pour substitut globalement, sur tous les autres champs, remplaçant la virgule (","
) avec rien (""
). Les 1
À la fin invoque le bloc de code par défaut: { print $0 }
.
Il y a une réponse bonne, en utilisant SED simplement une fois avec une boucle :
echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC DEV 23",345,534,"some more comma-separated words",202,NAME
Explication:
:a;
Est une étiquette de la branche furtrices/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /
pourrait contenir 3 parties fermées [^"]*,\?\|"[^",]*",\?
Match pour une chaîne ne contenant pas de double citation, peut-être suivie d'un coma o une chaîne jointe à deux doubles citation, sans coma et peut-être suivis d'un coma.ta
_ va boucler sur :a
si la commande précédente s/
a fait un certain changement.Une solution générale pouvant également gérer plusieurs virgules entre guillemets équilibrés nécessite une substitution imbriquée. Je mettez en œuvre une solution à Perl, qui traite toutes les lignes d'une entrée donnée et seulement des virgules de substitution dans toutes les autres citations:
Perl -pe 's/ " (.+? [^\\]) " # find all non escaped
# quoting pairs
# in a non-greedy way
/ ($ret = $1) =~ (s#,##g); # remove all commas within quotes
$ret # substitute the substitution :)
/gex'
ou bref
Perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'
Vous pouvez soit pipeler le texte que vous souhaitez traiter à la commande ou spécifier le fichier texte à traiter comme dernier argument de ligne de commande.
Vos deuxième citations sont égarées:
sed -e 's/\(".*\),\(.*"\)/\1 \2/g'
De plus, l'utilisation d'expressions régulières tend à correspondre à la partie la plus longue possible du texte, ce qui signifie que cela ne fonctionnera pas si vous avez plus d'un champ cité dans la chaîne.
sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'
C'est également un moyen de résoudre ce problème, cependant, avec une entrée pouvant contenir plus d'une virgule par champ cité, la première expression dans la SED devrait être répétée autant de fois que la teneur maximale de la virgule dans un seul champ, ou jusqu'à ce qu'elle soit. ne change pas du tout la sortie.
En cours d'exécution SED avec plus d'une expression devrait être plus efficace que plusieurs processus SED fonctionnant et un "TR" fonctionnant avec des tuyaux ouverts.
Cependant, cela peut avoir des conséquences indésirables si l'entrée n'est pas correctement formatée. I.e. citations imbriquées, citations non définies.
En utilisant l'exemple de fonctionnement:
echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'
Sortir:
123,ABC DEV 23,345,534,some more comma-separated words,202,NAME
En Perl - vous pouvez utiliser Text::CSV
Pour analyser cela, et faites-le trivialement:
#!/usr/bin/env Perl
use strict;
use warnings;
use Text::CSV;
my $csv = Text::CSV -> new();
while ( my $row = $csv -> getline ( \*STDIN ) ) {
#remove commas in each field in the row
$_ =~ s/,//g for @$row;
#print it - use print and join, rather than csv output because quotes.
print join ( ",", @$row ),"\n";
}
Vous pouvez imprimer avec Text::CSV
Mais il a tendance à préserver des citations si vous le faites. (Bien que je suggérais - plutôt que stripping citations pour votre sortie, vous pouvez simplement analyser en utilisant Text::CSV
en premier lieu).
En utilisant python
''.join([item if index % 2 == 0 else re.sub(',', '', item) for index, item in enumerate(row.split('"')) ])
J'ai créé une fonction pour boucler à travers tous les caractères de la chaîne.
[.____] Si le personnage est une citation, le chèque (b_in_qt) est marqué true.
[.____] tandis que b_in_qt est vrai, toutes les virgules sont remplacées par un espace.
[.____] B_IN_QT est défini sur FALSE lorsque la prochaine virgule est trouvée.
FUNCTION f_replace_c (str_in VARCHAR2) RETURN VARCHAR2 IS
str_out varchar2(1000) := null;
str_chr varchar2(1) := null;
b_in_qt boolean := false;
BEGIN
FOR x IN 1..length(str_in) LOOP
str_chr := substr(str_in,x,1);
IF str_chr = '"' THEN
if b_in_qt then
b_in_qt := false;
else
b_in_qt := true;
end if;
END IF;
IF b_in_qt THEN
if str_chr = ',' then
str_chr := ' ';
end if;
END IF;
str_out := str_out || str_chr;
END LOOP;
RETURN str_out;
END;
str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")
RESULTS
"blue","cat dog horse","",yellow,"green"