Comment ajouter une ligne à la fin du fichier uniquement si elle n'est pas encore là?

10

Je voudrais modifier un fichier sur place en ajoutant une ligne, seulement s'il n'existe pas encore pour rendre mon script à l'épreuve des balles.

Normalement, je ferais quelque chose comme:

cat >> ~/.bashrc <<EOF
export PATH=~/.composer/vendor/bin:\$PATH
EOF

Il est également possible de le faire via ansible ( line+ insertafter=EOF+ regexp), mais c'est une autre histoire.

Dans vi / ex, je pourrais faire quelque chose comme:

ex +'$s@$@\rexport PATH=\~/.composer/vendor/bin:$PATH@' -cwq ~/.bashrc

mais comment puis-je vérifier si la ligne est déjà là (et donc ne rien faire) idéalement sans répéter la même ligne?

Ou peut-être existe-t-il un moyen plus simple de le faire dans l'éditeur Ex?

Kenorb
la source
Vous ne pouvez pas l'externaliser? grep -Fq 'export PATH=~/.composer/vendor/bin:$PATH' ~/.bashrc || ex ...(ou cat, d'ailleurs)?
muru
Je crois que cela pourrait être quelque chose de similaire à cette approche , mais en face (lorsque la chaîne n'est pas là, faites quelque chose).
kenorb
ex ~/.bashrc -c "if search('export PATH=\~\/.composer\/vendor\/bin:\$PATH')>0 | norm quit | endif | norm Aexport PATH=~/.composer/vendor/bin:$PATH"
Alex Kroll
1
Notez que exportc'est une commande , donc le reste de la ligne est un mot shell, PAS une affectation. Par conséquent, contrairement à une affectation de variable (qui n'utilise pas export), vous avez besoin de guillemets doubles ou elle se cassera sur les espaces . Voir également Comment ajouter correctement un chemin d'accès à PATH .
Wildcard
1
J'ai trouvé le lien réel que je cherchais hier (bien que les liens ci-dessus soient également bons). Des citations sont-elles nécessaires pour l'affectation des variables locales?
Wildcard

Réponses:

6

Si vous voulez une protection contre les balles , vous voulez probablement quelque chose d'un peu plus portable que les options compliquées ci-dessus. Je m'en tiendrai aux fonctionnalités POSIX deex et le rendrais stupide-simple: supprimez simplement la ligne si elle est là (quel que soit le nombre de fois où elle est là), ajoutez-la à la fin du fichier, puis enregistrez et quittez:

ex -sc 'g/^export PATH=\~\/\.composer\/vendor\/bin:\$PATH$/d
$a
export PATH=~/.composer/vendor/bin:$PATH
.
x' ~/.bashrc

J'ai ancré l'expression régulière pour plus de robustesse, bien que je pense qu'il est presque impossible de faire correspondre accidentellement cette expression régulière. (Ancré signifie utiliser ^et $donc l'expression régulière ne correspondra que si elle correspond à une ligne entière .)

Notez que la +cmdsyntaxe n'est pas requise par POSIX, ni la caractéristique commune d'autoriser plusieurs -carguments.


Il existe une multitude d'autres façons simples de procéder. Par exemple, ajoutez la ligne à la fin du fichier, puis exécutez les deux dernières lignes du fichier via le filtre UNIX externe uniq:

ex -sc '$a
export PATH=~/.composer/vendor/bin:$PATH
.
$-,$!uniq
x' input

Ceci est très très propre et simple, et est également entièrement compatible POSIX. Il utilise la fonction d'échappement deex pour le filtrage de texte externe et l' utilitaire shell spécifié par POSIXuniq

L' essentiel est, exest conçu pour l'édition de fichiers sur place et c'est de loin le moyen le plus portable pour y parvenir de manière non interactive. Il est même antérieur à l'original vi, en fait - le nom viest pour "éditeur visuel", et l'éditeur non visuel sur lequel il a été construit était ex .)


Pour encore plus de simplicité (et pour le réduire à une seule ligne), utilisez printfpour envoyer les commandes à ex:

printf %s\\n '$a' 'export PATH=~/.composer/vendor/bin:$PATH' . '$-,$!uniq' x | ex input
Caractère générique
la source
2

Une solution possible consiste à créer une fonction pour ce faire:

function! AddLine()

    let l:res = 0
    g/export PATH=\~\/.composer\/vendor\/bin:\\\$PATH/let l:res = l:res+1

    if (l:res == 0)
        normal G
        normal oexport PATH=~/.composer/vendor/bin:\$PATH
    endif

endfunction

Nous créons d'abord une variable locale l:resqui sera utilisée pour compter le nombre d'occurrences de la ligne.

Nous utilisons la globalcommande: chaque fois que la ligne est appariée, elle l:resest incrémentée.

Enfin si l:resest toujours 0 cela signifie que la ligne n'apparaît pas dans le fichier donc on passe à la dernière ligne grâce à G, et on crée une nouvelle ligne avec olaquelle on écrit la ligne à ajouter.

Remarque 1 La méthode que j'ai utilisée pour définir la valeur de l:resest un peu lourde et pourrait être remplacée par celle suggérée par @Alex dans sa réponse par quelque chose comme:

let l:res = search('export PATH=\~\/.composer\/vendor\/bin:\\\$PATH')

Remarque 2 Pour rendre la fonction plus flexible, vous pouvez également utiliser des arguments:

function! AddLine(line)

    let l:line =  escape(a:line, "/~$" )

    let l:res = 0
    exe "g/" . l:line . "/let l:res = l:res+1"

    if (l:res == 0)
        normal G
        exe "normal o" . a:line
    endif

endfunction
  • Notez l'astuce avec escape()pour ajouter un \devant les caractères qui pourrait causer un problème dans la recherche.
  • Notez également que vous devrez toujours échapper \par vous-même lors de l'appel de la fonction (je n'ai pas réussi à m'échapper \automatiquement):

    call AddLine("export PATH=~/.composer/vendor/bin:\\$PATH")
    
statox
la source
2

Essayer:

ex ~/.bashrc -c "if search('export PATH=\~\/.composer\/vendor\/bin:\$PATH')>0 | norm quit | endif | norm Aexport PATH=~/.composer/vendor/bin:$PATH"

Notez que :appendcela ne fonctionnera pas à l'intérieur de if endif-block avec le mode Ex (voir VimDoc ) donc je dois le mettre à l' norm A...extérieur.

Alex Kroll
la source
1

J'utilise normalement:

perl -i -p0e '$_.="export PATH=~/bin:\$PATH\n" unless m!~/bin:!' ~/.bashrc
JJoao
la source
1

Afin de simplifier et d'éviter la présence de plusieurs \, la ligne à ajouter sera export PATH=mybin:PATH.

Stratégie principale: remplacer le "non mybin" texte (tous les fichiers) par text + mybin-path-definition:

 vim  +'s#\v%^((.|\n)(mybin:)@!)*%$#&\rexport PATH=mybin:PATH#e' -cx bashrc

ou avec une indentation plus didactique :

s#                                    substitute
   \v                                  very magical to redude \

   %^                                  file begin
   ((.|\n)(mybin:)@!)*                 multi-line str without "mybin:"
                                         (any char not followed by "mybin")*
   %$                                  end of file
 #                                    by

Si nous avons un match:

  1. &a le texte intégral de bashrcet
  2. & ne contient pas /mybin:/

alors:

 #                                    by ...
   &                                   all file
   export PATH=mybin:PATH              and the new path
 #e                                   don't complain if patt not found

-cx                                   save and leave 

MISE À JOUR : Enfin, nous pouvons faire un script vim:

$ cat bashrcfix

#!/usr/bin/vim -s
:e ~/fakebashrc
:s#\v%^((.|\n)(mybin:)@!)*%$#&\rexport PATH=mybin:PATH#e
:x

chmod 755 et installez-le. Usage:bashrcfix


attention: en \vmode, =signifie facultatif

JJoao
la source
1

La plupart de ces réponses semblent compliquées, que diriez-vous de bonnes vieilles commandes shell:

grep composer/vender ~/.bashrc || cat >> ~/.bashrc <<EOF
export PATH=~/.composer/vendor/bin:\$PATH
EOF

Cela pourrait facilement être une fonction Bash:

addBashrcPath() {
  if ! grep "$1" ~/.bashrc >/dev/null 2>&1; then
    printf 'export PATH=%s:$PATH' "$1" >> ~/.bashrc
  fi
}

addBashrcPath "~/.composer/vendor/bin"

EDIT : Le code de sortie de grep est important et dans ce cas, il doit être inversé ! grep. grep -vne sortira pas comme prévu dans cette situation. Merci @joeytwiddle pour l'astuce.

Sukima
la source
Je ne pense pas que grep -vvous donne le code de sortie que vous souhaitez. Mais ! grepdevrait le faire.
joeytwiddle
Si tel est le cas, vous n'avez même pas besoin du -vjuste !.
Sukima
Exactement. Vous pouvez modifier votre réponse pour apporter cette correction.
joeytwiddle
1
1. Vous devez utiliser -Fpour ne greppas interpréter les caractères comme des expressions régulières, et 2. Utiliser -qau lieu de rediriger vers /dev/null. Voir mon commentaire précédent sur la question .
muru
Bons points. Curieux de savoir comment portable -qet -Foptions? Je pensais que >/dev/nullc'était plus universel entre différentes plates-formes (sun vs hp vs Mac OS X vs Ubuntu vs FreeBSD vs…)
Sukima