Remplacer la chaîne par un index séquentiel

10

Quelqu'un peut-il suggérer une manière élégante d'accomplir cela?

Contribution:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

la sortie doit être:

test      instant1  ()

test      instant2  ()

test      instant1000()

Les lignes vides sont dans mes fichiers d'entrée et il y a beaucoup de fichiers dans le même répertoire que je dois traiter en même temps.

J'ai essayé cela pour remplacer de nombreux fichiers dans le même répertoire et je n'ai pas fonctionné.

for file in ./*; do perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

les erreurs:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

et j'ai aussi essayé ça:

perl -i -pe 's/instant/$& . ++$n/ge' *.vs

Cela a fonctionné mais l'index n'a cessé de s'incrémenter d'un fichier à l'autre. Je voudrais réinitialiser cela à 1 lors de la modification d'un nouveau fichier. Des bonnes suggestions?

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

fonctionne mais il a remplacé tous les autres fichiers ne devraient pas être remplacés. Je préfère simplement remplacer les fichiers par *.txtseulement.

user3342338
la source
Et sont-ils tous constitués exclusivement de lignes vides ou test instant ()?
terdon
Je remets les lignes à double interligne, elles sont souvent le signe que de nouveaux utilisateurs ne savent pas comment utiliser le balisage de ce site, c'est pourquoi terdon les a supprimés tout en indentant correctement votre bloc de contenu de fichier afin qu'il s'affiche comme contenu de fichier. J'espère que ça va maintenant.
Timo

Réponses:

14
perl -pe 's/instant/$& . ++$n/ge'

ou avec GNU awk:

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Pour modifier les fichiers sur place, ajoutez l' -ioption à perl:

perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*.vs

Ou récursivement:

find . -name '*.vs' -type f -exec perl -pi -e '
  s/instant/$& . ++$n{$ARGV}/ge' {} +

Explications

perl -pe 's/instant/$& . ++$n/ge'

-pconsiste à traiter l'entrée ligne par ligne, à évaluer l'expression transmise à -echaque ligne et à l'imprimer. Pour chaque ligne, nous substituons (en utilisant l' s/re/repl/flagsopérateur) instantà lui-même ( $&) et la valeur incrémentée d'une variable ++$n. Le gdrapeau est de faire la substitution globalement (pas seulement une fois), et de esorte que le remplacement soit interprété comme du code perl à valuer (pas une chaîne fixe).

Pour l'édition sur place où un appel Perl traite plus d'un fichier, nous voulons $nréinitialiser à chaque fichier. Au lieu de cela, nous utilisons $n{$ARGV}(où $ARGVest le fichier actuellement traité).

Celui awkmérite un peu d'explication.

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Nous utilisons la capacité de GNU awkpour séparer les enregistrements sur des chaînes arbitraires (même les regexps). Avec -vRS=instant, nous réglons le séparateur d'enregistrement sur instant. RTest la variable qui contient ce qui a été mis en correspondance RS, donc généralement, instantsauf pour le dernier enregistrement où ce sera la chaîne vide. Dans l'entrée ci-dessus, les enregistrements ( $0) et les terminateurs d'enregistrement ( RT) sont ( [$0|RT]):

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

Il nous suffit donc d'insérer un nombre incrémentiel au début de chaque enregistrement, sauf le premier.

C'est ce que nous faisons ci-dessus. Pour le premier enregistrement, nsera vide. Nous définissons ORS (le séparateur d'enregistrement de sortie ) sur RT, de sorte que les awk impressions n $0 RT. Il le fait sur la seconde expression ( ++n) qui est une condition qui est toujours évaluée à vrai (un nombre non nul), et donc l'action par défaut (d'impression $0 ORS) est effectuée pour chaque enregistrement.

Stéphane Chazelas
la source
4
Cela pourrait utiliser un peu d'explication .
Gilles 'SO- arrête d'être méchant'
5

sedn'est vraiment pas le meilleur outil pour le travail, vous voulez quelque chose avec de meilleures capacités de script. Voici quelques choix:

  • perl

    perl -00pe 's/instant/$& . $./e' file 

    Le -pmoyen "imprime chaque ligne" après avoir appliqué le script fourni -e. Le -00mode "paragraphe" est activé pour que les enregistrements (lignes) soient définis par des caractères newline ( \n) consécutifs , ce qui lui permet de traiter correctement les lignes à double interligne. $&est le dernier motif correspondant et $.est le numéro de ligne actuel du fichier d'entrée. Le een s///eme permet d'évaluer les expressions de l'opérateur de substitution.

  • awk (cela suppose que vos données sont exactement comme indiqué, avec trois champs séparés par des espaces)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 

    Ici, nous n'incrémentons la kvariable kque si la ligne actuelle n'est pas vide, /./auquel cas nous imprimons également les informations nécessaires. Les lignes vides sont imprimées telles quelles.

  • coquilles diverses

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 

    Ici, chaque ligne d'entrée est automatiquement divisée en espaces blancs et les champs sont enregistrés sous $a, $bet $c. Ensuite, à l'intérieur de la boucle, $cest augmenté d'un pour chaque ligne pour laquelle $aest pas vide et sa valeur actuelle est imprimé à côté de la seconde trame, $b.

REMARQUE: toutes les solutions ci-dessus supposent que toutes les lignes du fichier sont du même format. Sinon, la réponse de @ Stéphane est la voie à suivre.


Pour gérer de nombreux fichiers et en supposant que vous souhaitez effectuer cette opération sur tous les fichiers du répertoire en cours, vous pouvez utiliser ceci:

for file in ./*; do perl -i -00pe 's/instant/$& . $./e' "$file"; done

ATTENTION: Cela suppose des noms de fichiers simples sans espaces, si vous avez besoin de traiter quelque chose de plus complexe, optez pour (en supposant ksh93, zshou bash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done
terdon
la source
le script perl fonctionne. cependant il y a un petit problème si les lignes sont à double espace.
user3342338
@ user3342338 oui, cela incrémentera le compteur puisque j'utilise le numéro de ligne actuel. C'est une approche très naïve, comme je l'ai dit, celle de Stéphane est plus robuste. Rien de tout cela ne fonctionne si vous avez des lignes vides ou si l'une de vos lignes s'écarte de ce que vous montrez.
terdon
@ user3342338 voir la réponse mise à jour. Ils devraient tous fonctionner maintenant pour les fichiers à double interligne.
terdon
Grande réponse et l'option de méthodes alternatives !! Merci
Madivad
0

Si vous souhaitez résoudre ce problème avec, sedvous pouvez utiliser quelque chose comme ceci (en bash):

i=0
while read -r line; do
  sed "s/\(instant\)/\1${i}/" <<< "${line}"
  [[ ${line} =~ instant ]] && i=$(( i + 1 ))
done < file

ou une solution plus portable serait:

i=0
while read -r line; do
  echo "${line}" | sed "s/\(instant\)/\1${i}/"
  if echo "${line}" | grep -q inst; then
    i=$(( i + 1 ))
  fi
done < file
noAnton
la source