web-dev-qa-db-fra.com

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

Quelqu'un a-t-il écrit une fonction bash pour ajouter un répertoire à $ PATH uniquement s'il n'y est 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 ajoute uniquement le répertoire s'il ne se trouve pas là. Mais, si quelqu'un a déjà écrit (ou trouvé) une telle chose, je ne passerai pas le temps dessus.

125
Doug Harris

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 PATH="$1${PATH:+":$PATH"}" au lieu de la ligne PATH= ci-dessus.

123
Gordon Davisson

En développant la réponse de Gordon Davisson, cela supporte 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 ...

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

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
12
Dennis Williamson

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 utilisiez une version de bash> = 3, vous pouvez utiliser les expressions rationnelles de bash et effectuer:

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.

10
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.

7
Russell

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 notation ${var//pattern/sub} de bash; voir le manuel bash pour plus de détails.

6
Rob Hague

Pour les préposées, j'aime bien la solution de @ Russell, mais il y a un petit bug: si vous essayez d'ajouter quelque chose comme "/ bin" à un chemin de "/ 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
}
5
PeterS6g

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, tout à lui revient), il existe depuis des lustres dans ma vie. 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/
5
terdon

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.

4
nagul

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 (grâce à cette question pour m'indiquant 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
}
2
Doug Harris

Voir Comment éviter de dupliquer la variable de chemin dans csh? sur StackOverflow pour un ensemble de réponses à cette question.

2
Jonathan Leffler

Je suis un peu surpris que personne ne l'ait mentionné pour le moment, mais vous pouvez utiliser readlink -f pour convertir les 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 commande readlink -f convertira (entre autres choses) un chemin relatif en chemin absolu. Cela vous permet de faire quelque chose comme

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

2. Pourquoi testons-nous deux fois sur PATH?

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

Peut-être plus important encore, l'objectif principal de readlink est, comme son nom l'indique, d'examiner un lien symbolique et de lire le chemin qu'il contient (c'est-à-dire les points). 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 que vous avez déjà /usr/lib/Perl/5.14 dans votre PATH, eh bien, c’est correct; nous pouvons simplement le laisser tel quel. Mais, si /usr/lib/Perl/5.14 ne figure pas déjà dans votre PATH, nous appelons readlink et obtenons ARGA = /usr/lib/Perl/5.14.2, puis nous ajoutons cela à PATH. Mais attendez une minute - si déjà dit pathappend /usr/lib/Perl/5.14, alors vous avez déjà /usr/lib/Perl/5.14.2 dans votre PATH et, encore une fois, nous devons vérifier cela pour éviter de l'ajouter à PATH une seconde fois.

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

Si cela n’est pas clair, cette ligne teste si la variable readlink réussit. Ceci est juste une bonne pratique de programmation défensive. Si nous allons utiliser le résultat de la commande m en tant que dans commande n (où m < n ), il est prudent de vérifier si la commande m a échoué et gère cela d'une manière ou d'une autre. Je ne pense pas qu'il soit probable que readlink échoue - mais, comme indiqué dans Comment récupérer le chemin absolu d'un fichier arbitraire à partir d'OSX et ailleurs , readlink est une GNU invention. Comme il n’est pas spécifié dans POSIX, sa disponibilité dans Mac OS, Solaris et d’autres Unix non Linux est sujette à caution. 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, vous ne voudrez pas faire pathappend ..

Le deuxième test -d ([ -d "$ARGA" ]) est probablement inutile. Je ne vois aucun scénario où $ARG est un répertoire et readlink réussit, mais $ARGA n’est pas un répertoire. Je viens de copier-coller la première instruction if pour créer la troisième et j'ai laissé le test -d 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 s'il en est un, et l'ignore s'il ne l'est pas. Cela peut (ou non) être approprié si vous utilisez pathappend uniquement dans les fichiers "." (tels que .bash_profile et .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 la commande pathappend, plutôt que mysql? Et cette pathappend n’a rien dit; il a simplement ignoré silencieusement l'argument incorrect?

Comme je l’ai dit plus haut, c’est une bonne pratique 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
}
2
qwertyzw

Cela fonctionne bien:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
1
user2425755
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
1
GreenFox

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 grande collection de prepend/append/clean/unique-ify/etc Les fonctions du shell doivent être utiles pour la manipulation du chemin. Le tout, dans leur état actuel, se trouve ici: http://Pastebin.com/xS9sgQsX (commentaires et améliorations très bienvenus!)

0
andybuckley

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 tiré de la réponse de Guillaume Perrault-Archambaultà cette question et la réponse de mike511ici .

UPDATE 2017-11-23: Correction d'un bug par @Scott

0
go2null

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 ajoutez 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
}
0
Tom Hale

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 entendu, ces entrées pourraient toujours être dupliquées si elles étaient ajoutées par un autre script, tel que /etc/profile.

0
David Kennedy

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
}
0
dpatru

J'ai légèrement modifié la réponse de Gordon Davisson pour utiliser le répertoire en cours si aucun n'est fourni. Donc, vous pouvez juste faire padd à partir 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
}
0
Thorsten Lorenz