Grep pour trouver la bonne ligne, sed pour changer le contenu, puis la remettre dans le fichier d'origine?

9

J'essaie de changer un seul mot sur une ligne spécifique dans un fichier, mais j'ai du mal à me connecter tous ensemble.

Fondamentalement, sur une ligne de mon fichier, il y a un mot-clé 'firmware_revision', et sur cette ligne (et seulement cette ligne) je veux remplacer le mot 'test' par le mot 'production'.

Je peux donc faire ceci:

grep 'firmware_revision' myfile.py | sed 's/test/production'

Cela sélectionnera la ligne que je veux et effectuera la substitution, mais je ne peux pas comprendre comment obtenir cette nouvelle ligne dans le fichier d'origine pour remplacer l'ancienne ligne. Je ne peux évidemment pas simplement le rediriger vers le fichier, alors que dois-je faire?

Même si j'utilise grepdes fichiers temporaires, en utilisant pour obtenir uniquement la ligne dont j'ai besoin, je perds toutes les autres données du fichier, donc je ne peux plus simplement tout rediriger vers un fichier temporaire, puis remplacer l'original par le temp.

Modifier - Quelqu'un a demandé plus d'informations

Disons que j'ai un fichier plein de lignes comme ça

[
  ('key_name1', str, 'value1', 'Description'),
  ('key_name2', str, 'value2', 'Description'),
  ('key_name3', str, 'value3', 'Description'),
  ('firmware_revision', str, 'my-firmware-name-test', 'Firmware revision name')
]

maintenant, je veux écrire un script (idéalement une ligne) qui trouvera la ligne qui contient «firmware_revision» et change toutes les instances du mot «test» sur cette ligne en «production». Le mot «test» pourrait se trouver à d'autres endroits de ce fichier et je ne veux pas qu'ils soient modifiés. Donc, pour être clair, je veux changer la ligne ci-dessus en

('firmware_revision', str, 'my-firmware-name-production', 'Firmware revision name')

Comment puis-je faire cela?

John Allard
la source
5
sedest très puissant, il peut effectuer les deux fonctions ( grepet la substitution) mais nous aurons besoin de plus d'informations sur l'apparence de la ligne pour vous aider.
grochmal
J'ajouterai plus d'informations au message d'origine
John Allard
7
Essayez:sed -i.bak '/firmware_revision/ s/test/production/' myfile.py
John1024
1
Ah ça a parfaitement fonctionné! Pouvez-vous expliquer la syntaxe?
John Allard
@JohnAllard Très bien. J'ai ajouté une réponse avec explication.
John1024

Réponses:

22

Essayer:

sed -i.bak '/firmware_revision/ s/test/production/' myfile.py

Ici, /firmware_revision/agit comme une condition. C'est vrai pour les lignes qui correspondent à l'expression régulière firmware_revisionet faux pour les autres lignes. Si la condition est vraie, alors la commande qui suit est exécutée. Dans ce cas, cette commande est une commande de remplacement qui remplace la première occurrence de testwith production.

En d'autres termes, la commande s/test/production/n'est exécutée que sur les lignes qui correspondent à l'expression régulière firmware_revision. Toutes les autres lignes passent inchangées.

Par défaut, sed envoie sa sortie en sortie standard. Vous vouliez cependant modifier le fichier en place. Nous avons donc ajouté l' -ioption. En particulier, -i.bakentraîne la modification du fichier sur place avec une copie de sauvegarde enregistrée avec une .bakextension.

Si vous avez décidé que la commande fonctionne pour vous et que vous souhaitez vivre dangereusement et ne pas créer de sauvegarde, alors, avec GNU sed (Linux), utilisez:

sed -i '/firmware_revision/ s/test/production/' myfile.py

En revanche, sur BSD (OSX), l' -ioption doit avoir un argument. Si vous ne souhaitez pas conserver de sauvegarde, fournissez-lui un argument vide. Ainsi, utilisez:

sed -i '' '/firmware_revision/ s/test/production/' myfile.py

Éditer

Dans l'édition de la question, l'OP demande que chaque occurrence de testsur la ligne soit remplacée par production. Dans ce cas, nous ajoutons l' goption à la commande de remplacement pour un remplacement global (pour cette ligne):

sed -i.bak '/firmware_revision/ s/test/production/g' myfile.py
John1024
la source
5
Pour être complet, vous pouvez également expliquer la -i.bakpartie.
Stephen Harris
5
@StephenHarris Bon point. Explication de l' -iajout.
John1024
Je pense que vous pourriez probablement atteindre les étoiles, juste debout sur le livre expliquant toutes les choses qui sedpeuvent faire ...
KlaymenDK
@ John1024 Pour les non BSD, pourriez-vous ajouter les raisons du premier ''après -i?
Hastur
3
OP voulait changer toutes les instances du mot «test» sur cette ligne en «production» . Il est important d'ajouter / g à la commande sed dans ce cas: sed -i '/firmware_revision/ s/test/production/g' myfile.pysinon, seule la première instance est modifiée.
knub
4

Sur les machines plus anciennes avec la vieille école sedqui ne prend pas en charge l'option -i:

TF=$( mktemp -t "${0##*/}"_$$_XXXXXXXX ) && \
trap 'rm -f "$TF"' EXIT HUP INT QUIT TERM && \
sed '/firmware_revision/ s/test/production/' myfile.py >"$TF" && \
mv -f "$TF" myfile.py
Satō Katsura
la source
1
Sur ces machines plus anciennes, vous pouvez simplement utiliser edet le faire en une seule ligne:printf %s\\n g/firmware_revision/s/test/production/g w q | ed -s myfile.py
don_crissti
1
@don_crissti Très vrai, mais ed(1)n'offre aucun prétexte pour montrer l'utilisation de mktemp(1).
Satō Katsura
Si vous utilisez un fichier temporaire, vous pouvez aussi bien conserver l'inode et les perms du fichier d'origine, de la même manière ed. Au lieu de mv -f "$TF" myfile.pl, utilisez cat "$TF" > myfile.pl && rm -f "$TF". BTW, il est recommandé d'utiliser des noms de variables en minuscules ( $tfau lieu de $TF), ils sont garantis de ne pas entrer en conflit avec les vars intégrés de bash (probablement d'autres shells bourne aussi).
cas
@cas Re:: cat "$TF" > myfile.plessayez ceci: touch a b; chmod 0444 a; cat b >a(BTW, sed -ine fonctionnera pas dans ce cas non plus). Mieux vaut laisser l'utilisateur s'en occuper. Re:: rm -f "$TF"pas nécessaire, voyez trap ... EXIT. Re: $tfau lieu de $TF: peut-être; c'est une question de style.
Satō Katsura
C'est le comportement normal et attendu des autorisations de fichiers Unix. Mon point était que créer un nouveau fichier et le déplacer sur l'original 1. entraînera un nouvel inode pour ce nom de fichier (brisant tous les liens physiques). 2. éventuellement changer les perms, si le courant umask diffère des perms d'origine. En ce qui concerne les vars en majuscules, ce n'est pas seulement une question de style - beaucoup de gens qui ont fait l'erreur d'utiliser des majuscules PATH ou RANDOM ou SHELL ou quoi que ce soit pour leurs propres variables ont trouvé la voie difficile. (et oui, j'utilise souvent des vars majuscules moi-même. Je sais que c'est faux, j'essaie de rompre avec l'habitude).
cas