web-dev-qa-db-fra.com

Comment trouver / remplacer et incrémenter un numéro correspondant avec sed / awk?

Droit au but, je me demande comment utiliser grep/find/sed/awk pour faire correspondre une certaine chaîne (qui se termine par un nombre) et incrémenter ce nombre de 1. Le plus proche que je suis venu est de concaténer un 1 à la fin (qui fonctionne assez bien) car le point principal est de simplement changer la valeur. Voici ce que je fais actuellement:

find . -type f | xargs sed -i 's/\(\?cache_version\=[0-9]\+\)/\11/g'

Comme je ne pouvais pas comprendre comment augmenter le nombre, j'ai capturé le tout et j'ai simplement ajouté un "1". Avant, j'avais quelque chose comme ça:

find . -type f | xargs sed -i 's/\?cache_version\=\([0-9]\+\)/?cache_version=\11/g'

Donc, au moins, je comprends comment capturer ce dont j'ai besoin.

Au lieu d'expliquer à quoi cela sert, je vais simplement expliquer ce que je veux qu'il fasse. Il devrait trouver du texte dans n'importe quel fichier, récursivement, en fonction du répertoire actuel (ce n'est pas important, il pourrait s'agir de n'importe quel répertoire, donc je le configurerais plus tard), qui correspond à "? Cache_version =" avec un numéro. Il incrémentera ensuite ce nombre et le remplacera dans le fichier.

Actuellement, ce que j'ai ci-dessus fonctionne, c'est juste que je ne peux pas incrémenter ce nombre trouvé à la fin. Il serait plus agréable de pouvoir incrémenter au lieu d’ajouter un "1" afin que les valeurs futures ne soient pas "11", "111", "1111", "11111", etc.

J'ai parcouru des dizaines d'articles/explications, et souvent assez, la suggestion est d'utiliser awk, mais je ne peux pas pour la vie de les mélanger. Le plus proche que j'ai pu utiliser awk, qui ne remplace rien, est:

grep -Pro '(?<=\?cache_version=)[0-9]+' . | awk -F: '{ print "match is", $2+1 }'

Je me demande s'il existe un moyen de diriger un sed à la fin et de passer le nom du fichier d'origine afin que sed puisse avoir le nom de fichier et le numéro incrémenté (à partir du awk), ou tout ce dont il a besoin que xargs a.

Techniquement, ce nombre n'a pas d'importance; ce remplacement vise principalement à s'assurer qu'il y a un nouveau numéro, 100% certainement différent du précédent. Alors que j'écrivais cette question, j'ai réalisé que je pourrais aussi bien utiliser le temps système - secondes depuis Epoch (la technique souvent utilisée par AJAX pour éliminer la mise en cache pour les requêtes "identiques" suivantes). I fini avec cela, et il semble parfait:

CXREPLACETIME=`date +%s`; find . -type f | xargs sed -i "s/\(\?cache_version\=\)[0-9]\+/\1$CXREPLACETIME/g"

(Je stocke la valeur en premier afin que tous les fichiers obtiennent la même valeur, au cas où cela s'étend sur plusieurs secondes pour une raison quelconque)

Mais j'aimerais quand même connaître la question d'origine, sur l'incrémentation d'un nombre correspondant. Je suppose qu'une solution simple serait d'en faire un script bash, mais je pensais tout de même qu'il y aurait un moyen plus simple que de parcourir chaque fichier récursivement et de vérifier son contenu pour une correspondance, puis de le remplacer, car il s'agit simplement d'incrémenter un nombre correspondant ... pas beaucoup d'autre logique. Je ne veux tout simplement pas écrire dans d'autres fichiers ou quelque chose comme ça - il devrait le faire en place, comme sed le fait avec l'option "i".

37
Ian

Je pense que trouver un fichier n'est pas la partie difficile pour vous. Je vais donc juste au point, pour faire le calcul +1. Si vous avez gnu sed , cela pourrait se faire de cette façon:

sed -r 's/(.*)(\?cache_version=)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' file

prenons un exemple:

kent$  cat test 
Ello
barbaz?cache_version=3fooooo
bye

kent$  sed -r 's/(.*)(\?cache_version=)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' test     
Ello                                                                             
barbaz?cache_version=4fooooo
bye

vous pouvez ajouter l'option -i si vous le souhaitez.

modifier

/e vous permet de passer la partie correspondante à une commande externe et de faire une substitution avec le résultat de l'exécution. Gnu sed seulement.

voir cet exemple: commande/outil externe echo, bc sont utilisés

kent$  echo "result:3*3"|sed -r 's/(result:)(.*)/echo \1$(echo "\2"\|bc)/ge'       

donne la sortie:

result:9

vous pouvez utiliser une autre commande externe puissante, comme cut, sed (encore), awk ...

54
Kent

Version pure sed:

Cette version n'a pas de dépendances sur d'autres commandes ou variables d'environnement. Il utilise un portage explicite. Pour le transport, j'utilise le symbole @, mais un autre nom peut être utilisé si vous le souhaitez. Utilisez quelque chose qui n'est pas présent dans votre fichier d'entrée. Il trouve d'abord SEARCHSTRING<number> et y ajoute un @. Il répète les chiffres incrémentés qui ont un report en attente (c'est-à-dire, ont un symbole de report après: [0-9]@) Si 9 a été incrémenté, cet incrément produit un report lui-même et le processus se répétera jusqu'à ce qu'il n'y ait plus de report en attente. Enfin, les portées qui ont été cédées mais qui n'ont pas encore été ajoutées à un chiffre sont remplacées par 1.

sed "s/SEARCHSTRING[0-9]*[0-9]/&@/g;:a {s/0@/1/g;s/1@/2/g;s/2@/3/g;s/3@/4/g;s/4@/5/g;s/5@/6/g;s/6@/7/g;s/7@/8/g;s/8@/9/g;s/9@/@0/g;t a};s/@/1/g" numbers.txt
9
Martijn

Cette commande Perl recherchera tous les fichiers dans le répertoire courant (sans le traverser, vous aurez besoin du module File::Find Ou similaire pour cette tâche plus complexe) et incrémentera le nombre d'une ligne qui correspond à cache_version=. Il utilise l'indicateur /e De l'expression régulière qui évalue la pièce de rechange.

Perl -i.bak -lpe 'BEGIN { sub inc { my ($num) = @_; ++$num } } s/(cache_version=)(\d+)/$1 . (inc($2))/eg' *

Je l'ai testé avec file dans le répertoire courant avec les données suivantes:

hello
cache_version=3
bye

Il sauvegarde le fichier d'origine (ls -1):

file
file.bak

Et file maintenant avec:

hello
cache_version=4
bye

J'espère que cela peut être utile pour ce que vous recherchez.


UPDATE pour utiliser File::Find pour parcourir les répertoires. Il accepte * Comme argument mais les rejettera avec ceux trouvés avec File::Find. Le répertoire pour commencer la recherche est le courant d'exécution du script. Il est codé en dur dans la ligne find( \&wanted, "." ).

Perl -MFile::Find -i.bak -lpe '

    BEGIN { 
        sub inc { 
            my ($num) = @_; 
            ++$num 
        }

        sub wanted {
            if ( -f && ! -l ) {  
                Push @ARGV, $File::Find::name;
            }
        }

        @ARGV = ();
        find( \&wanted, "." );
    }

    s/(cache_version=)(\d+)/$1 . (inc($2))/eg

' *
7
Birei

C'est moche (je suis un peu rouillé), mais voici un début en utilisant sed:

orig="something1" ;
text=`echo $orig | sed "s/\([^0-9]*\)\([0-9]*\)/\1/"` ;
num=`echo $orig | sed "s/\([^0-9]*\)\([0-9]*\)/\2/"` ;
echo $text$(($num + 1))

Avec un nom de fichier d'origine ($orig) de "quelque chose1", sed sépare le texte et les parties numériques en $text et $num, puis ceux-ci sont combinés dans la section finale avec un nombre incrémenté, résultant en something2.

Ce n'est qu'un début, car il ne prend pas en compte les cas avec des numéros dans le nom de fichier ou les noms sans numéro à la fin, mais nous espérons que cela aidera votre objectif initial d'utiliser sed.

Cela peut en fait être simplifié dans sed en utilisant des tampons, je crois (sed peut fonctionner récursivement), mais je suis vraiment rouillé avec cet aspect.

3
David Ravetti