sed: comment remplacer la ligne si trouvé ou ajouter à la fin du fichier si non trouvé?

34

Avec un seul fichier d'entrée contenant uniquement des commentaires (à partir de #) et des lignes de valeur VARIABLE =, est-il possible de remplacer une valeur pour une seule variable si elle est trouvée et, sinon, d'ajouter la paire à la fin du fichier si elle n'est pas trouvée?

Ma méthode actuelle fonctionne en la supprimant lors d'un premier passage, puis en l'ajoutant à la fin du fichier lors d'un second passage, mais cette méthode perturbe l'ordre des lignes (et comporte également deux commandes différentes):

sed -r "/^FOOBAR=.*$/d"      -i samefile &&
sed -r "$ a\FOOBAR=newvalue" -i samefile

Est-il possible de le faire, c.-à-d. garder l'ordre de la ligne, dans une seule ligne sed? Si un autre utilitaire (awk, ...) le fait, je le prendrais en charge.

BlakBat
la source

Réponses:

29

C'est en fait assez simple avec sed: si une ligne correspond, copiez-la simplement dans l' hancien espace puis sremplacez la valeur.
Sur la dernière $ligne, xmodifiez les espaces de maintien et de motif, puis vérifiez si ce dernier est vide. Si ce n'est pas vide, cela signifie que la substitution a déjà été faite, donc rien à faire. Si elle est vide, cela signifie qu'aucune correspondance n'a été trouvée. Remplacez l'espace modèle par la variable = valeur souhaitée , puis ajoutez-le à la ligne en cours dans le tampon de maintien. Enfin, e xchange encore:

sed '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile

Ce qui précède est la gnu sedsyntaxe. Portable:

sed '/^FOOBAR=/{
h
s/=.*/=newvalue/
}
${
x
/^$/{
s//FOOBAR=newvalue/
H
}
x
}' infile
don_crissti
la source
1
À quoi ressemblerait-il si newvalueest stocké dans une variable?
user1810087
sed "/^${varName}=/{h;s/=.*/=${varValue}/};\${x;/^$/{s//${varName}=${varValue}/;H};x}" ${VARFILE}avec des variables, des guillemets doubles pour permettre la substitution de variable et $éviter une substitution qui n’est pas une substitution à ce stade, en plus si votre valeur est stockée dans $$varName:sed "/^${varName}=/{h;s/=.*/=${!varName}/};\${x;/^$/{s//${varName}=${!varName}/;H};x}" ${VARFILE}
DarkMukke
1
Cette solution n'est pas "simple" lol. Mais ça marche. Les pages de manuel sed ne sont pas les choses les plus faciles à parcourir, il serait vraiment utile de pouvoir détailler l’effet de chaque section de l’expression :)
user2066480 Le
18

Cela peut probablement être raccourci. Ce n'est pas une seule commande sed et il utilise également grep, mais cela semble être fondamentalement ce que vous voulez. Il s’agit d’une seule ligne et édite le fichier sur place (aucun fichier temporaire).

grep -q "^FOOBAR=" file && sed "s/^FOOBAR=.*/FOOBAR=newvalue/" -i file || 
    sed "$ a\FOOBAR=newvalue" -i file
snapshoe
la source
13

Voici une sedapproche plus simple , car je ne trouve pas sed hold spacefacile de travailler avec. Si vous êtes à l'aise avec hold space, l'approche don_crissti offre une possibilité supplémentaire de conserver quelque chose de la ligne existante, mais cela est généralement très rare.

Dans cette approche, il vous suffit d'imprimer toutes les lignes sauf la ligne à supprimer, puis d'ajouter le remplacement à la fin.

sed -n -e '/^FOOBAR=/!p' -e '$aFOOBAR=newvalue' infile
haridsv
la source
1
Cette version est très utile si vous envisagez de mettre à jour un fichier pouvant contenir une valeur existante mais obsolète.
guillem
3

Sur la base des autres réponses, si vous voulez remplacer la valeur d'une variable si cette variable est présente dans le fichier et l'ajouter à la fin du fichier si ce n'est pas le cas (ce qui n'est pas le cas de vos sedcommandes postées ), vous pourrait essayer ceci:

perl -ne '$c=1 if s/^FOOBAR=.*$/FOOBAR=newvalue/;  
             print; 
             END{print "FOBAR=newvalue" unless $c==1}' file > tmpfile && 
mv tmpfile file
terdon
la source
1

C'est un peu plus simple dans awk, bien que l'édition "sur place" ne soit pas automatique:

awk -v varname="FOOBAR" -v newval="newvalue" '
    BEGIN {FS = OFS = "="}
    $1 == varname {$2 = newval; found = 1}
    {print}
    END {if (! found) {print varname, newval}}
' file > tempfile &&
mv tempfile file
Glenn Jackman
la source
1

Utilisez simplement grepet echopour créer un enregistrement vide:

grep -q '^FOOBAR=' somefile || echo 'FOOBAR=VALUE' >> somefile
sed -i 's/FOOBAR=.*$/FOOBAR=VALUE/' somefile 

Chaque ligne s'échappe avec le code d'erreur zéro.

MUY Belgique
la source
Vous perdez la commande de ligne en ajoutant des commandes supplémentaires et grepetecho
BlakBat
En effet si vous insérez les nouveaux éléments à la fin du fichier, mais continuez à commander si vous remplacez une ancienne valeur.
MUY Belgique
0

Fonctionne réellement avec ce qui suit: sed -i '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile Dans la réponse choisie -i est manquant.

Yogesh Kamat
la source
3
C'est vraiment un commentaire et non une réponse à la question initiale. Pour critiquer ou demander des éclaircissements à un auteur, laissez un commentaire en dessous de son message - vous pouvez toujours commenter vos propres messages, et une fois que vous avez suffisamment de réputation, vous pourrez commenter n'importe quel message . Veuillez lire pourquoi j'ai besoin de 50 réputation pour commenter? Que puis-je faire à la place?
DavidPostill
-1

faire cela avec perl et in-place ça marche très bien pour moi:

grep ^FOOBAR= my.file && perl -i -ple "s/^FOOBAR=.+/FOOBAR=newvalue/g" my.file || echo FOOBAR=newvalue >> my.file
antweiss
la source
2
Cette réponse est mauvaise à bien des égards! grepet echoet perlau lieu de tout faire perl? En outre, écrire dans un fichier ( >> my.file) au fur et à mesure que vous le lisez ( grep my.file)?
vladr