Ajoutez un répertoire à $ PATH s'il n'y est pas déjà

126

Quelqu'un a-t-il écrit une fonction bash pour ajouter un répertoire à $ PATH uniquement s'il ne s'y trouve pas déjà?

J'ajoute généralement à PATH en utilisant quelque chose comme:

export PATH=/usr/local/mysql/bin:$PATH

Si je construis mon PATH dans .bash_profile, alors il n'est pas lu, sauf si la session dans laquelle je suis est une session de connexion - ce qui n'est pas toujours vrai. Si je construis mon PATH dans .bashrc, il s'exécute avec chaque sous-shell. Donc, si je lance une fenêtre de terminal et que je lance ensuite screen, puis un script shell, je reçois:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....

Je vais essayer de créer une fonction bash appelée add_to_path()qui n’ajoute le répertoire que s’il n’y est pas. Mais, si quelqu'un a déjà écrit (ou trouvé) une telle chose, je ne passerai pas le temps dessus.

Doug Harris
la source
Voir stackoverflow.com/questions/273909/… pour une infrastructure utile.
dmckee
unix.stackexchange.com/questions/4965/…
Ciro Santilli a dévoilé
Si vous définissez le problème comme "ajoutant uniquement si ce n'est déjà fait", vous serez vraiment surpris lorsque le jour viendra où il sera important que l'élément inséré soit au début, mais il ne finira pas là. Une meilleure approche consisterait à insérer l'élément, puis à supprimer les doublons. Ainsi, si la nouvelle entrée était déjà présente, elle sera effectivement déplacée au début.
Don Hatch

Réponses:

125

De mon .bashrc:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

Notez que PATH doit déjà être marqué comme exporté. Une réexportation n'est donc pas nécessaire. Ceci vérifie si le répertoire existe et est un répertoire avant de l'ajouter, ce qui ne vous intéressera peut-être pas.

De plus, cela ajoute le nouveau répertoire à la fin du chemin; pour mettre au début, utilisez à la PATH="$1${PATH:+":$PATH"}"place de la PATH=ligne ci-dessus .

Gordon Davisson
la source
26
Ça me tient à cœur.
Dennis Williamson
4
@Neil: Ça marche, parce que ça se compare au ":$PATH:"lieu de juste"$PATH"
Gordon Davisson
3
@GordonDavisson: Je m'excuse, mon test était faux et vous avez raison.
Neil
2
@GordonDavisson Quel est le point de la substance dans les accolades. Je n'arrive pas à comprendre " ${PATH:+"$PATH:"}1 $"
boatcoder
5
@ Mark0978: C'est ce que j'ai fait pour résoudre le problème signalé par bukzor. ${variable:+value}signifie vérifier si variableest défini et a une valeur non vide, et si c'est le cas, donne le résultat de l'évaluation value. Fondamentalement, si PATH est non vide pour commencer, il le définit sur "$PATH:$1"; si elle est vide, le réglage est juste "$1"(notez l'absence de deux points).
Gordon Davisson
19

En développant la réponse de Gordon Davisson, cela soutient plusieurs arguments

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

Donc vous pouvez faire pathappend path1 path2 path3 ...

Pour prépending,

pathprepend() {
  for ((i=$#; i>0; i--)); 
  do
    ARG=${!i}
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}

Semblable à pathappend, vous pouvez faire

pathprepend path1 path2 path3 ...

Guillaume Perrault-Archambault
la source
3
C'est bien! J'ai fait un petit changement. Pour la fonction 'pathprepend', il est pratique de lire les arguments en sens inverse pour pouvoir dire, par exemple, pathprepend P1 P2 P3et se retrouver avec PATH=P1:P2:P3. Pour obtenir ce comportement, changer for ARG in "$@" doàfor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael
Merci @ishmael, bonne suggestion, j'ai modifié la réponse. Je me rends compte que votre commentaire date de plus de deux ans, mais je n'y suis pas retourné depuis. Je dois trouver un moyen d’arriver dans ma boîte de réception pour recevoir les courriels d’échange de piles!
Guillaume Perrault-Archambault
14

Voici quelque chose de ma réponse à cette question combinée à la structure de la fonction de Doug Harris. Il utilise des expressions régulières Bash:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
Dennis Williamson
la source
Cela a fonctionné pour moi uniquement en utilisant $1au lieu de${1}
Andrei
@Andrei: Oui, les accolades sont inutiles dans ce cas. Je ne sais pas pourquoi je les ai inclus.
Dennis Williamson
10

Mettez ceci dans les commentaires à la réponse sélectionnée, mais les commentaires ne semblent pas supporter la mise en forme PRE, ajoutez donc la réponse ici:

@ gordon-davisson Je ne suis pas un grand fan de citations et de concaténations inutiles. En supposant que vous utilisez une version de bash> = 3, vous pouvez utiliser les expressions rationnelles intégrées de bash et faire:

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

Cela gère correctement les cas où il y a des espaces dans le répertoire ou le PATH. On peut se demander si le moteur de regex intégré de bash est suffisamment lent pour être vraiment moins efficace que la concaténation et l’interpolation des chaînes utilisée par votre version, mais elle me semble plus esthétique encore.

Christopher Smith
la source
1
Les commentaires formatting using the backtickne prennent en charge que les résultats mais vous n’obtenez aucun contrôle de paragraphe décent.
boatcoder
Cela met l'addition à la fin. Il est souvent souhaitable d’ajouter au début afin de remplacer les emplacements existants.
Dennis Williamson
@DennisWilliamson C'est un bon point, bien que je ne le recommande pas comme comportement par défaut. Il n'est pas difficile de comprendre comment changer pour un pré-paiement.
Christopher Smith
@ChristopherSmith-re: unnecessary quotingimplique que vous savez à l'avance que ce $PATHn'est pas nul. "$PATH"rend OK si PATH est null ou non. De même si $1contient des caractères qui pourraient confondre l'analyseur de commandes. Mettre l'expression rationnelle entre guillemets "(^|:)$1(:|$)"empêche cela.
Jesse Chisholm
@ JesseChisholm: En fait, je crois que le point de vue de Christopher est que les règles sont différentes entre [[et ]]. Je préfère citer tout ce qui peut avoir besoin d'être cité, à moins qu'il cause à l' échec, mais je crois qu'il a raison, et que les cours ne sont pas vraiment nécessaires autour $PATH. Par contre, il me semble que vous avez raison $1.
Scott
7
idempotent_path_prepend ()
{
    PATH=${PATH//":$1"/} #delete any instances in the middle or at the end
    PATH=${PATH//"$1:"/} #delete any instances at the beginning
    export PATH="$1:$PATH" #prepend to beginning
}

Lorsque vous avez besoin que $ HOME / bin apparaisse exactement une fois au début de votre $ PATH et nulle part ailleurs, n’acceptez aucun substitut.

Russell
la source
Merci, c'est une belle solution élégante, mais j'ai trouvé que je devais faire PATH=${PATH/"... plutôt que PATH=${PATH//"... pour que ça fonctionne.
Mark Booth
La forme à double barre oblique doit correspondre à n’importe quel nombre de correspondances; la barre oblique unique ne correspond qu'à la première (recherchez "Substitution de modèle" dans la page de manuel bash). Je ne sais pas pourquoi ça n'a pas marché ...
Andybuckley
Cela échoue dans le cas inhabituel qui $1est la seule entrée (pas de deux points). L'entrée devient doublée.
Dennis Williamson
Il supprime également de manière trop agressive, comme indiqué par PeterS6g .
Dennis Williamson
6

Voici une solution alternative qui présente l’avantage supplémentaire de supprimer les ententes redondantes:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

Le seul argument de cette fonction est ajouté au préfixe PATH et la première instance de la même chaîne est supprimée du chemin existant. En d'autres termes, si le répertoire existe déjà dans le chemin, il est promu au lieu d'être ajouté en tant que duplicata.

La fonction ajoute deux points au chemin afin de s’assurer que toutes les entrées ont un signe deux-points au début, puis la nouvelle entrée est ajoutée au chemin existant avec cette entrée supprimée. La dernière partie est réalisée en utilisant la ${var//pattern/sub}notation de bash ; voir le manuel bash pour plus de détails.

Rob Hague
la source
Bonne pensée; mise en œuvre défectueuse. Considérez ce qui se passe si vous avez déjà /home/roberten vous PATHet vous pathadd /home/rob.
Scott
5

Voici le mien (je crois que cela a été écrit il y a des années par Oscar, l'administrateur système de mon ancien laboratoire, lui revient entièrement), cela fait longtemps qu'il est dans ma poche. Il présente l’avantage supplémentaire de vous permettre d’ajouter ou d’ajouter le nouveau répertoire comme vous le souhaitez:

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

Usage:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/
terdon
la source
5

Pour le préfixe, j'aime bien la solution de @ Russell, mais il existe un petit bogue: si vous essayez de préfixer quelque chose comme "/ bin" sur un chemin "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "il remplace" / bin: "3 fois (quand cela ne correspond pas du tout). En combinant un correctif pour cela avec la solution ajoutée de @ gordon-davisson, je comprends ceci:

path_prepend() {
    if [ -d "$1" ]; then
        PATH=${PATH//":$1:"/:} #delete all instances in the middle
        PATH=${PATH/%":$1"/} #delete any instance at the end
        PATH=${PATH/#"$1:"/} #delete any instance at the beginning
        PATH="$1${PATH:+":$PATH"}" #prepend $1 or if $PATH is empty set to $1
    fi
}
PeterS6g
la source
4

Un simple alias comme celui ci-dessous devrait faire l'affaire:

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "

Tout ce qu'il fait est de diviser le chemin sur le caractère: et de comparer chaque composant avec l'argument que vous transmettez. Grep recherche une correspondance de ligne complète et affiche le nombre.

Exemple d'utilisation:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No

Remplacez la commande echo par addToPath ou un alias / fonction similaire.

Nagul
la source
L'utilisation de "grep -x" est probablement plus rapide que la boucle que j'ai mise dans ma fonction bash.
Doug Harris
2

Voici ce que j'ai fouetté:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=$1
    for d in $path_list
    do
        if [ $d == $new_dir ]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}

Maintenant, dans .bashrc j'ai:

add_to_path /usr/local/mysql/bin

Version mise à jour suite au commentaire sur la façon dont mon original ne gérera pas les répertoires avec des espaces (merci à cette question de m'avoir indiqué d'utiliser IFS):

add_to_path ()
{
    new_dir=$1
    local IFS=:
    for d in $PATH
    do
        if [[ "$d" == "$new_dir" ]]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}
Doug Harris
la source
1
Cela peut échouer si un nom de répertoire déjà PATHcontient des espaces blancs, *, ?ou [... ].
Scott
Bon point ... mais je suis un gars de la vieille école sous Linux et je n'utiliserais jamais un chemin contenant des espaces:
Doug Harris
Bon pour vous, pour ne pas créer des choses avec des espaces dans leurs noms. Vous devriez avoir honte d'avoir écrit du code qui ne puisse pas les gérer quand ils existent. Et quel est le rapport avec le fait d'être un «type Linux old-school»? Windoze a peut-être popularisé l'idée (merci, Documents and Settingset Program Files), mais Unix a déjà pris en charge les noms de chemins contenant des espaces, bien avant que Windoze n'existe.
Scott
2

Je suis un peu surpris que personne n'en ait encore parlé, mais vous pouvez utiliser readlink -fpour convertir des chemins relatifs en chemins absolus et les ajouter au PATH en tant que tels.

Par exemple, pour améliorer la réponse de Guillaume Perrault-Archambault,

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

devient

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
        then
            if ARGA=$(readlink -f "$ARG")               #notice me
            then
                if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
                then
                    PATH="${PATH:+"$PATH:"}$ARGA"
                fi
            else
                PATH="${PATH:+"$PATH:"}$ARG"
            fi
        fi
    done
}

1. L'essentiel - à quoi sert-il?

La readlink -fcommande convertira (entre autres choses) un chemin relatif en un chemin absolu. Cela vous permet de faire quelque chose comme

$ cd / path / to / my / bin / dir
$ pathappend .
$ echo "$ PATH"
<your_old_path> : / path / to / my / bin / dir

2. Pourquoi testons-nous deux fois sur PATH?

Eh bien, considérons l'exemple ci-dessus. Si l'utilisateur dit à partir du répertoire une deuxième fois, sera . Bien sûr, ne sera pas présent dans . Mais alors sera mis à (l'équivalent absolu de chemin de ), qui est déjà dans . Nous devons donc éviter d’ajouter à une deuxième fois.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Peut-être plus important encore, l'objectif principal de readlink, comme son nom l'indique, est d'examiner un lien symbolique et de lire le chemin qu'il contient (c.-à-d. Qu'il pointe vers). Par exemple:

$ ls -ld /usr/lib/perl/5.14
-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/perl/5.14 -> 5.14.2
$ readlink /usr/lib/perl/5.14
5.14.2
$ readlink -f /usr/lib/perl/5.14
/usr/lib/perl/5.14.2

Maintenant, si vous dites pathappend /usr/lib/perl/5.14, et vous avez déjà /usr/lib/perl/5.14dans votre CHEMIN, eh bien, c'est bien; nous pouvons simplement le laisser tel quel. Mais, si ce /usr/lib/perl/5.14n'est pas déjà dans votre PATH, nous appelons readlinket obtenons ARGA= /usr/lib/perl/5.14.2, puis nous ajoutons cela à PATH. Mais attendez une minute - si vous avez déjà dit pathappend /usr/lib/perl/5.14, alors vous avez déjà /usr/lib/perl/5.14.2dans votre PATH, et, encore une fois, nous devons vérifier cela pour éviter de l'ajouter PATHune seconde fois.

3. Quel est le problème if ARGA=$(readlink -f "$ARG")?

En cas de doute, cette ligne teste si l'opération readlinkréussit. Ceci est juste une bonne pratique de programmation défensive. Si nous allons utiliser le résultat de la commande  m dans le cadre de la commande  n (où m  <  n ), il est prudent de vérifier si la commande  m a échoué et de le gérer d'une manière ou d'une autre. Je ne pense pas que cela risque d’ readlinkéchouer - mais, comme indiqué dans Comment récupérer le chemin absolu d’un fichier arbitraire sous OS X et ailleurs, il readlinks’agit d’une invention de GNU. Comme il n'est pas spécifié dans POSIX, sa disponibilité dans Mac OS, Solaris et d'autres Unix non Linux est discutable. (En fait, je viens de lire un commentaire qui dit “readlink -fne semble pas fonctionner sous Mac OS X 10.11.6, mais realpathfonctionne " clé en main". Ainsi, si vous utilisez un système qui ne fonctionne pas readlinkou readlink -fqui ne fonctionne pas, vous pourrez peut-être le modifier. script à utiliser realpath.) En installant un filet de sécurité, nous rendons notre code un peu plus portable.

Bien sûr, si vous êtes sur un système qui n'a pas readlink(ou  realpath), vous ne voudrez pas le faire .pathappend .

Le deuxième -dtest ( [ -d "$ARGA" ]) est probablement probablement inutile. Je ne peux pas penser à un scénario où $ARGest un répertoire et readlinkréussit, mais  $ARGAn'est pas un répertoire. Je viens de copier-coller la première ifdéclaration pour créer la troisième et j'ai laissé le  -dtest là-bas par paresse.

4. Autres commentaires?

Ouais. Comme beaucoup d'autres réponses ici, celle-ci vérifie si chaque argument est un répertoire, le traite si c'est le cas et l'ignore s'il ne l'est pas. Cela peut être (ou non) adéquat si vous utilisez pathappend uniquement des .fichiers “ ” (comme .bash_profileet .bashrc) et d’autres scripts. Mais, comme le montre cette réponse (ci-dessus), il est parfaitement possible de l’utiliser de manière interactive. Vous serez très perplexe si vous le faites

$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found

Avez-vous remarqué que j'ai dit nysql dans le pathappendcommandement, plutôt que mysql? Et cela pathappendn'a rien dit; il a juste silencieusement ignoré l'argument incorrect?

Comme je l'ai dit plus haut, il est recommandé de gérer les erreurs. Voici un exemple:

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ]
        then
            if [[ ":$PATH:" != *":$ARG:"* ]]
            then
                if ARGA=$(readlink -f "$ARG")           #notice me
                then
                    if [[ ":$PATH:" != *":$ARGA:"* ]]
                    then
                        PATH="${PATH:+"$PATH:"}$ARGA"
                    fi
                else
                    PATH="${PATH:+"$PATH:"}$ARG"
                fi
            fi
        else
            printf "Error: %s is not a directory.\n" "$ARG" >&2
        fi
    done
}
qwertyzw
la source
(1) Vous devez ajouter des guillemets: readlink -f "$ARG". (2) Je ne sais pas pourquoi cela se produirait (en particulier après que le -d "$ARG"test a réussi), mais vous voudrez peut-être vérifier s'il a readlinkéchoué. (3) Vous semblez négliger sa readlinkfonction première - mapper un nom de lien symbolique sur un "nom de fichier réel". Si (par exemple) /binest un lien symbolique vers /bin64, des appels répétés à pathappend /binpourraient aboutir à un message PATH …:/bin64:/bin64:/bin64:/bin64:…. Vous devriez probablement (aussi) vérifier si la nouvelle valeur de $ARGest déjà dans PATH.
Scott
(1) Bonne observation, je l'ai corrigé. (2) dans quel cas readlink échouerait-il? En supposant qu'un chemin est correct et qu'il fait référence à un emplacement valide. (3) Je ne suis pas sûr de savoir ce qui dicte la fonction principale de readlink, je pense que la plupart (sinon tous?) Des chemins d'un système de fichiers unix peuvent être divisés en 2 types de liens, des liens symboliques et des liens durs. Vous avez raison, mais le but de ma réponse n'était pas de l'ajouter (car j'ai remarqué que d'autres réponses l'avaient déjà mentionné). La seule raison pour laquelle
j'ajoute
(2) Je veux dire, si à tout le moins le nom de la commande implique / suggère son but, alors «link» dans readlink peut faire référence à des liens physiques ou à des liens symboliques. Vous avez cependant raison: man readlink dit 'readlink - affiche des liens symboliques résolus ou des noms de fichiers canoniques', le .dans mon exemple, je pense, peut être considéré comme un nom de fichier canonique. Correct?
Qwertyzw
(1) Pour les personnes qui comprennent les liens symboliques, readlinkle nom de celui-ci en indique clairement le but - il regarde un lien symbolique et lit le chemin qu'il contient (c.-à-d. Qu'il pointe vers). (2) Eh bien, j'ai dit que je ne savais pas pourquoi readlinkcela échouerait. Je faisais remarquer que si un script ou une fonction contient plusieurs commandes, et que la commande  n échouera de manière catastrophique (ou n’aura aucun sens) si la commande  m échoue (où m  <  n ), il est prudent de vérifiez si la commande m a échoué et gérez cela d'une manière ou d'une autre - à tout le moins,… (suite)
Scott
(Suite)… abandonner le script / la fonction avec un diagnostic. En théorie, cela readlinkpourrait échouer si (a) le répertoire a été renommé ou supprimé (par un autre processus) entre vos appels à testet readlink, ou (b) s'il a /usr/bin/readlinkété supprimé (ou corrompu). (3) Vous semblez manquer mon point. Je ne vous encourage pas à reproduire d'autres réponses. Je dis qu'en vérifiant si l'original ARG(à partir de la ligne de commande) est déjà entré PATH, mais en ne répétant pas la vérification pour le nouveau ARG(le résultat de readlink), votre réponse est incomplète et donc incorrecte. … (Suite)
Scott
1
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
GreenFox
la source
1

Cela fonctionne bien:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
Akceptor
la source
0

Mes versions sont moins attentives aux chemins vides et insistent pour que les chemins soient valides et les répertoires que certaines postées ici, mais je trouve une collection volumineuse de prepend / append / clean / unique-ify / etc. les fonctions du shell doivent être utiles pour la manipulation du chemin. L'ensemble, dans leur état actuel, se trouve ici: http://pastebin.com/xS9sgQsX (commentaires et améliorations très bienvenus!)

Andybuckley
la source
0

Vous pouvez utiliser un liner perl one:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

La voici en bash:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding $1 to PATH."
    if [[ ! -d "$1" ]]; then
        echo "$1 is not a directory.";
    elif [[ ":$PATH:" == *":$1:"* ]]; then
        echo "$1 is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}$1" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}
dpatru
la source
0

J'ai légèrement modifié la réponse de Gordon Davisson pour utiliser le répertoire actuel si aucun n'est fourni. Donc, vous pouvez simplement faire à paddpartir du répertoire que vous voulez ajouter à votre PATH.

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}
Thorsten Lorenz
la source
0

Vous pouvez vérifier si une variable personnalisée a été définie, sinon définissez-la puis ajoutez les nouvelles entrées:

if [ "$MYPATHS" != "true" ]; then
    export MYPATHS="true"
    export PATH="$PATH:$HOME/bin:"

    # java stuff
    export JAVA_HOME="$(/usr/libexec/java_home)"
    export M2_HOME="$HOME/Applications/apache-maven-3.3.9"
    export PATH="$JAVA_HOME/bin:$M2_HOME/bin:$PATH"

    # etc...
fi

Bien sûr, ces entrées peuvent toujours être dupliquées si elles sont ajoutées par un autre script, tel que /etc/profile.

David Kennedy
la source
0

Ce script vous permet d'ajouter à la fin de $PATH:

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

Ou ajouter au début de $PATH:

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2

# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case $1 in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && PATH=$prefix:$PATH
}
Tom Hale
la source
0

Voici un moyen conforme à POSIX.

# USAGE: path_add [include|prepend|append] "dir1" "dir2" ...
#   prepend: add/move to beginning
#   append:  add/move to end
#   include: add to end of PATH if not already included [default]
#          that is, don't change position if already in PATH
# RETURNS:
# prepend:  dir2:dir1:OLD_PATH
# append:   OLD_PATH:dir1:dir2
# If called with no paramters, returns PATH with duplicate directories removed
path_add() {
    # use subshell to create "local" variables
    PATH="$(path_unique)"
    export PATH="$(path_add_do "$@")"
}

path_add_do() {
    case "$1" in
    'include'|'prepend'|'append') action="$1"; shift ;;
    *)                            action='include'   ;;
    esac

    path=":$PATH:" # pad to ensure full path is matched later

    for dir in "$@"; do
        #       [ -d "$dir" ] || continue # skip non-directory params

        left="${path%:$dir:*}" # remove last occurrence to end

        if [ "$path" = "$left" ]; then
            # PATH doesn't contain $dir
            [ "$action" = 'include' ] && action='append'
            right=''
        else
            right=":${path#$left:$dir:}" # remove start to last occurrence
        fi

        # construct path with $dir added
        case "$action" in
            'prepend') path=":$dir$left$right" ;;
            'append')  path="$left$right$dir:" ;;
        esac
    done

    # strip ':' pads
    path="${path#:}"
    path="${path%:}"

    # return
    printf '%s' "$path"
}

# USAGE: path_unique [path]
# path - a colon delimited list. Defaults to $PATH is not specified.
# RETURNS: `path` with duplicated directories removed
path_unique() {
    in_path=${1:-$PATH}
    path=':'

    # Wrap the while loop in '{}' to be able to access the updated `path variable
    # as the `while` loop is run in a subshell due to the piping to it.
    # https://stackoverflow.com/questions/4667509/shell-variables-set-inside-while-loop-not-visible-outside-of-it
    printf '%s\n' "$in_path" \
    | /bin/tr -s ':' '\n'    \
    | {
            while read -r dir; do
                left="${path%:$dir:*}" # remove last occurrence to end
                if [ "$path" = "$left" ]; then
                    # PATH doesn't contain $dir
                    path="$path$dir:"
                fi
            done
            # strip ':' pads
            path="${path#:}"
            path="${path%:}"
            # return
            printf '%s\n' "$path"
        }
}

Il est cribbed de Guillaume Perrault-Archambault 's réponse à cette question et mike511 ' s réponse ici .

MISE À JOUR 2017-11-23: Correction d'un bug par @Scott

go2null
la source
J'allais upvoter pour offrir une option de ligne de commande permettant de choisir entre l'ajout au début et l'ajout après, avec une valeur par défaut. Mais ensuite je me suis dit: c’est énormément de code quelque peu cryptique, sans explication. (Et le fait que vous ayez deux fonctions, où l'une change de PATH et reprend sa nouvelle valeur, et l'autre capture cette sortie et l' assigne à nouveau à PATH , est simplement une complexité inutile.)… (Suite)
Scott
(Suite)… Et puis j'ai remarqué que les liens étaient faux. (Et je ne suis pas sûr de savoir pourquoi vous blâmez ces gars-là; vous ne semblez pas avoir copié beaucoup de leurs réponses.) Et puis j'ai remarqué que le code était faux. Je suppose que cela permet de maintenir un PATH bien formé, mais rien ne garantit qu’il est déjà bien formé, surtout si vous en avez un non éclairé /etc/profile. Le répertoire que vous essayez d'ajouter à PATH est peut-être déjà présent plusieurs fois , et votre code y étouffe. … (Suite)
Scott
(Suite) ... Par exemple, si vous essayez de préfixer /a/ckà /b:/a/ck:/tr:/a/ck, vous obtenez /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott