Quelle est la bonne façon de filtrer un fichier texte pour supprimer les lignes vides?

11

J'ai un fichier .csv (sur un mac) qui a un tas de lignes vides, par exemple:

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum 

lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum 

lorem ipsum ","2","3","4"

Que je veux convertir:

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum  lorem ipsum ","2","3","4"

Je sais qu'il doit y avoir un paquebot mais je ne connais pas awk ou sed. Tous les conseils sont grandement appréciés!

pitosalas
la source
1
Selon cet exemple, vous souhaitez réellement supprimer les sauts de ligne intégrés des champs. Est-ce exact? En d'autres termes, il y a 6 lignes d'entrée et devrait être 2 lignes de sortie?
manatwork
Oui, c'est exactement ce dont j'essaie de me débarrasser: des sauts de ligne intégrés à l'intérieur d'une chaîne entre guillemets.
pitosalas
Vous avez donc besoin de quelque chose qui supprime les retours à la ligne entre guillemets. Cela va être un peu plus compliqué, car vous avez besoin d'une expression régulière multiligne.
tongpu

Réponses:

11

Vous pouvez utiliser le mode grep -v(invert match) pour ce faire:

grep -v '^$' old-file.csv > new-file.csv

Notez que ces fichiers doivent être différents, en raison du fonctionnement des redirections shell. Le fichier de sortie est ouvert (et vidé) avant la lecture du fichier d'entrée. Si vous avez des moreutils (pas par défaut sur Mac OS X), vous pouvez utiliser spongepour contourner cela:

grep -v '^$' file.csv | sponge file.csv

Mais bien sûr, vous aurez plus de mal à revenir en arrière en cas de problème.

Si vos "lignes vierges" peuvent réellement contenir des espaces (cela semble être le cas), vous pouvez utiliser ceci à la place:

egrep -v '^[[:space:]]*$' old-file.csv > new-file.csv

Cela ignorera les lignes vides ainsi que les lignes contenant uniquement des espaces. Vous pouvez bien sûr faire la même spongetransformation dessus.

derobert
la source
Merci .... N'a supprimé aucune ligne vide ... Peut-être que ^ $ ne correspond pas? Mais les lignes sont vides à ma connaissance. Rappelez-vous que c'est un cdv créé par Excel sur un mac ... Est-ce que ça dit quelque chose? (Ne vous
enfuyez
@pitosalas Ce ne sont probablement pas des lignes vides. Essayez de le changer pour egrep -v '^[[:space:]]*$'... noter grep -> egrep et le nouveau motif étrange
derobert
Ça n'a pas marché. Supprimé un tas de guillemets doubles et fait un gâchis ...
pitosalas
@pitosalas Je ne sais pas comment cela supprimerait les guillemets doubles. Il ne devrait pouvoir supprimer que les espaces. Et en effet, c'est ce qu'il fait quand je le teste sur les données d'exemple que vous avez publiées ...
derobert
@pitosalas pourriez-vous vérifier si l'une de ces commandes crache quelque chose qui semble raisonnable (par opposition à du charabia): iconv -f utf16le file.csv | headouiconv -f utf16be file.csv | head
derobert
8

L'option la plus simple est juste grep .. Ici, le point signifie "correspondre à n'importe quoi", donc si la ligne est vide, elle ne correspond pas. Sinon, il imprime la ligne entière telle quelle.

Onturenio
la source
6

Pour supprimer les lignes vides, en place , avec ksh93:

sed '/./!d' file 1<>; file

L' <>;opérateur de redirection est spécifique à ksh93 et ​​est le même que l' <>opérateur standard , sauf que ksh tronque le fichier une fois la commande terminée.

sed '/./!d'est une façon compliquée d'écrire grep ., mais malheureusement GNU grep se plaint au moins si sa sortie stdout pointe vers le même fichier que son stdin. Vous diriez que l'on pourrait écrire:

grep . file | cat 1<>; file

Mais malheureusement, il y a un bogue dans ksh93 (au moins ma version (93u +)), en ce que le fichier semble être tronqué à zéro dans ce cas.

grep . file | { cat; } 1<>; file

Semble contourner ce bogue, mais maintenant, il est beaucoup plus compliqué que la commande sed.

Stéphane Chazelas
la source
Veuillez combiner vos réponses en une entrée bien formatée avec un guide rapide pour savoir quand chaque solution doit être utilisée. Les différentes approches de différents problèmes confondus dans des réponses flottantes ont rendu cette question un peu désastreuse à lire.
Caleb
@Caleb, Tout se résume à une question très peu claire, donc toutes les réponses de chacun sont pour des interprétations différentes de la question. Pour chaque réponse, j'ai essayé de dire à quelle question il essayait de répondre.
Stéphane Chazelas
Juste pour info: essayé awk '/./' file 1<>; filece qui a fonctionné. Pour moi, c'est encore plus clair quesed '/./!d'
grebneke
5

Voici une Perldoublure pour cela:

perl -pi -e 's/^\s*\n//' yourfile

EDIT: Code amélioré basé sur les commentaires de ruakh ci-dessous.

Joseph R.
la source
1
Ouperl -ni -e '/./ and print' yourfile
derobert
1
@peterph $est une ancre (c'est-à-dire de largeur nulle) donc elle exclut la nouvelle ligne. En ce qui concerne l'espace superflu, c'est la raison pour laquelle j'ai ajouté que /xje ne voulais Perlpas essayer d'interpoler `$ \` dans l'expression régulière
Joseph R.
1
Vous n'en avez pas besoin $, étant donné que vous en avez \n. (Alternativement - vous n'avez pas besoin du \n, étant donné que vous avez le \s*et le $; mais je pense s/^\s*\n//qu'il est plus clair que la nouvelle ligne est supprimée.) Vous n'avez pas non plus besoin du /m; cela n'a aucun effet sur cette commande. Et une fois que vous vous serez débarrassé de l' $espace et de l'espace, vous n'en aurez plus besoin /x.
ruakh
1
@JosephR .: Le \nlui - même peut être supprimé; ce que vous ne pouvez pas faire, c'est supprimer à la fois le $ et le \n. Il y s/^\s*//aurait donc le problème que vous décrivez, mais ce s/^\s*$//serait bien, à cause du \s*et du $. (Voyez-vous ce que je veux dire?)
ruakh
1
@JosephR. Ce qui se passe, c'est que la correspondance $ peut être effectuée avant une nouvelle ligne (à condition que l' /mindicateur soit activé ou que la nouvelle ligne soit le tout dernier caractère de la chaîne, ou les deux), mais elle peut également correspondre à la fin de la chaîne. Par exemple, "abc" =~ m/^abc$/c'est vrai. Dans le cas de \s*$, le \s*est suffisamment gourmand pour manger la nouvelle ligne, puis le $correspond à la fin de chaîne. (Mais je pense que s/^\s*\n//c'est plus clair, de toute façon, donc votre réponse est très bien comme elle est maintenant.)
ruakh
5

Sur la base de la clarification dans les commentaires de votre question, quelque chose comme:

awk -v RS= -v ORS= 1

peut faire ce que vous voulez.

Un séparateur d'enregistrements vide est un cas spécial qui indique awkque les enregistrements doivent être des paragraphes (séparés par des séquences de lignes vides). La définition du séparateur d'enregistrements de sortie sur la chaîne vide signifie également que le contenu de ces paragraphes (sans les séparateurs) doit être concaténé. 1est juste une vraie condition pour imprimer chaque enregistrement.

Cela omettrait cependant la nouvelle ligne de fin, vous pouvez donc faire:

awk -v RS= -v ORS= '1;END{if (NR) printf "\n"}'
Stéphane Chazelas
la source
3

Je sais que cela aurait été plus facile si j'avais donné le fichier, mais malheureusement, il contenait des informations confidentielles que je ne pouvais pas partager. En attendant je me suis écrit un script rubis qui semblait faire l'affaire:

require 'csv'
c = CSV.open("outfile1.csv", "w")
CSV.foreach("data.csv", :encoding => 'windows-1251:utf-8') do |row|
  row = row.map { |a| a.class == String ? a.gsub(/\r/, '') : a}
  c << row
end
c.close

Merci à tous pour votre aide!

pitosalas
la source
2
awk '
    length == 0 {next} 
    /^[^"]/ && /"$/ {print; next} 
    {printf("%s", $0)}
' filename

produit

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
glenn jackman
la source
2

J'ai trouvé une idée pour une solution possible sur stackoverflow .

sed -i ':a;N;$!ba;s/[^"]\n\s*\n/ /g' file.csv

Vous devriez probablement sauvegarder votre fichier csv avant de le tester, mais au moins pour l'exemple que vous avez fourni, il fonctionne parfaitement.

Une bonne explication sur le fonctionnement interne de cette expression est offerte dans la réponse, je viens de la modifier pour rechercher des lignes qui ne se terminent pas par un "( [^"]\n).

tongpu
la source
1

Si, à partir de votre propre réponse, vous souhaitez supprimer les caractères de nouvelle ligne contenus dans les chaînes entre guillemets, vous pouvez faire:

 perl -0777 -pe 's/".*?"/$_=$&;s:\n::g;$_/gse'

Vous pouvez également utiliser l' -iindicateur perl pour modifier les fichiers en place .

 perl -0777 -pe 's/".*?"/$_=$&;s:\n::g;$_/gse' file1 file2...

Ou avec GNU awk:

 awk -v RS=\" 'NR%2==0 {gsub("\n","")}; {printf "%s", $0 RT}'

ou:

 awk -vRS=\" '1-NR%2{gsub("\n","")}{ORS=RT}1'

(si vous êtes en compétition pour le plus court)

Notez que les supposer qu'il n'y a pas échappèrent caractères à double citation dans l'entrée.

Stéphane Chazelas
la source
0

Il semble en effet que vous souhaitiez plus que la suppression de lignes vides, mais supprimez chaque séquence de 2 ou plusieurs caractères de nouvelle ligne.

Ce que vous pourriez faire avec perl:

perl -0777 -pe 's/\n{2,}//gs' file

Vous pouvez également utiliser l' -iindicateur perl pour modifier les fichiers en place .

perl -0777 -pi -e 's/\n{2,}//gs' file1 file2...
Stéphane Chazelas
la source
0

Il existe un moyen toujours plus court de supprimer les lignes vides dans AWK:

awk 'NF' file

Mais pour obtenir la sortie que vous voulez, tout ce dont vous avez besoin est une simple doublure:

awk 'NF {printf("%s ", $0); i++;} !(i % 2) {printf("\n");}' file

Explication

Dans AWK, une ligne vide signifie que la ligne / l'enregistrement n'a pas de champs, c'est-à-dire que la NFvariable (Nombre de champs) est nulle. Le liner ci-dessus ne s'exécutera que lors de l' NF > 0impression de toutes les lignes, mais les lignes vides.

Le i++est le compteur de lignes non vide.

Le !(i % 2)est utilisé pour imprimer deux lignes consécutives non vides à la manière de la sortie souhaitée, c'est-à-dire que chaque fois qu'un multiple de 2 est trouvé, l' moduloinstruction !(i % 2)donne 1, ce qui termine la concaténation de deux lignes non vides.

Marcelo Augusto
la source
Ma faute! Pardon. Je n'ai pas lu toute sa question et la sortie souhaitée. La réponse est maintenant corrigée. Merci. :-)
Marcelo Augusto
0

Vous pouvez utiliser Vim en mode Ex:

ex -sc v/./d -cx b.csv
  1. v/./ trouver des lignes vides

  2. d supprimer

  3. x sauver et fermer

Steven Penny
la source