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


126

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

In genere aggiungo a PATH usando qualcosa come:

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

Se costruisco il mio PERCORSO in .bash_profile, allora 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 PERCORSO in .bashrc, viene eseguito con ogni subshell. Quindi, se lancio una finestra Terminale e quindi eseguo schermo e quindi eseguo uno script di shell, ottengo:

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

Proverò a creare una funzione bash chiamata add_to_path()che aggiunge la directory solo se non è lì. Ma se qualcuno ha già scritto (o trovato) una cosa del genere, non ci passerò il tempo.


Vedi stackoverflow.com/questions/273909/… per alcune infrastrutture che possono aiutarti.
dmckee,


Se inquadra il problema come "aggiungendo solo se non già presente", rimarrai brutalmente sorpreso quando arriva il giorno in cui è importante che l'elemento inserito sia all'inizio ma non finisce lì. Un approccio migliore sarebbe quello di inserire l'elemento e quindi rimuovere i duplicati, quindi se la nuova voce era già lì verrà effettivamente spostata all'inizio.
Don Hatch,

Risposte:


125

Dal mio .bashrc:

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

Nota che il PERCORSO dovrebbe già essere contrassegnato come esportato, quindi non è necessario riesportare. Controlla se la directory esiste e è una directory prima di aggiungerla, di cui potresti non preoccuparti.

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


26
Ci tengo.
Dennis Williamson,

4
@Neil: funziona, perché si confronta con ":$PATH:"invece di solo"$PATH"
Gordon Davisson

3
@GordonDavisson: mi scuso, il mio test è stato sbagliato e hai ragione.
Neil,

2
@GordonDavisson Qual è il punto delle cose tra parentesi graffe. Non riesco a ${PATH:+"$PATH:"}
decifrarlo

5
@ Mark0978: Questo è quello che ho fatto per risolvere il problema che Bukzor ha sottolineato. ${variable:+value}significa verificare se variableè definito e ha un valore non vuoto e se lo fa dà il risultato della valutazione value. Fondamentalmente, se il PERCORSO non è vuoto all'inizio, lo imposta su "$PATH:$1"; se è vuoto lo imposta su just "$1"(notare la mancanza di due punti).
Gordon Davisson il

19

Espandendo la risposta di Gordon Davisson, questo supporta molteplici argomenti

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

Quindi puoi fare pathappend path1 path2 path3 ...

Per anteporre,

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


3
Questo è fantastico! Ho fatto una piccola modifica. Per la funzione 'pathprepend', è conveniente leggere gli argomenti al contrario, quindi puoi dire, per esempio, pathprepend P1 P2 P3e finire con PATH=P1:P2:P3. Per ottenere questo comportamento, cambia for ARG in "$@" doinfor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael il

Grazie @ishmael, buon suggerimento, ho modificato la risposta. Mi rendo conto che il tuo commento ha più di due anni, ma non sono tornato da allora. Devo capire come ottenere e-mail di scambio in pila per atterrare nella mia casella di posta!
Guillaume Perrault-Archambault,

14

Ecco qualcosa dalla mia risposta a questa domanda combinata con la struttura della funzione di Doug Harris. Utilizza espressioni regolari di Bash:

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

Questo ha funzionato per me solo usando $1invece${1}
Andrei il

@Andrei: Sì, le parentesi graffe non sono necessarie in questo caso. Non sono sicuro del motivo per cui li ho inclusi.
Dennis Williamson,

10

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

@ gordon-davisson Non sono un grande fan di citazioni e concatenazioni non necessarie. Supponendo che tu stia usando una versione bash> = 3, puoi invece usare regexs integrati in 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 nel PERCORSO. C'è qualche dubbio sul fatto che il motore regex integrato da bash sia abbastanza lento da renderlo meno efficiente della concatenazione e dell'interpolazione di stringhe della tua versione, ma in qualche modo mi sembra più esteticamente pulito.


1
I commenti supportano formatting using the backticksolo ma non si ottiene alcun controllo di paragrafo decente.
Boatcoder

Questo mette l'aggiunta alla fine. È spesso preferibile aggiungere all'inizio per sostituire le posizioni esistenti.
Dennis Williamson,

@DennisWilliamson Questo è un punto giusto, anche se non lo consiglierei come comportamento predefinito. Non è difficile capire come cambiare per un prepend.
Christopher Smith,

@ChristopherSmith - re: unnecessary quotingimplica che sai in anticipo che $PATHnon è nulla. "$PATH"rende OK se PATH è nullo o no. Allo stesso modo se $1contiene caratteri che potrebbero confondere il parser dei comandi. Mettere la regex tra virgolette "(^|:)$1(:|$)"impedisce questo.
Jesse Chisholm,

@JesseChisholm: In realtà, credo che il punto di Christopher sia che le regole siano diverse tra [[e ]]. Io preferisco citare tutto ciò che può avere bisogno di essere citato, a meno che fa sì che a fallire, ma credo che abbia ragione, e che le citazioni in realtà non sono necessari in giro $PATH. D'altra parte, mi sembra che tu abbia ragione $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
}

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


Grazie, è una bella soluzione elegante, ma ho scoperto che dovevo fare PATH=${PATH/"... piuttosto che PATH=${PATH//"... per farlo funzionare.
Mark Booth,

Il modulo a doppia barra dovrebbe corrispondere a qualsiasi numero di corrispondenze; la singola barra corrisponde solo alla prima (cerca "Sostituzione del modello" nella pagina man di bash). Non sono sicuro del motivo per cui non ha funzionato ...
Andybuckley,

Ciò fallisce nel caso insolito che $1è l'unica voce (senza due punti). L'entrata diventa doppia.
Dennis Williamson,

Elimina anche in modo troppo aggressivo, come sottolineato da PeterS6g .
Dennis Williamson,

6

Ecco una soluzione alternativa che ha l'ulteriore vantaggio di rimuovere gli enti ridondanti:

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

Il singolo argomento di questa funzione è 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, quindi antepone la nuova voce al percorso esistente con quella voce rimossa. L'ultima parte viene eseguita usando la ${var//pattern/sub}notazione di bash ; consultare il manuale di bash per maggiori dettagli.


Buon pensiero; implementazione imperfetta. Considera cosa succede se hai già /home/robertte PATHe te pathadd /home/rob.
Scott,

5

Ecco il mio (credo che sia stato scritto anni fa da Oscar, l'amministratore del mio vecchio laboratorio, tutto merito a lui), è stato nel mio bashrc per 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

Per anteporre, mi piace la soluzione di @ Russell, ma c'è un piccolo bug: se provi a anteporre qualcosa come "/ bin" a un percorso di "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "sostituisce" / bin: "3 volte (quando non corrispondeva affatto). Combinando una soluzione per questo con la soluzione accodata 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
}

4

Un semplice alias come questo qui sotto 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 verifica la corrispondenza di una riga 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.


L'uso di "grep -x" è probabilmente più veloce del loop che ho inserito nella mia funzione bash.
Doug Harris,


2

Ecco cosa ho montato:

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 dopo il commento su come il mio originale non gestirà le directory con spazi (grazie a questa domanda per indicarmi 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
}

1
Questo potrebbe non riuscire se qualsiasi nome di directory già PATHcontiene spazi, *, ?, o [... ].
Scott,

Buon punto ... ma io sono un ragazzo di vecchia scuola di Linux e non userei mai un percorso con spazi bianchi :-)
Doug Harris,

Buon per te, per non creare cose con spazi bianchi nei loro nomi. Peccato per te per aver scritto codice che non può gestirli quando esistono. E cosa c'entra l'essere "un ragazzo Linux di vecchia scuola"? Windoze potrebbe aver reso popolare l'idea (grazie Documents and Settingse Program Files), ma Unix ha supportato percorsi che contengono spazi bianchi da prima che Windoze esistesse.
Scott,

2

Sono un po 'sorpreso che nessuno lo abbia ancora menzionato, ma puoi usarlo readlink -fper convertire percorsi relativi in ​​percorsi assoluti e aggiungerli al PERCORSO in quanto tale.

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

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

diventa

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. Le basi - A cosa serve questo?

Il readlink -fcomando convertirà (tra le altre cose) un percorso relativo in un percorso assoluto. Questo ti permette di fare qualcosa del genere

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

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

Bene, considera l'esempio sopra. Se l'utente dice dalla directory una seconda volta, sarà . Certo, non sarà presente in . Ma poi sarà impostato su (l'equivalente del percorso assoluto di ), che è già in . Quindi abbiamo bisogno di evitare di aggiungere ad una seconda volta.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Forse ancora più importante, lo scopo principale di readlinkè, come suggerisce il nome, guardare un link simbolico e leggere il 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.14nel tuo PERCORSO, bene, va bene; possiamo lasciarlo così com'è. Ma, se /usr/lib/perl/5.14non è già nel PATH, che noi chiamiamo readlinke otteniamo ARGA= /usr/lib/perl/5.14.2, e poi aggiungiamo che a PATH. Ma aspetta un minuto - se hai già detto pathappend /usr/lib/perl/5.14, allora hai già /usr/lib/perl/5.14.2nel tuo PERCORSO e, ancora una volta, dobbiamo controllarlo per evitare di aggiungerlo PATHuna seconda volta.

3. Qual è il if ARGA=$(readlink -f "$ARG")problema con ?

Nel caso in cui non sia chiaro, questa riga verifica se ha readlinkesito positivo. Questa è solo una buona pratica di programmazione difensiva. Se useremo l'output del comando  m come parte del comando  n (dove m  <  n ), è prudente verificare se il comando  m non è riuscito e gestirlo in qualche modo. Non credo che probabilmente readlinkfallirà, ma, come discusso in Come recuperare il percorso assoluto di un file arbitrario da OS X e altrove, readlinkè un'invenzione GNU. Non è specificato in POSIX, quindi la sua disponibilità in Mac OS, Solaris e altri Unix non Linux è discutibile. (In effetti, ho appena letto un commento che dice "readlink -fnon sembra funzionare su Mac OS X 10.11.6, ma realpathfunziona immediatamente . ”Quindi, se utilizzi un sistema che non ha readlinko dove readlink -fnon funziona, potresti essere in grado di modificarlo script da usare realpath.) Installando una rete di sicurezza, rendiamo il nostro codice un po 'più portabile.

Naturalmente, se utilizzi un sistema che non ha readlink(o  realpath), non vorrai farlo .pathappend .

Il secondo -dtest ( [ -d "$ARGA" ]) è davvero probabilmente non necessario. Non riesco a pensare a nessuno scenario in cui $ARGuna directory ha readlinkesito positivo, ma  $ARGAnon è una directory. Ho appena copiato e incollato la prima ifistruzione per creare la terza e ho lasciato -dlì il  test per pigrizia.

4. Altri commenti?

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

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

Hai notato che ho detto nysql nel pathappendcomando, piuttosto che mysql? E questo pathappendnon ha detto nulla; ha semplicemente ignorato in silenzio l'argomento errato?

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

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
}

(1) Si dovrebbe aggiungere citazioni: readlink -f "$ARG". (2) Non so perché sia ​​successo (soprattutto dopo che il -d "$ARG"test è riuscito), ma potresti voler verificare se readlinknon è riuscito. (3) Sembra che tu stia trascurando readlinkla funzione principale: mappare il nome di un link simbolico a un "nome file reale". Se (ad esempio) /binè un collegamento simbolico a /bin64, le chiamate ripetute a pathappend /binpotrebbero comportare il PERCORSO …:/bin64:/bin64:/bin64:/bin64:…. Probabilmente dovresti (anche) verificare se il nuovo valore di $ARGè già in PATH.
Scott,

(1) Buona osservazione, l'ho risolto. (2) in che caso readlink fallirebbe? Supponendo che un percorso sia corretto e si riferisca a una posizione valida. (3) Non sono sicuro di ciò che detta la funzione principale di readlink, credo che la maggior parte dei percorsi (se non tutti?) In un filesystem unix possano essere divisi in 2 tipi di link, link simbolici e hard link. Per quanto riguarda la voce duplicata del percorso, hai ragione, ma lo scopo della mia risposta non era quello di aggiungerlo (come ho notato che altre risposte lo hanno già menzionato). L'unica ragione per cui sto aggiungendo un'altra risposta è quella di contribuire con qualcosa che pensavo non fosse già stato contribuito
qwertyzw,

(2) Voglio dire, se almeno il nome del comando implicava / suggeriva il suo scopo, allora "link" in readlink può fare riferimento sia a hard sia a soft link. Hai ragione comunque: man readlink dice 'readlink - stampa collegamenti simbolici risolti o nomi di file canonici', .nel mio esempio credo possa essere considerato come un nome di file canonico. Corretta?
qwertyzw,

(1) Per le persone che comprendono i collegamenti simbolici, readlinkil nome implica chiaramente il suo scopo: osserva un collegamento simbolico e legge il percorso che contiene (cioè indica). (2) Bene, ho detto che non sapevo perché readlinkfallire. Stavo sottolineando che se uno script o una funzione contiene più comandi e il comando  n è garantito per fallire in modo catastrofico (o non ha alcun senso) se il comando  m fallisce (dove m  <  n ), è prudente buona pratica controlla se il comando m è fallito e gestiscilo in qualche modo - per lo meno, ... (proseguendo)
Scott

(Proseguendo) ... interrompe lo script / la funzione con una diagnostica. Ipoteticamente, readlinkpotrebbe non riuscire se (a) la directory è stata rinominata o eliminata (da un altro processo) tra le chiamate a teste readlink, oppure (b) se è /usr/bin/readlinkstata eliminata (o danneggiata). (3) Mi sembra che manchi il mio punto. Non ti sto incoraggiando a duplicare altre risposte; Sto dicendo che, verificando se l'originale ARG(dalla riga di comando) è già presente PATH, ma non ripetendo il controllo per il nuovo ARG(l'output da readlink), la risposta è incompleta e quindi errata. ... (proseguendo)
Scott,

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

1

In questo modo funziona bene:

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

0

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


0

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;' "$@"
}

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

Ho leggermente modificato la risposta di Gordon Davisson per usare l'attuale dir se non ne viene fornito nessuno. Quindi puoi semplicemente fare padddalla directory che vuoi aggiungere al tuo PERCORSO.

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

0

È possibile 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 comunque essere duplicate se aggiunte da un altro script, come /etc/profile.


0

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

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 "$@")"
}

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"
        }
}

E 'cribbed da Guillaume Perrault-Archambault 'risposta s a questa domanda e mike511 ' risposta s qui .

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


Stavo per votare questo per aver fornito un'opzione da riga di comando per scegliere tra anteponendo e postpendendo (aggiungendo), con un valore predefinito. Ma poi ho pensato: questo è un sacco di codice un po 'criptico senza spiegazioni. (E il fatto che tu abbia due funzioni, in cui una cambia PATH e fa eco al suo nuovo valore, e l'altra cattura quell'output e lo assegna di nuovo a PATH , è solo una complessità non necessaria.) ... (continua)
Scott

(Proseguendo) ... E poi ho notato che i collegamenti erano sbagliati. (E non sono sicuro del perché stai incolpando quei ragazzi; non sembra che tu abbia copiato molto dalle loro risposte.) E poi ho notato che il codice era sbagliato. Immagino che faccia un lavoro OK nel mantenere un PERCORSO ben formato, ma non c'è garanzia che sia già ben formato, specialmente se hai un non illuminato /etc/profile. La directory che stai tentando di aggiungere a PATH potrebbe essere già lì più di una volta , e il tuo codice è soffocato. ... (proseguendo)
Scott,

(Segue) ... Per esempio, se si tenta di anteporre /a/cka /b:/a/ck:/tr:/a/ck, si ottiene /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.