Comment supprimer une ligne si elle contient un caractère exactement une fois


10

Je veux supprimer une ligne d'un fichier qui ne contient un caractère particulier qu'une seule fois, si elle est présente plusieurs fois ou n'est pas présente, conservez la ligne dans le fichier.

Par exemple:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Ici, le caractère que je veux supprimer est Cainsi, la commande doit supprimer les lignes FGTHDCet JUTDYCparce qu'elles ont Cexactement une fois.

Comment puis-je faire cela en utilisant soit sedou awk?

Réponses:


20

Dans, awkvous pouvez définir le séparateur de champ sur n'importe quoi. Si vous le définissez sur C, vous aurez autant de champs +1 que d'occurrences de C.

Donc, si vous dites que awk -F'C' '{print NF}' <<< "C1C2C3"vous obtenez 4: CCCconsiste en 3 Cs, et donc 4 champs.

Vous souhaitez supprimer les lignes dans lesquelles Cse produit exactement une fois. En tenant compte de cela, dans votre cas, vous souhaiterez supprimer les lignes dans lesquelles il y a exactement deux Cchamps. Il suffit donc de les ignorer:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD

4
Utilisation astucieuse du awkséparateur de champ!
Valentin B.

interressant, comme dans le cas par défaut (FS = "") il ignore les espaces de tête ($ 1 = le premier non-espace sur la ligne) et aussi les répétitions (vous pouvez avoir 5 espaces pour séparer les champs 1 et 2) ... espace est probablement traité spécialement? (pour le voir, on peut le faire awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'et le nourrir de quelques lignes, certaines ayant plusieurs spces, et d'autres commençant par des espaces)
Olivier Dulac

2
@OlivierDulac, oui, l'espace est géré spécialement comme spécifié par POSIX .
Wildcard

8

approche sed :

sed -i '/^[^C]*C[^C]*$/d' input

-i L'option permet la modification de fichier sur place

/^[^C]*C[^C]*$/- correspond aux lignes qui Cne contiennent qu'une seule fois

d - supprimer les lignes correspondantes


8

Cela peut être fait avec sed:

Code:

sed '/C.*C/p;/C/d' file1

Résultats:

DTHGTY
HYTRHD
HTCCYD

Comment?

  1. Associez et imprimez n'importe quelle ligne avec au moins deux copies de Cvia/C.*C/p
  2. Supprimez toute ligne avec un Cvia /C/d, cela inclut les lignes déjà imprimées à l'étape 1
  3. Imprimer par défaut le reste des lignes

2
Approche alternative intelligente; Je l'aime.
Wildcard

6

Cela supprime les lignes avec exactement une occurrence de C.

grep -v '^[^C]*C[^C]*$' file

L'expression régulière [^C]correspond à un caractère qui n'est pas C (ou nouvelle ligne), et l'opérateur de répétition (alias étoile de Kleene) *spécifie zéro ou plusieurs répétitions de l'expression précédente.

La sortie par défaut de grep(et de la plupart des autres outils orientés texte) est la sortie standard; rediriger vers un nouveau fichier et peut-être le déplacer par-dessus le fichier d'origine si c'est ce que vous voulez. Le même regex peut être utilisé avec sed -ipour l'édition sur place:

sed -i '/^[^C]*C[^C]*$/d' file

(Sur certaines plates-formes, notamment * BSD, y compris macOS, l' -ioption nécessite un argument, comme -i ''.)


1
sed -i '/^[^C]*C[^C]*$/d' file- on dirait qu'il a été posté avant, comment pensez-vous, plagiat?
RomanPerekhrest

1
En effet, il y a une certaine duplication. J'ai commencé par la grepréponse, mais elle s'étend évidemment facilement à la sed -ivariante. Je n'ai pas vu votre réponse car je cherchais des grepréponses précédentes .
tripleee

1
Il est plus sûr de ne pas tout simplement -iavec sedet au lieu de rediriger vers un nouveau fichier et remplacer l'original que si l' sedutilitaire est sorti sans erreur.
Kusalananda

2
Ougrep -vx '[^C]*C[^C]*'
Stéphane Chazelas

@Kusalananda Mais alors vous pourriez aussi bien l'utiliser grepparce qu'il est plus clair et plus robuste (en particulier, seda un code de sortie moins informatif).
tripleee

4

L'outil POSIX pour les modifications scriptées d'un fichier (plutôt que d'imprimer le contenu modifié en sortie standard) est ex.

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Bien sûr, vous pouvez l' utilisersed -i si votre version de Sed le prend en charge, sachez que ce n'est pas portable si vous écrivez un script destiné à être exécuté sur différents types de systèmes.


David Foerster a demandé dans les commentaires:

Y a-t-il une raison pour laquelle vous utilisez printfet non echoou quelque chose comme ça ex -c COMMAND?

Réponse: oui.

Pour printfvs echoc'est une question de portabilité; voir Pourquoi printf est-il meilleur que l'écho? Et il est également plus facile de séparer les nouvelles lignes entre les commandes à l'aide de printf.

Pour printf ... | exvs. ex -c ..., c'est une question de gestion des erreurs. Pour cette commande spécifique, cela n'aurait pas d'importance, mais en général c'est le cas; par exemple, essayez de mettre

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

dans un script. Comparez avec ce qui suit:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

Le premier se bloque et attend l'entrée; le second se fermera lorsque EOF sera reçu par la excommande, donc le script continuera. Il existe d'autres solutions de contournement, telles que s///e, mais elles ne sont pas spécifiées par POSIX. Je préfère utiliser le formulaire portable, illustré ci-dessus.

Pour la gcommande, il doit y avoir une nouvelle ligne à la fin, et je préfère utiliser printfpour encapsuler les commandes plutôt que d'incorporer une nouvelle ligne entre guillemets simples.


1
Y a-t-il une raison pour laquelle vous utilisez printfet non echoou quelque chose comme ça ex -c COMMAND?
David Foerster

@DavidFoerster, oui. J'ai commencé à vous répondre dans les commentaires mais cela s'est allongé, alors je l'ai ajouté à la réponse.
Wildcard

Merci et +1! Je connaissais printfVS echo(bien que je préfère généralement echoquand l'argument est codé en dur), mais je ne l'ai pas exbeaucoup utilisé jusqu'à présent.
David Foerster

2

Voici quelques options utilisant perl.

Puisque vous ne correspondez qu'à un seul caractère, vous pouvez utiliser tr/C//(une traduction, sans remplacement), pour renvoyer le nombre de correspondances de C:

perl -lne 'print if tr/C// != 1' file

Plus généralement, si vous souhaitez faire correspondre une chaîne à plusieurs caractères ou une expression régulière, vous pouvez utiliser ceci:

perl -lne 'print if (@m = /C/g) != 1' file

Cela affecte les correspondances de l'expression régulière /C/gà une liste @met imprime des lignes lorsque la longueur de cette liste n'est pas 1.

Le -icommutateur peut être ajouté pour modifier "sur place".


2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

Notez qu'il assume GNU sed, t #...typiquement une branche à l'étiquette appelée #...dans la plupart des autres sedimplémentations.
Stéphane Chazelas

Même le !bGNU sed car la branche n'aime rien sauf une étiquette ou une nouvelle ligne après.

Oui, b, t, :, }(et r file, w file...) ne peut pas avoir une commande après eux sur la même ligne. Vous pouvez également utiliser des -eoptions distinctes .
Stéphane Chazelas

Votre option perl ne produit pas la sortie correcte. Je suppose que vous avez oublié d'ajouter le gmodificateur.
Tom Fenech

@TomFenech Vous avez raison. Je corrige ça. Merci.

1

Pour tous ceux qui le souhaitent awk, je proposerais

awk '/C[^C]*C/{next}//{print}'

sautez la ligne si elle correspond au motif, imprimez-la autrement. Vous n'avez pas vraiment besoin {print}, vous pouvez utiliser //et imprimer par défaut, mais je pense que c'est plus clair.

Ma première pensée a été d'utiliser egrep -vle même modèle, mais cela ne répond pas réellement à la question posée.


1
Quel est l'intérêt de faire correspondre quelque chose après {next}? Dites simplement awk '/pattern/ {next} 1'et toutes les lignes ne correspondant pas au motif seront imprimées. Ou, mieux, awk '!/pattern/'pour les imprimer directement.
fedorqui

@fedorqui bon point à propos de !/pattern/(qui a en quelque sorte glissé mon esprit) mais je préfère de loin voir un explication //{print}plutôt qu'un cryptique 1. Supposez le moins de compétence et de maîtrise de la part de la personne suivante pour maintenir votre code, tout en évitant de le rendre sérieusement moins efficace ou efficient.
nigel222
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.