2
votes

Mettre à jour un champ spécifique avec sed

J'essaye de mettre à jour un champ spécifique sur une ligne spécifique avec la commande sed dans Bourne Shell.

Disons que j'ai un fichier TopScorer.txt

player="Salah"
NewGoals="10"
OldGoals=$(awk -F':' '$1=="'$player'"' TopScorer.txt | cut -d':' -f3)
sed -i '/^'$player'/ s/'$OldGoals'/'$NewGoals'/' TopScorer.txt

Output> Salah:10:9:3 instead of Salah:9:10:3

Et j'ai besoin de mettre à jour la 3e colonne (objectifs) d'un joueur, j'ai essayé cette commande et cela fonctionne à moins que les jeux et les objectifs aient la même valeur puis il met à jour le premier un

Player:Games:Goals:Assists
Salah:9:9:3
Kane:10:8:4

Y a-t-il une solution? Dois-je utiliser des délimiteurs et $ 3 == ... pour spécifier ce champ? J'ai aussi essayé l'option / 2 pour la deuxième occurrence mais ce n'est pas très pratique dans mon cas.


3 commentaires

Votre devis est assez cassé. Les variables doivent être entre guillemets et le script sed pourrait aussi bien l'être. Le code avec des erreurs de citation semble généralement fonctionner correctement tant que vous travaillez avec des jetons simples, puis exploser de manière intéressante lorsque vous commencez à travailler sur des données réelles. Pour en savoir plus, consultez stackoverflow. com / questions / 10067266 /…


Jetez peut-être un œil à sqlite pour ce type de chose - c'est une base de données sans configuration qui ne nécessite aucun serveur en cours d'exécution - juste un seul exécutable binaire.


Devez-vous utiliser sed ?


5 Réponses :


1
votes

Cela fonctionnera.

Avec la première partie de sed, j'essaie de faire correspondre une ligne complète qui calcule le lecteur, et je garde tous les champs que je veux conserver en utilisant \ (.

La deuxième partie, je reconstruis la ligne avec quelques constantes et la valeur de \ 1 et la valeur de \2^

player="Salah"
NewGoals="10"
sed "s/^$player:\([^:]*\):[^:]*:\([^:]*\)\$/$player:\1:$NewGoals:\2/"


1 commentaires

Vous vous retrouvez toujours avec une condition de concurrence entre la lecture du fichier pour obtenir les anciens objectifs et sa mise à jour avec une nouvelle valeur calculée. Le script Awk dans l'autre réponse résout ce problème de manière plus élégante.



4
votes

Vous pouvez simplement le faire avec awk seul et non avec sed . Notez également que awk a une syntaxe interne pour importer des variables depuis le shell. Ainsi, votre code devient simplement

awk -F: -v pl="$player" -v goals="$NewGoals" 
   'BEGIN { OFS = FS } $1 == pl { $3= goals }1' TopScorer.txt > tmpfile && mv tmpfile TopScorer.txt

Le -F: définit le de-limiteur d'entrée comme : et la partie impliquant - v importe vos variables shell dans le contexte de awk . Le BEGIN {OFS = FS} définit le séparateur de champ de sortie sur le même que l'entrée. Ensuite, nous faisons la correspondance en utilisant les variables importées et mettons à jour $ 3 à la valeur requise.

Pour effectuer les modifications sur place, utilisez un fichier temporaire

awk -F: -v pl="$player" -v goals="$NewGoals" 
    'BEGIN { OFS = FS } $1 == pl { $3= goals }1' TopScorer.txt


6 commentaires

J'ai quelque chose de similaire awk -F ':' 'BEGIN {OFS = FS} / ^' $ player '/ {$ 3 =' $ NewGoals '} 1' TopScorer.txt avant la redirection et le déplacement.


@ DavidC.Rankin qui introduit des vulnérabilités de sécurité et laisse les variables du shell ouvertes pour le fractionnement de mots, le globbing, etc. puisque $ player (et $ NewGoals ) n'est pas entre guillemets dans le shell ( devrait être / ^ '"$ player"' / etc.) et crée également des erreurs cryptiques basées sur le contenu des variables shell puisque vous intégrez leur extension au texte réel du script awk avant awk l'interprète et ce n'est pas réparable. Ne fais pas ça. Utilisez -v ou similaire pour définir les variables awk à partir des valeurs des variables shell, puis utilisez ces variables awk dans le script comme le fait Inian.


@EdMorton merci. C'est le détail plus fin qui m'a laissé me demander comment gérer au mieux l'expansion des variables dans la commande awk . Je vois le problème de la division des mots, mais est-ce que la préoccupation concernant le contenu des variables du shell est le contenu inconnu potentiel? Parce que, il semble que si vous connaissez le contenu qui sera développé, l'utilisation de -v ou du shell devrait être la même? Mais si le -v est de protéger contre le contenu faux dans les extensions, alors cela a du sens comme approche générale.


@ DavidC.Rankin oui, la préoccupation concerne le contenu inconnu potentiel ou le contenu connu mais qui produit un comportement inattendu. C'est le même argument que la raison pour laquelle vous devriez toujours citer des variables shell à moins que vous n'ayez un objectif spécifique à l'esprit qui vous oblige à ne pas le faire et à comprendre pleinement toutes les implications et conséquences.


Essayez foo = $ 'bonjour \ nworld'; awk -v foo = "$ foo" 'BEGIN {print foo}' et vous obtiendrez la sortie attendue. Maintenant, essayez de changer la commande awk en awk 'BEGIN {print "' $ foo '"}' ou toute autre chose qui laisse la variable shell se développer pour faire partie du script awk et le voir échouer de différentes manières . Il y a tellement de façons dont il peut échouer (souvent de manière cryptée et / ou silencieuse) étant donné tellement de valeurs de la variable shell.


C'est un très bon exemple du problème. L'apprentissage a eu lieu. Je vous remercie.



1
votes

Pourriez-vous s'il vous plaît essayer de suivre une fois. L'avantage de cette approche est que je ne suis pas un champ de codage en dur pour les objectifs. Ce programme recherchera le champ d'en-tête partout où l'objectif est présent (par exemple -> 4ème ou 5ème n'importe quel champ), il changera pour cette colonne spécifique uniquement.

1ère solution: strong> Lorsque vous devez apporter des modifications à toutes les occurrences de nom de joueur, utilisez ce qui suit.

NewGoals=10
awk -v newgoals="$NewGoals" 'BEGIN{FS=OFS=":"} FNR==1{for(i=1;i<=NF;i++){if($i=="Goals"){field=i}}} FNR>1{if($1=="Salah" && FNR==2){$field=newgoals}} 1' Input_file

2e solution: Si vous souhaitez modifier la valeur des objectifs d'un joueur spécifique sur une ligne spécifique uniquement, essayez de la suivre.

NewGoals=10
awk -v newgoals="$NewGoals" 'BEGIN{FS=OFS=":"} FNR==1{for(i=1;i<=NF;i++){if($i=="Goals"){field=i}}} FNR>1{if($1=="Salah"){$field=newgoals}} 1' Input_file

Ci-dessus apportera des modifications uniquement pour la ligne 2, vous pouvez la modifier en modifiant FNR == 2 en 2ème condition où FNR fait référence au numéro de ligne dans awk . Si vous souhaitez enregistrer la sortie dans Input_file lui-même, vous pouvez ajouter > temp_file && mv temp_file Input_file aux codes ci-dessus.


0 commentaires

2
votes

Vous pouvez l'essayer avec Perl

$ perl -i.bak -F: -lane '$F[2]=$ENV{NewGoals} if $F[0] eq $ENV{player} ; print join(":",@F) ' TopScorer.txt
$ cat TopScorer.txt
Player:Games:Goals:Assists
Salah:9:10:3
Kane:10:8:4
$

ou les exporter et appeler Perl one-liner entre guillemets simples

$ export NewGoals="10"
$ export player="Salah"
$  perl -F: -lane '$F[2]=$ENV{NewGoals} if $F[0] eq $ENV{player} ; print join(":",@F) ' TopScorer.txt
Player:Games:Goals:Assists
Salah:9:10:3
Kane:10:8:4
$

Notez que Perl a le commutateur -i et vous pouvez faire le remplacement sur place, donc

$ player="Salah"
$ NewGoals="10"
$ perl -F: -lane "\$F[2]=$NewGoals  if ( \$F[0] eq $player ) ; print join(':',@F) " TopScorer.txt
Player:Games:Goals:Assists
Salah:9:10:3
Kane:10:8:4
$


0 commentaires

2
votes

Cela pourrait fonctionner pour vous (GNU sed):

(player=Salah;newGoals=10;sed -i "/^$name/s/[^:]*/$newGoals/3" /tmp/file)

Utilisez un sous-shell pour ne pas polluer le shell actuel (...) . Utilisez sed et pattern matching pour faire correspondre le premier champ de chaque enregistrement à la variable player et remplacez le troisième champ de l'enregistrement correspondant par le contenu de newGoals .

PS Si les variables sont nécessaires dans d'autres processus, le sous-shell n'est pas nécessaire, c'est-à-dire supprimez les ( et )


0 commentaires