Dans un gros fichier csv (> 1 Go), j'ai quelque chose comme:
"34432", "name", "0", "very long description"
mais au lieu de cela, j'aimerais avoir
34432, "name", 0, "very long description".
Je regardais sed
mais cette tâche est hors de mon champ.
Des conseils pour y parvenir?
Utiliser Perl:
Perl -ne 's/"(\d+)"/$1/g; print' file.csv > new_file.txt
Tout le travail est fait par s/"(\d+)"/$1/g
où
s/patternA/patternB/
est utilisé pour remplacer patternA
par patternB
\d+
entourés de guillemets doubles.\d+
) sont utilisées pour capturer le ou les chiffres et les réutiliser en tant que modèle de remplacement avec la variable spéciale Perl $1
.Une regex GNU sed qui devrait fonctionner dans ce cas est
sed -r 's/"([0-9]+)"/\1/g'
Pour le sed pur, vous devez échapper aux parenthèses de regroupement et au modificateur +
sed 's/"\([0-9]\+\)"/\1/g'
Vous pouvez effectuer la substitution sur place avec certaines versions de sed, par exemple.
sed -ri 's/"([0-9]+)"/\1/g' file.csv
Vous pouvez également utiliser la classe POSIX [[:digit:]]
à la place de la plage de caractères [0-9]
Votre description du problème n'est pas très spécifique. Je suppose que vous souhaitez supprimer les guillemets doubles autour des premier et troisième champs uniquement. Si tel est le cas, l'un d'entre eux devrait fonctionner:
sed
sed -r 's/^"([^"]+)"(\s*,\s*[^,]+)\s*,\s*"([^"]+)"/\1\2, \3/' file.csv
Le -r
active les expressions régulières étendues, ce qui nous permet d'utiliser des parenthèses pour capturer des motifs sans avoir à les échapper. Nous faisons donc correspondre une citation au début de la ligne (^"
), suivie d'un ou plusieurs caractères non-guillemets ([^"]+
), puis de la citation de clôture, suivie de 0 ou de plusieurs espaces, un virgule, puis à nouveau 0 ou plusieurs espaces (\s*,\s*
), puis un extrait de virgule jusqu'à la virgule suivante (ce qui définit le deuxième champ). Enfin, nous cherchons 0 ou plusieurs espaces, une virgule, et nous le remplaçons par le premier motif capturé (\1
), puis le deuxième (\2
), une virgule, un espace et le troisième.
Perl
Perl -pe 's/^"([^"]+)"(\s*,\s*[^,]+)\s*,\s*"([^"]+)"/$1$2, $3/; ' file.csv
-p
signifie imprimer chaque ligne après avoir appliqué le script transmis par -e
. Le script lui-même est fondamentalement le même regex que dans le sed
ci-dessus. Seulement ici, les modèles capturés sont $1
.
awk
awk -F, -v OFS="," '{gsub("\"","",$1)0gsub("\"","",$3);}1;' file.csv
Le -F
définit le séparateur de champ sur ,
. OFS
est le séparateur de champ de sortie qui est également réglé sur ,
pour que les lignes soient correctement imprimées. La gsub
effectue la substitution en remplaçant tout "
par rien puisque nous l'exécutons sur les 1er ($1
) et 3ème champs ($3
), il ne supprimera que les guillemets ces champs. Le 1;
est simplement awk
en abrégé pour "imprimer la ligne".
Le petit script ci-dessous prend l'argument de ligne de commande file, itère chaque ligne de ce fichier et divise chaque ligne en une liste d'éléments en utilisant ,
comme séparateur. Chaque entrée est ensuite décochée et sa chaîne numérique vérifiée; si une chaîne est numérique, elle n'est pas citée.
#!/usr/bin/env python
import sys
with open(sys.argv[1]) as fp:
for line in fp:
new_vals = []
vals = line.strip().split(',')
for val in vals:
val = val.strip().rstrip().replace('"','')
if not val.isdigit():
val = '"' + val + '"'
new_vals.append(val)
print(",".join(new_vals))
Essai:
$ cat input.txt
"34432", "name", "0", "very long description"
"1234", "othe name" , "42", "another description"
$ ./unquote_integers.py input.txt
34432,"name",0,"very long description"
1234,"othe name",42,"another description"
Notes complémentaires:
Il a été demandé dans les commentaires, pourquoi le script supprime les guillemets autour de chaque élément avant d'évaluer si l'élément est une chaîne numérique ou non. La raison principale en est que l’inclusion de guillemets doubles fera en sorte que des éléments comme "123"
soient évalués à False
, c’est-à-dire non numérique. Effectivement, nous devons évaluer le contenu des guillemets doubles d’une manière ou d’une autre. Maintenant, il existe une autre façon d’aborder ceci en prenant une tranche de liste de chaque valeur. Cependant, ce n’est pas mieux que d’utiliser .replace()
depuis le début. Cela raccourcit le code, mais au moins dans ce cas, la brièveté d'un script n'est pas pertinente - notre objectif est de faire en sorte que le code fonctionne, et non le code-golf.
Voici la solution alternative avec des tranches de liste:
#!/usr/bin/env python
import sys
with open(sys.argv[1]) as fp:
for line in fp:
new_vals = []
vals = line.strip().split(',')
for val in vals:
val = val.strip().rstrip() #remove extra spaces
val = val.replace('"','') if val[1:-1].isdigit() else val
new_vals.append(val)
print(",".join(new_vals))