web-dev-qa-db-fra.com

Comment ajouter une ligne dans sed si aucune correspondance n'est trouvée

J'utilise la commande sed suivante pour remplacer certains paramètres dans un fichier de configuration:

sed -i 's/^option.*/option=value/g' /etc/fdm_monitor.conf

Maintenant j'ai un problème. Si la ligne n'existe pas, je veux l'ajouter au bas du fichier.

J'appelle cela avec un popen sur un programme C. J'ai essayé d'utiliser awk.

30
Evilmachine

Essaye ça:

grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file
57
kev

En utilisant sed, la syntaxe simple:

sed \
    -e '/^\(option=\).*/{s//\1value/;:a;n;ba;q}' \
    -e '$aoption=value' filename

Cela remplacerait le paramètre s'il existe, sinon il serait ajouté au bas du fichier.

Utilisez l'option -i si vous souhaitez modifier le fichier sur place.


Si vous voulez accepter et conserver les espaces, et en plus supprimer le commentaire, si la ligne existe déjà, mais est commentée, écrivez:

sed -i \
    -e '/^#\?\(\s*option\s*=\s*\).*/{s//\1value/;:a;n;ba;q}' \
    -e '$aoption=value' filename

Veuillez noter que ni l'option ni la valeur ne doivent contenir une barre oblique /, sinon vous devrez l'échapper à \/.


Pour utiliser les variables bash $option et $value, vous pouvez écrire:

sed -i \
    -e '/^#\?\(\s*'${option//\//\\/}'\s*=\s*\).*/{s//\1'${value//\//\\/}'/;:a;n;ba;q}' \
    -e '$a'${option//\//\\/}'='${value//\//\\/} filename

L'expression bash ${option//\//\\/} cite des slash, elle remplace tout / par \/.

Note: i Je viens de me faire prendre au piège dans un problème. Dans bash, vous pouvez citer "${option//\//\\/}", mais dans le sh de busybox, cela ne fonctionne pas, vous devez donc éviter les guillemets, au moins dans les environnements autres que shell.


Tous combinés dans une fonction bash:

# call option with parameters: $1=name $2=value $3=file
function option() {
    name=${1//\//\\/}
    value=${2//\//\\/}
    sed -i \
        -e '/^#\?\(\s*'"${name}"'\s*=\s*\).*/{s//\1'"${value}"'/;:a;n;ba;q}' \
        -e '$a'"${name}"'='"${value}" $3
}

Explication:

  • /^\(option=\).*/: Fait correspondre les lignes qui commencent par option= et (.*) ignorent tout ce qui se trouve après le =. Le \(\) inclut la partie que nous allons réutiliser comme \1 plus tard.
  • /^#\?(\s*'"${option//////Buch"'\s*=\s*).*/: Ignore le code mis en commentaire avec # au début de la ligne. \? signifie «optionnel». Le commentaire sera supprimé car il se trouve en dehors de la partie copiée dans \(\). \s* signifie «un nombre quelconque d'espaces blancs» (espace, tabulateur). Les espaces blancs sont copiés, car ils sont dans \(\), vous ne perdez donc pas le formatage.
  • /^\(option=\).*/{…}: Si correspond à une ligne /…/, alors exécutez la commande suivante. La commande à exécuter n'est pas une commande unique, mais un bloc {…}.
  • s//…/: rechercher et remplacer. Puisque le terme recherché est vide //, il s'applique à la dernière correspondance, qui était /^\(option=\).*/.
  • s//\1value/: Replace the last match with everything in (…) , referenced by\1and the textvalue`
  • :a;n;ba;q: Définissez l'étiquette a, puis lisez la ligne suivante n, puis branchez b (ou allez-y) à l'étiquette a, ce qui signifie: lisez toutes les lignes jusqu'au et du fichier. Après la première correspondance, récupérez toutes les lignes suivantes sans autre En traitement. Alors q guit et donc ignorer tout le reste.
  • $aoption=value: à la fin du fichier $, ajoutez a le texte option=value

Plus d'informations sur sed et un aperçu des commandes sont sur mon blog:

8
Marc Wäckerlin

Voici un one-liner sed qui fait le travail en ligne. Notez que il conserve l'emplacement de la variable et son indentation dans le fichier lorsqu'il existe. Ceci est souvent important pour le contexte, comme lorsqu'il y a des commentaires ou lorsque la variable est dans un bloc en retrait. Toute solution basée sur le paradigme "supprimer-ensuite-ajouter" échoue mal à cet égard.

   sed -i '/^[ \t]*option=/{h;s/=.*/=value/};${x;/^$/{s//option=value/;H};x}' test.conf

Avec une paire générique variable/valeur, vous pouvez l'écrire de la manière suivante:

   var=c
   val='12 34' # it handles spaces nicely btw
   sed -i '/^[ \t]*'"$var"'=/{h;s/=.*/='"$val"'/};${x;/^$/{s//c='"$val"'/;H};x}' test.conf

Enfin, si vous souhaitez également conserver des commentaires en ligne, vous pouvez le faire avec un groupe de captures. Par exemple. si test.conf contient les éléments suivants:

a=123
# Here is "c":
  c=999 # with its own comment and indent
b=234
d=567

Puis en cours d'exécution

var='c'
val='"yay"'
sed -i '/^[ \t]*'"$var"'=/{h;s/=[^#]*\(.*\)/='"$val"'\1/;s/'"$val"'#/'"$val"' #/};${x;/^$/{s//'"$var"'='"$val"'/;H};x}' test.conf

Produit que:

a=123
# Here is "c":
  c="yay" # with its own comment and indent
b=234
d=567
4
MoonCactus

En utilisant sed, vous pourriez dire:

sed -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename

Cela remplacerait le paramètre s'il existe, sinon il serait ajouté au bas du fichier.


Utilisez l'option -i si vous souhaitez modifier le fichier sur place:

sed -i -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename
3
devnull

En tant que ligne unique awk:

awk -v s=option=value '/^option=/{$0=s;f=1} {a[++n]=$0} END{if(!f)a[++n]=s;for(i=1;i<=n;i++)print a[i]>ARGV[1]}' file

ARGV [1] est votre entrée file. Il est ouvert et écrit dans la boucle for du bloc END. L'ouverture de file pour une sortie dans le bloc END supprime le besoin d'utilitaires tels que sponge ou l'écriture dans un fichier temporaire, puis mving du fichier temporaire à file.

Les deux assignations au tableau a[] accumulent toutes les lignes de sortie dans a. if(!f)a[++n]=s ajoute le nouveau option=value si la boucle awk principale n'a pas trouvé option dans file

J'ai ajouté quelques espaces (pas beaucoup) pour la lisibilité, mais vous avez vraiment besoin d'un seul espace dans tout le programme awk, l'espace après print. Si file inclut # comments, ils seront préservés.

3
stepse

voici un awk one-liner:

 awk -v s="option=value" '/^option/{f=1;$0=s}7;END{if(!f)print s}' file

ceci ne fait pas de changement sur place sur le fichier, vous pouvez cependant:

awk '...' file > tmpfile && mv tmpfile file
2
Kent

Voici une implémentation awk

/^option *=/ { 
  print "option=value"; # print this instead of the original line
  done=1;               # set a flag, that the line was found
  next                  # all done for this line
}
{print}                 # all other lines -> print them
END {                   # end of file
  if(done != 1)         # haven't found /option=/ -> add it at the end of output
    print "option=value"
}

Exécuter en utilisant

awk -f update.awk < /etc/fdm_monitor.conf > /etc/fdm_monitor.conf.tmp && \
   mv /etc/fdm_monitor.conf.tmp /etc/fdm_monitor.conf

ou 

awk -f update.awk < /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf

EDIT: Comme un one-liner: 

awk '/^option *=/ {print "option=value";d=1;next}{print}END{if(d!=1)print "option=value"}' /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf
2
rzymek
 sed -i 's/^option.*/option=value/g' /etc/fdm_monitor.conf
 grep -q "option=value" /etc/fdm_monitor.conf || echo "option=value" >> /etc/fdm_monitor.conf
2
Jack
sed -i '1 h
1 !H
$ {
   x
   s/^option.*/option=value/g
   t
   s/$/\
option=value/
   }' /etc/fdm_monitor.conf

Charger tout le fichier dans le tampon, à la fin, changer toutes les occurrences et si aucun changement ne se produit, ajouter à la fin

1
NeronLeVelu

J'ai élaboré la solution grep/sed de kev en définissant des variables afin de réduire les duplications.

Définissez les variables dans la première ligne (indice: $_option doit correspondre à tout le long de la ligne jusqu'à la valeur [y compris les séparateurs tels que = ou:]).

_ file = "/ etc/ssmtp/ssmtp.conf" _option = "mailhub =" _value = "mon.domaine.tld"\
 sh -c '\ 
 grep -q "^ $ _ option" "$ _file"\
 && sed -i "s/^ $ _ option. */$ _ option $ _value /" "$ _file"\
 || echo "$ _option $ _value" >> "$ _file"\
 '

Rappelez-vous que le sh -c '...' a simplement pour effet d'élargir la portée des variables sans qu'il soit nécessaire d'utiliser une export. (Voir Définition d'une variable d'environnement avant qu'une commande dans bash ne fonctionne pas pour la deuxième commande dans un tube )

0
Jonas Eberle

Vous pouvez utiliser cette fonction pour rechercher et rechercher des modifications de configuration:

#!/bin/bash

#Find and Replace config values
find_and_replace_config () {
   file=$1
   var=$2
   new_value=$3
   awk -v var="$var" -v new_val="$new_value" 'BEGIN{FS=OFS="="}match($1, "^\\s*" var "\\s*") {$2=" " new_val}1' "$file" > output.tmp && Sudo mv output.tmp $file
}

find_and_replace_config /etc/php5/Apache2/php.ini max_execution_time 60
0
Guray Celik