J'ai un fichier texte contenant plusieurs lignes du format suivant:
science, social tennis, ping_pong, chess
J'ai besoin d'analyser le fichier texte et d'imprimer la sortie des champs en ignorant les virgules. Ici, ce seront les champs 2 ou 3 comme ceci:
name,list_of_subjects,list_of_sports,school Eg1: john,science\,social,football,florence_school Eg2: james,painting,tennis\,ping_pong\,chess,highmount_school
Je ne sais pas comment ignorer les caractères échappés. Comment puis-je le faire avec awk
ou sed
dans le terminal?
7 Réponses :
Vous pouvez remplacer les séquences \,
par un autre caractère qui n'apparaîtra pas dans votre texte, diviser le texte autour des virgules restantes puis remplacer le caractère choisi par des virgules:
sed $'s/\\\,/\31/g' input | awk -F, '{ printf "Name: %s\nSubjects : %s\nSports: %s\nSchool: %s\n\n", $1, $2, $3, $4 }' | tr $'\31' ','
Remplacez \,
par un caractère que vos enregistrements ne contiennent pas normalement (par exemple \ n
) et restaurez-le avant l'impression. Par exemple:
$ awk 'BEGIN{ FS=OFS="," } NR>1{ if(gsub(/\\,/,"\n")) for(i=1;i<=NF;++i) gsub(/\n/,"\\,",$i); print $2,$3 }' file science\,social,football painting,tennis\,ping_pong\,chess
Puisque le premier gsub
est exécuté sur l'ensemble de l'enregistrement (c'est-à-dire $ 0
), awk est obligé de recalculer les champs. Mais le second n'est effectué que sur le deuxième champ (c'est-à-dire $ 2
), donc il n'affectera pas les autres champs. Voir: Modification des champs .
Pour pouvoir extraire plusieurs champs avec des virgules correctement échappées, vous devez insérer des gsub \ n
s dans tous les champs avec une boucle for comme dans l'exemple suivant:
$ awk -F',' 'NR>1{ if(gsub(/\\,/,"\n")) gsub(/\n/,",",$2); print $2 }' file science,social painting
Comment ne pas imprimer de lignes vides si la liste est vide dans ces colonnes? À l'heure actuelle, cette solution imprime une ligne vide si la liste est vide.
Input Line1: john, science \, social, football, florence_school Line2: james, painting, tennis \, ping_pong \, chess, highmount_school Line3: robert , snooker, ridgemont Line4: jim, géographie , Oakmont
La deuxième ligne de sortie (troisième ligne d'entrée) n'est-elle pas supposée être tennis, ping_pong, échecs
?
@potong ce n'est pas un exemple de sortie. Comme vous pouvez le voir, le 2e champ de la 2e ligne et le 3e champ de la 3e ligne contiennent des virgules échappées et OP dit qu'ils doivent être analysés comme ça
@oguzismail votre logique de code peut échouer si deux contre-obliques précèdent une virgule, si l'op veut échapper à la contre-oblique.
@jxc pourquoi OP voudrait-il échapper au caractère d'échappement?
@oguzismail Il n'est pas rare dans le traitement de données réel d'échapper au caractère d'échappement. Probablement pas un souci d'opération, mais mieux vaut connaître les problèmes potentiels.
@jxc Ok, si c'est un souci pour OP et qu'il me le fait savoir je vais mettre à jour ma réponse.
Pourquoi awk
et sed
quand bash avec coreutils est juste suffisant:
list_of_subjects : science social list_of_sports : football list_of_subjects : painting list_of_sports : tennis ping_pong chess
affichera:
# Sorry my cat. Using `cat` as input pipe cat <<EOF | name,list_of_subjects,list_of_sports,school Eg1: john,science\,social,football,florence_school Eg2: james,painting,tennis\,ping_pong\,chess,highmount_school EOF # remove first line! tail -n+2 | # substitute `\,` by an unreadable character: sed 's/\\\,/\xff/g' | # read the comma separated list while IFS=, read -r name list_of_subjects list_of_sports school; do # read the \xff separated list into an array IFS=$'\xff' read -r -d '' -a list_of_subjects < <(printf "%s" "$list_of_subjects") # read the \xff separated list into an array IFS=$'\xff' read -r -d '' -a list_of_sports < <(printf "%s" "$list_of_sports") echo "list_of_subjects : ${list_of_subjects[@]}" echo "list_of_sports : ${list_of_sports[@]}" done
Notez que ce sera probablement plus lent que la solution en utilisant awk
.
Notez que le principe de fonctionnement est le même que dans les autres réponses - remplacez \,
chaîne par un autre caractère unique, puis utilisez ce caractère pour parcourir les deuxième et troisième éléments de champ.
wrt Pourquoi awk et sed quand bash avec coreutils est juste assez
- parce que le faire avec une boucle bash prendrait plus de code, serait plus compliqué, plus difficile à écrire de manière robuste, serait moins portable et beaucoup plus lent que le faire avec awk. Les gars qui ont inventé shell ont également inventé awk pour shell à appeler pour manipuler du texte - ils avaient leurs raisons ...
Vous pouvez peut-être joindre des colonnes avec une fonction.
{ for (col=1; col<=NF; col++) { if ($col ~ /\\$/) { joincol(col) } } }
Cela peut être utilisé ainsi:
function joincol(col, i) { $col=$col FS $(col+1) for (i=col+1; i<NF; i++) { $i=$(i+1) } NF-- }
Notez que la décrémentation de NF n'est pas définie comportement dans POSIX. Il peut supprimer le dernier champ, ou il peut ne pas l'être, et toujours conforme POSIX. Cela fonctionne pour moi dans BSDawk et Gawk. YMMV. Peut contenir des noix.
Utilisez le FPAT
de gawk :
awk -v FPAT='(\\\\.|[^,\\\\]*)+' '{print gensub("\\\\", "", "g", $3)}' file #list_of_sports #football #tennis,ping_pong,chess
puis utilisez gnusub
pour remplacer les contre-obliques:
awk -v FPAT='(\\\\.|[^,\\\\]*)+' '{print $3}' file #list_of_sports #football #tennis\,ping_pong\,chess
Attention : FPAT
et gensub
sont des fonctionnalités spécifiques à Gawk.
Utilisation de Perl. Remplacez le \,
par un caractère de contrôle, dites \ x01
, puis remplacez-le à nouveau par ,
$ cat laxman.txt john,science\,social,football,florence_school james,painting,tennis\,ping_pong\,chess,highmount_school $ perl -ne ' s/\\,/\x01/g and print ' laxman.txt | perl -F, -lane ' for(@F) { if( /\x01/ ) { s/\x01/,/g ; print } } ' science,social tennis,ping_pong,chess
p >
Cela pourrait fonctionner pour vous (GNU sed):
sed -E 's/\\,/\n/g;y/,\n/\n,/;s/^[^,]*$//Mg;s/\n//g;/^$/d' file
Remplacez les virgules entre guillemets par des retours à la ligne, puis rétablissez les nouvelles lignes en virgules et les virgules en nouvelles lignes. Supprimez toutes les lignes qui ne contiennent pas de virgule. Supprimer les lignes vides.