Ajouter une ligne à un fichier uniquement s'il n'existe pas déjà

157

Je dois ajouter la ligne suivante à la fin d'un fichier de configuration:

include "/configs/projectname.conf"

vers un fichier appelé lighttpd.conf

J'envisage d'utiliser sedpour faire cela, mais je ne peux pas comprendre comment.

Comment pourrais-je l'insérer uniquement si la ligne n'existe pas déjà?

Benjamin Dell
la source
Si vous essayez de modifier un fichier ini, l'outil crudinipeut être une bonne option (mais pas encore pour lighthttpd)
rubo77

Réponses:

292

Restez simple :)

grep + echo devrait suffire:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar

Edit: incorporé des suggestions @cerin et @ thijs-wouters .

drAlberT
la source
2
J'ajouterais le commutateur -q à grep pour supprimer la sortie: grep -vq ...
Dave Kirby
1
Pour info, utiliser -v et && ne semble pas faire l'affaire, alors que || sans -v fonctionne.
bPizzi
3
Cela ne fonctionne que pour les lignes très simples. Sinon, grep interprète la plupart des caractères non alpha de votre ligne comme des motifs, ce qui l'empêche de détecter la ligne dans le fichier.
Cerin
2
Belle solution. Cela fonctionne également pour déclencher des expressions plus compliquées, bien sûr. Le mien utilise le echopour déclencher un catheredoc multiligne dans un fichier de configuration.
Eric L.
2
Ajouter -spour ignorer les erreurs lorsque le fichier n'existe pas, en créant un nouveau fichier avec uniquement cette ligne.
Frank
78

Ce serait une solution propre, lisible et réutilisable utilisant grepet echopour ajouter une ligne à un fichier uniquement si elle n'existe pas déjà:

LINE='include "/configs/projectname.conf"'
FILE='lighttpd.conf'
grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"

Si vous avez besoin de faire correspondre toute la ligne, utilisez grep -xqF

Ajouter -spour ignorer les erreurs lorsque le fichier n'existe pas, en créant un nouveau fichier avec uniquement cette ligne.

rubo77
la source
5
S'il s'agit d'un fichier pour lequel vous avez besoin des autorisations root:sudo grep -qF "$LINE" "$FILE" || echo "$LINE" | sudo tee -a "$FILE"
nebffa
Cela ne fonctionne pas lorsque la ligne commence par un -.
hyperknot
@zsero: bon point! J'ai ajouté --dans la commande grep, donc il n'interprétera plus un $ LINE commençant par un tiret comme des options.
rubo77
13

Voici une sedversion:

sed -e '\|include "/configs/projectname.conf"|h; ${x;s/incl//;{g;t};a\' -e 'include "/configs/projectname.conf"' -e '}' file

Si votre chaîne est dans une variable:

string='include "/configs/projectname.conf"'
sed -e "\|$string|h; \${x;s|$string||;{g;t};a\\" -e "$string" -e "}" file
Suspendu jusqu'à nouvel ordre.
la source
Pour une commande qui remplace une chaîne partielle par l'entrée de fichier de configuration complète / mise à jour, ou ajoute la ligne si nécessaire:sed -i -e '\|session.*pam_mkhomedir.so|h; ${x;s/mkhomedir//;{g;tF};a\' -e 'session\trequired\tpam_mkhomedir.so umask=0022' -e '};:F;s/.*mkhomedir.*/session\trequired\tpam_mkhomedir.so umask=0022/g;' /etc/pam.d/common-session
bgStack15
Bien, cela m'a beaucoup aidé +1. pourriez-vous s'il vous plaît, expliquer votre expression sed?
Rahul
J'aimerais voir une explication annotée de cette solution. J'utilise seddepuis des années mais surtout juste la scommande. Les échanges d'espace holdet d' patternespace me dépassent.
nortally
11

Si vous écrivez dans un fichier protégé, les réponses de @drAlberT et @ rubo77 peuvent ne pas fonctionner pour vous car on ne peut pas sudo >>. Une solution tout aussi simple serait alors d'utiliser tee --append(ou, sur MacOS, tee -a):

LINE='include "/configs/projectname.conf"'
FILE=lighttpd.conf
grep -qF "$LINE" "$FILE"  || echo "$LINE" | sudo tee --append "$FILE"
hamx0r
la source
6

Si, un jour, quelqu'un d' autre a faire face à ce code comme « code existant », cette personne sera reconnaissant si vous écrivez un code moins exotérique, comme

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi
Marcelo Ventura
la source
1
Désolé, si vous ne connaissez pas shell, alors allez et administrez Windows. Les solutions utilisant && et || sont une syntaxe shell normale et doivent être comprises par tout administrateur compétent. Il n'y a rien du tout d'ésotérique.
Graham Nicholls
3
Merci pour ton commentaire Graham! Oui, c'est une syntaxe shell normale. Et oui, tout administrateur compétent doit le comprendre. Cependant, il fonctionne en tant que tel basé sur un effet secondaire de l'algorithme d'évaluation d'expression dans le shell. Vous pouvez donc l'appeler autant que vous voulez, sauf simple - ce qui était mon point d'origine.
Marcelo Ventura
6

une autre solution sed est de toujours l'ajouter sur la dernière ligne et de supprimer une préexistante.

sed -e '$a\' -e '<your-entry>' -e "/<your-entry-properly-escaped>/d"

"correctement échappé" signifie mettre une expression régulière qui correspond à votre entrée, c'est-à-dire échapper à tous les contrôles regex de votre entrée actuelle, c'est-à-dire mettre une barre oblique inverse devant ^ $ / *? + ().

cela pourrait échouer sur la dernière ligne de votre fichier ou s'il n'y a pas de nouvelle ligne pendante, je ne suis pas sûr, mais cela pourrait être résolu par un branchement astucieux ...

Robin479
la source
1
Joli one-liner, court et facile à lire. Fonctionne très bien pour la mise à jour etc / hosts:sed -i.bak -e '$a\' -e "$NEW_IP\t\t$HOST.domain\t$HOST" -e "/.*$HOST/d" /etc/hosts
Noam Manos
Une version plus petite:sed -i -e '/<entry>/d; $a <entry>'
Jérôme Pouiller
@Jezz Je pense que cela échouera, si l'entrée est déjà à la fin du fichier, car la dcommande redémarrera le programme sed et sautera ainsi le append, si la suppression se trouvait sur la dernière ligne.
Robin479
@ Robin479 Damned, append doivent être exécutés avant delete: sed -i -e '$a<entry>' -e '/<entry>/d'. C'est presque la même que votre réponse initiale.
Jérôme Pouiller
3

utiliser awk

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file file
ghostdog74
la source
S'agit-il d'un «fichier fichier» ou simplement d'un «fichier»?
webdevguy
C'est file fileparce qu'il effectue deux passes sur le même fichier. NR==FNRest vrai sur le premier passage, mais pas sur le second. C'est un idiome Awk courant.
tripleee
1

Les réponses utilisant grep sont fausses. Vous devez ajouter une option -x pour correspondre à la ligne entière, sinon les lignes comme celles- #text to addci correspondent toujours lorsque vous cherchez à ajouter exactement text to add.

La solution correcte est donc quelque chose comme:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar
Thijs Wouters
la source
1

Utilisation de sed: il s'insérera à la fin de la ligne. Vous pouvez également transmettre des variables comme d'habitude bien sûr.

grep -qxF "port=9033" $light.conf
if [ $? -ne 0 ]; then
  sed -i "$ a port=9033" $light.conf
else
    echo "port=9033 already added"
fi

Utilisation de oneliner sed

grep -qxF "port=9033" $lightconf || sed -i "$ a port=9033" $lightconf

L'utilisation de l'écho peut ne pas fonctionner sous root, mais fonctionnera comme ceci. Mais cela ne vous permettra pas d'automatiser les choses si vous cherchez à le faire, car il pourrait demander un mot de passe.

J'ai eu un problème lorsque j'essayais de modifier à partir de la racine pour un utilisateur particulier. Le simple fait d'ajouter l' $usernameavant était une solution pour moi.

grep -qxF "port=9033" light.conf
if [ $? -ne 0 ]; then
  sudo -u $user_name echo "port=9033" >> light.conf
else
    echo "already there"    
fi
Rakib Fiha
la source
Bienvenue dans stackoverflow! Veuillez lire attentivement la question avant de publier une réponse - le PO a demandé comment insérer en utilisant sed
Markoorn
-1

J'avais besoin de modifier un fichier avec des autorisations d'écriture restreintes si nécessaire sudo. en travaillant à partir de la réponse de ghostdog74 et en utilisant un fichier temporaire:

awk 'FNR==NR && /configs.*projectname\.conf/{f=1;next}f==0;END{ if(!f) { print "your line"}} ' file > /tmp/file
sudo mv /tmp/file file
webdevguy
la source
Cela ne semble pas correct, il perdra les autres lignes du fichier lorsque la ligne de configuration sera manquante.
tripleee