it-swarm-eu.dev

Aggiungi la directory a $ PATH se non è già lì

Qualcuno ha scritto una funzione bash per aggiungere una directory a $ PATH solo se non è già lì?

In genere aggiungo al PERCORSO usando qualcosa come:

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

Se costruisco il mio PATH in .bash_profile, non viene letto a meno che la sessione in cui mi trovo sia una sessione di accesso, il che non è sempre vero. Se costruisco il mio PATH in .bashrc, viene eseguito con ogni sottoshell. Quindi, se lancio una finestra di Terminale e poi eseguo lo schermo e poi eseguo uno script di Shell, ottengo:

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

Ho intenzione di provare a costruire una funzione di bash chiamata add_to_path() che aggiunge solo la directory se non è lì. Ma, se qualcuno ha già scritto (o trovato) una cosa del genere, non mi dilungherò su questo.

125
Doug Harris

Dal mio .bashrc:

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

Si noti che PATH dovrebbe già essere contrassegnato come esportato, quindi non è necessario riesportare. Questo controlla se la directory esiste ed è una directory prima di aggiungerla, cosa che potrebbe non interessare.

Inoltre, questo aggiunge la nuova directory alla fine del percorso; per mettere all'inizio, utilizzare PATH="$1${PATH:+":$PATH"}" anziché la riga PATH= sopra.

123
Gordon Davisson

Espandendo la risposta di Gordon Davisson, questo supporta più argomenti

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

Quindi puoi fare pathappend path1 path2 path3 ...

Per prepending,

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

Simile a pathappend, puoi farlo

pathprepend path1 path2 path3 ...

Ecco qualcosa da la mia risposta a questa domanda combinata con la struttura di Doug La funzione di Harris. Usa le espressioni regolari di Bash:

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

Metti questo nei commenti alla risposta selezionata, ma i commenti non sembrano supportare la formattazione PRE, quindi aggiungendo la risposta qui:

@ gordon-davisson Non sono un grande fan di citazioni e concatenazioni inutili. Supponendo che tu stia usando una versione bash> = 3, puoi invece usare le regex incorporate di bash e fare:

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

Questo gestisce correttamente i casi in cui ci sono spazi nella directory o il PERCORSO. C'è qualche domanda sul fatto che il motore regex di bash sia abbastanza lento da rendere questa rete meno efficiente della concatenazione di stringhe e dell'interpolazione della tua versione, ma in qualche modo mi sembra semplicemente più esteticamente pulita.

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
}

Quando hai bisogno di $ HOME/bin per apparire esattamente una volta all'inizio del tuo $ PATH e da nessun'altra parte, non accettare sostituti.

7
Russell

Ecco una soluzione alternativa che ha l'ulteriore vantaggio di rimuovere le entrate ridondanti:

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

Il singolo argomento di questa funzione viene anteposto al PERCORSO e la prima istanza della stessa stringa viene rimossa dal percorso esistente. In altre parole, se la directory esiste già nel percorso, viene promossa in primo piano anziché aggiunta come duplicato.

La funzione funziona anteponendo i due punti al percorso per garantire che tutte le voci abbiano i due punti in primo piano e quindi anteporre la nuova voce al percorso esistente con quella voce rimossa. L'ultima parte viene eseguita usando la notazione ${var//pattern/sub} di bash; vedi il manuale di bash per maggiori dettagli.

6
Rob Hague

Per prepending, mi piace la soluzione di @ Russell, ma c'è un piccolo bug: se provi ad anteporre qualcosa come "/ bin" ad un percorso di "/ sbin:/usr/bin:/var/usr/bin:/usr/local/bin:/usr/sbin "sostituisce"/bin: "3 volte (quando non corrispondeva affatto). Combinando una correzione per questo con la soluzione di aggiunta di @ gordon-davisson, ottengo questo:

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

Ecco il mio (credo che sia stato scritto anni fa da Oscar, il sysadmin del mio vecchio laboratorio, tutto merito di lui), è stato in giro nella mia base da secoli. Ha il vantaggio aggiuntivo di consentire di anteporre o aggiungere la nuova directory come desiderato:

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

Uso:

$ 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 semplice alias come questo di seguito dovrebbe fare il trucco:

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

Tutto ciò che fa è dividere il percorso sul carattere: e confrontare ogni componente con l'argomento passato. Grep controlla la corrispondenza di una linea completa e stampa il conteggio.

Esempio di utilizzo:

$ 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

Sostituisci il comando echo con addToPath o qualche alias/funzione simile.

4
nagul

Ecco cosa ho inventato:

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
}

Ora in .bashrc ho:

add_to_path /usr/local/mysql/bin

Versione aggiornata seguente commento su come il mio originale non gestirà le directory con spazi (grazie a questa domanda per indicandomi di usare 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

Vedi Come evitare di duplicare la variabile del percorso in csh? su StackOverflow per un set di risposte a questa domanda.

2
Jonathan Leffler

Sono un po 'sorpreso dal fatto che nessuno lo abbia ancora menzionato, ma puoi usare readlink -f per convertire i percorsi relativi in ​​percorsi assoluti e aggiungerli al PERCORSO come tale.

Ad esempio, per migliorare la risposta di Guillaume Perrault-Archambault,

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

diventa

pathappend() {
    for ARG in "[email protected]"
    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. Nozioni di base - Che cosa fa questo?

Il comando readlink -f (tra le altre cose) convertirà un percorso relativo in un percorso assoluto. Questo ti permette di fare qualcosa di simile

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

2. Perché testiamo per essere due volte in PATH?

Bene, considera l'esempio sopra. Se l'utente dice pathappend . dalla directory /path/to/my/bin/dir una seconda volta, ARG sarà .. Ovviamente, . non sarà presente in PATH. Ma allora ARGA sarà impostato su /path/to/my/bin/dir (l'equivalente del percorso assoluto di .), che è già in PATH. Quindi dobbiamo evitare di aggiungere /path/to/my/bin/dir a PATH una seconda volta.

Forse ancora più importante, lo scopo principale di readlink è, come suggerisce il nome, di guardare un link simbolico e di leggere il nome del percorso che contiene (cioè, punta a). Per esempio:

$ 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

Ora, se dici pathappend /usr/lib/Perl/5.14 e hai già /usr/lib/Perl/5.14 nel tuo PATH, beh, va bene; possiamo semplicemente lasciarlo così com'è. Ma, se /usr/lib/Perl/5.14 non è già nel tuo PATH, chiamiamo readlink e ottieni ARGA = /usr/lib/Perl/5.14.2, e poi aggiungiamo che a PATH. Ma aspetta un minuto - se tu già hai detto pathappend /usr/lib/Perl/5.14, allora hai già /usr/lib/Perl/5.14.2 nel tuo PATH, e, ancora, dobbiamo verificarlo per evitare di aggiungerlo a PATH una seconda volta.

3. Qual è l'accordo con if ARGA=$(readlink -f "$ARG")?

Nel caso non sia chiaro, questa linea verifica se il readlink ha esito positivo. Questa è solo una buona pratica di programmazione difensiva. Se useremo l'output dal comando m as parte del comando n (dove m < n ), è prudente controllare se il comando m fallito e gestirlo in qualche modo. Non credo sia probabile che readlink fallirà - ma, come discusso in Come recuperare il percorso assoluto di un file arbitrario da OSX e altrove , readlink è un'invenzione GNU. Non è specificato in POSIX, quindi la sua disponibilità in Mac OS, Solaris e altri Unix non Linux è discutibile. Installando una rete di sicurezza, rendiamo il nostro codice un po 'più portatile.

Ovviamente, se sei su un sistema che non ha readlink, non vorrai fare pathappend ..

Il secondo test -d ([ -d "$ARGA" ]) è davvero probabilmente non necessario. Non riesco a pensare a nessuno scenario in cui $ARG sia una directory e readlink abbia esito positivo, ma $ARGA non è una directory. Ho appena copiato e incollato la prima istruzione if per creare il terzo, e ho lasciato il -d test lì per pigrizia.

4. Qualche altro commento?

Si. Come molte altre risposte qui, questa verifica se ogni argomento è una directory, la elabora se lo è, e la ignora se non lo è. Questo può (o non può) essere adeguato se si utilizza pathappend solo nei file "." (come .bash_profile e .bashrc) e altri script. Ma, come ha mostrato questa risposta (sopra), è perfettamente fattibile usarlo in modo interattivo. Sarai molto perplesso se lo fai

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

Hai notato che ho nysql nel comando pathappend, piuttosto che mysql? E che pathappend non ha detto nulla; ha semplicemente ignorato l'argomento non corretto?

Come ho detto sopra, è buona pratica gestire gli errori. Ecco un esempio:

pathappend() {
    for ARG in "[email protected]"
    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

In questo modo funziona bene:

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

Le mie versioni sono meno attente ai percorsi vuoti e insistono sul fatto che i percorsi siano validi e le directory rispetto ad alcuni postati qui, ma trovo una raccolta molto ampia di antefatte/append/clean/unique-ify/etc. Le funzioni di shell sono utili per la manipolazione del percorso. L'intero lotto, nel loro stato attuale, è qui: http://Pastebin.com/xS9sgQsX (feedback e miglioramenti molto graditi!)

0
andybuckley

Ecco un modo conforme a 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 "[email protected]")"
}

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 "[email protected]"; 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"
        }
}

È criptato da Guillaume Perrault-Archambault risposta a questa domanda e mike511 risposta qui .

AGGIORNAMENTO 2017-11-23: bug corretto per @Scott

0
go2null

Questo script ti consente di aggiungere alla fine di $PATH:

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

Oppure aggiungi all'inizio di $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

Puoi verificare se è stata impostata una variabile personalizzata, altrimenti impostarla e quindi aggiungere le nuove voci:

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

Naturalmente, queste voci potrebbero ancora essere duplicate se aggiunte da un altro script, come /etc/profile.

0
David Kennedy

Puoi usare un rivestimento 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;' "[email protected]"
}

Eccolo in 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

Ho leggermente modificato la risposta di Gordon Davisson per usare la directory corrente se non ne viene fornita nessuna. Quindi puoi semplicemente fare padd dalla directory che vuoi aggiungere al tuo PATH.

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