3
votes

awk: Imprimez 1 $ avec un nombre variable de champs supplémentaires sur la même ligne

J'ai un fichier d'entrée avec ~ 100 lignes et ~ 100 champs par ligne. Chaque champ représente une valeur positive ou négative. Je souhaite imprimer 1 $ suivi uniquement des champs positifs ou négatifs dans chaque ligne. Le nombre de champs positifs ou négatifs par ligne est aléatoire.

exemple d'entrée

for j in 1 2; do
positive=ofile.p0
negative=ofile.m0

awk 'NR>5{
    printf $1>"positive";
    printf $1>"negative";
    for(i=3;i<=NF;i++)
       if($i~/[-+]?[0-9]+/)
           if ($i>0) printf OFS $i>"positive";
           else if($i<0) printf OFS $i>"negative";
    print "">"positive";
    print "">"negative";
}'ofile.0$j

mv positive $positive$j
mv negative $negative$j

done

résultat souhaité

positif

awk 'BEGIN {ORS="\t"} NR>5 {print $1} {for(i=3;i<=NF;i++) if ( $i > 0 && i <= NF} {print $i}}' input > output

negative

 0 9
 0 8
 0 7
 2 7
 2 6
 4 4
 4 3
 4 2
 4 1

contexte et tentative

Les sorties ci-dessus affichent $ 1, suivi des valeurs positives ou négatives restantes champs sur la même ligne que $ 1.

Le code actuel que j'ai essayé (pour les valeurs positives, à partir de la ligne 6 dans mon entrée):

awk 'NR>5{for(i=3; i<=NF; i++) if ( $i > 0 ) print $1, $i}' input > output

Ceci fonctionne bien, sauf que j'imprime une sortie comme:

0 -1 -2 -3
2 -2 -3 -4 -5
4 -6 -7

J'ai aussi essayé:

0 9 8 7
2 7 6
4 4 3 2 1

mais ensuite je passer à une nouvelle ligne dans la sortie. Si je change ORS en \ n via une condition 'else if (i = NF) {ORS = ...}', alors il imprime toutes les sorties de champ pour chaque i sur une nouvelle ligne, comme l'instruction BEGIN n'a aucun effet.

question

Comment puis-je dire à awk d'imprimer $ 1, puis imprimer toutes les autres sorties de la même ligne d'entrée sur la même ligne de sortie, puis avancer d'une nouvelle ligne dans la sortie et répéter le processus pour la ligne de saisie suivante?

Merci.

réponse à la réponse de Tiw

J'ai essayé d'exécuter ceci en boucle pour mes deux fichiers:

0 x 9 8 7 -1 -2 -3
2 x 7 6 -2 -3 -4 -5
4 x 4 3 2 1 -6 -7

mais il se bloque. Edit: la réponse de Tiw a été mise à jour avec% s dans printf. Cela fonctionne avec ce changement.

awk

7 commentaires

sortie vers un fichier différent.


@Blaisem, continuellement les valeurs + et - les valeurs arrivent toutes les deux mais pourquoi certaines lignes sont imprimées pour + et d'autres pour -? Pourriez-vous s'il vous plaît expliquer plus à ce sujet?


@ RavinderSingh13 Mon objectif est de séparer une entrée en valeurs positives et négatives. Je voudrais une sortie qui contient l'entrée d'origine, mais uniquement les champs qui avaient des valeurs positives. La deuxième sortie contient également l'entrée d'origine, mais uniquement les champs qui avaient des valeurs négatives. J'avais prévu d'avoir un code awk pour séparer les valeurs positives, et un second code awk pour séparer les valeurs négatives. Est ce que ça aide? Je vous remercie.


@Blaisem .. La solution Perl vous convient-elle? ..


Est-ce que 0 doit être considéré comme positif ou négatif ou pas du tout imprimé?


@EdMorton J'ignore 0 pour le moment (c'est-à-dire sans imprimer). J'ai pensé que si mes besoins changent, je peux toujours adapter n'importe quel code pour inclure les champs 0 si nécessaire.


@ stack0114106 Je n'ai jamais utilisé Perl, mais il est bon d'avoir des réponses diverses pour les futurs téléspectateurs. Merci d'avoir répondu!


3 Réponses :


3
votes

Essayez ceci:

awk 'NF>5{
    printf "%s",$1>"positive";
    printf "%s",$1>"negative"; 
    for(i=2;i<=NF;i++) 
        if($i~/^[-+]?[0-9]+$/)     ## Another and better way is $i == $i + 0 
            if ($i>0) printf "%s",OFS $i>"positive"; 
            else if($i<0) printf "%s",OFS $i>"negative"; 
    print "">"positive";
    print "">"negative";
}' input

Avec un fichier nommé input:

0 -1 -2 -3
2 -2 -3 -4 -5
4 -6 -7

Cela créera deux fichiers ,
un positif:

0 9 8 7
2 7 6
4 4 3 2 1

un négatif:

0 x 9 8 7 -1 -2 -3
2 x 7 6 -2 -3 -4 -5
4 x 4 3 2 1 -6 -7

Mettez plusieurs lignes pour une meilleure lisibilité:

awk 'NF>5{printf "%s",$1>"positive";printf "%s",$1>"negative"; for(i=2;i<=NF;i++) if($i~/^[-+]?[0-9]+$/) if ($i>0) printf "%s",OFS $i>"positive"; else if($i<0) printf "%s",OFS $i>"negative"; print "">"positive";print "">"negative";}' input

C'est assez simple, donc je suppose que c'est facile à comprendre.
Remarque Je n'ai pas utilisé {} pour citer le bloc après les pour et if , car ils n'ont chacun qu'une seule commande après, donc les citations peuvent être enregistrées.
print imprimera un caractère de nouvelle ligne \ n à la fin, printf ne le fera pas.
Aussi NR signifie N ombre de R ecords, c'est-à-dire le numéro de ligne, j'ai changé en NF , ce qui signifie N ombre de F ields, je pense que c'est ce que vous vouliez.

if ($ i ~ / ^ [- +]? [ 0-9] + $ /) est de tester le champ est un nombre.
Si le champ n'est pas vide, $ i == $ + 0 est un meilleur moyen.
Et combiné avec le test, le champ n'est pas 0 ou vide, utilisez $ i && ($ i == $ i + 0) .


7 commentaires

Je vous remercie! J'ai essayé de l'implémenter de différentes manières, mais le code se bloque. Je l'ai exécuté avec bash -xv et je ne vois pas la raison de l'erreur. J'essaye toujours de le regarder. J'ai modifié mon script dans la question originale en réponse à vous.


ah je viens de remarquer la modification de printf "% s". Je vais essayer ça maintenant.


$ i ~ / [- +]? [0-9] + / correspondrait à Joe90 et à d'autres non-nombres. Le test pour un nombre donné un $ i non nul est $ i == $ i + 0 . Pour l'impression, plutôt que 2 lignes très similaires, considérez une seule: printf "% s", OFS $ i> ($ i> = 0? "Positive": "négative") (en supposant que OP veut que 0 soit considéré comme positif)


@EdMorton Donc je peux remplacer cette ligne par $ i == $ i + 0? D'ailleurs, comme je n'ai que des chiffres, je pourrais simplement supprimer complètement la ligne, non? Également sur la suggestion d'impression, ($ i> = 0) n'imprimerait-il pas des valeurs positives sur ma sortie négative, ou le code en tient-il compte?


Bien, mais puisque vous ne voulez pas imprimer de zéros, changez-le en ($ i! = 0) && ($ i == $ i + 0) . Non, print ($ i> = 0? "Positif": "négatif") n'imprimera pas de nombres positifs sur votre sortie négative. Voir https://en.wikipedia.org/wiki/%3F: pour savoir comment les expressions ternaires fonctionnent.


@EdMorton J'ai oublié d'ajouter ^ et $ , merci pour la mise en garde :)


@Blaisem a mis à jour la réponse, ajouté ^ et $ à l'expression régulière, ma négligence, désolé. Et cette partie peut également être remplacée par if ($ i && ($ i == $ i + 0)) .



2
votes

La première chose à faire est de vérifier si le champ est un nombre, si tel est le cas, vous pouvez faire la vérification. Dans awk , vous pouvez vérifier si une variable est un nombre en y ajoutant zéro, et vérifier si elle renvoie la même valeur.

Pour les nombres positifs, procédez comme suit:

awk '{for(i=1;i<=NF;++i) if ($i+0 == $i && $i >= 0) printf $i OFS; printf ORS}' file


0 commentaires

1
votes

Si Perl est une option,

Entrez:

$  perl -ne ' open(POS,">>pos.txt"); open(NEG,">>neg.txt"); @p=/(\S+)(?<=\d)/g; 
          print POS "$p[0] "; print NEG "$p[0] "; 
           for(@p[1..$#p]) { print NEG "$_ " if $_ < 0; print POS "$_ " if $_>=0  } 
             print POS "\n"; print NEG "\n" ' blaisem.txt

$ cat pos.txt
0 9 8 7
2 7 6
4 4 3 2 1

$ cat neg.txt
0 -1 -2 -3
2 -2 -3 -4 -5
4 -6 -7

$

+ ve et -ve exécutions séparées

$ perl -ne ' @p=/(\S+)(?<=\d)/g;print "$p[0] "; for(@p[1..$#p]) { print "$_ " if $_ >=0 } print "\n" ' blaisem.txt
0 9 8 7
2 7 6
4 4 3 2 1

$ perl -ne ' @p=/(\S+)(?<=\d)/g;print "$p[0] "; for(@p[1..$#p]) { print "$_ " if $_ < 0 } print "\n" ' blaisem.txt
0 -1 -2 -3
2 -2 -3 -4 -5
4 -6 -7

$

+ ve et -ve dans un seul script

$ cat blaisem.txt
0 x 9 8 7 -1 -2 -3
2 x 7 6 -2 -3 -4 -5
4 x 4 3 2 1 -6 -7

$


0 commentaires