Aggiungere directory a $ PATH se non è già presente

Qualcuno ha scritto una function bash per aggiungere una directory a $ PATH solo se non è già presente?

Tipicamente aggiungo a PATH usando qualcosa come:

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

Se costruisco il mio PATH in .bash_profile, allora non viene letto a less che la session in cui si trova non sia una session di login – che non è sempre vero. Se costruisco il mio PATH in .bashrc, allora funziona con each sottosuolo. Quindi, se lancio una window Terminale e quindi eseguire lo schermo e quindi eseguire uno script di shell, ottengo:

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

Proverò a build una function bash chiamata add_to_path() che aggiunge solo la directory se non c'è. Ma se qualcuno ha già scritto (o trovato) una cosa del genere, non voglio trascorrere il tempo su di esso.

Dal mio .bashrc:

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

Si noti che PATH dovrebbe essere già contrassegnato come esportto, quindi non è necessario riesportre. Questo controlla se la directory esiste e è una directory prima di aggiungerla, cosa che non ti interesserà.

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

Espandendo la risposta di Gordon Davisson, questo support 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 il prepending,

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

Simile al pathappend, puoi fare

pathprepend path1 path2 path3 …

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

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

Inserisci i commenti nella risposta selezionata, ma i commenti non sembrano supportre la formattazione PRE, quindi aggiungi la risposta qui:

@ gordon-davisson Non sono un grande fan di citazione e concatenazione inutili. Supponendo che si utilizza una versione bash> = 3, è ansible utilizzare invece bash's built in regexs 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 PATH. C'è qualche dubbio sul fatto che il motore incorporato di bash è abbastanza lento da rendere less efficiente la concatenazione e l'interpolazione delle stringhe che la tua versione fa, ma in qualche modo mi sembra più esteticamente pulita.

 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 nessun altro posto, non accettare sostituti.

Ecco il mio (credo che sia stato scritto anni fa da Oscar, lo sysadmin del mio vecchio laboratorio, tutti accreditati a lui), la sua è stata in giro nel mio bashrc da secoli. Ha il vantaggio aggiuntivo di consentire di prelevare 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/ 

Per prima cosa, mi piace la soluzione di @ Russell, ma c'è un piccolo bug: se si tenta di inserire qualcosa come "/ bin" in un path di "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "sostituisce" / bin: "3 volte (quando non corrispondeva affatto). Combinando una correzione per quella con la soluzione appendice 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 } 

Ecco una soluzione alternativa che ha il vantaggio aggiuntivo di rimuovere entre ridondanti:

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

L'unico argomento a questa function viene aggiunto al PATH e la prima istanza della stessa string viene rimossa dal path esistente. In altre parole, se la directory esiste già nel path, viene promossa nella parte anteriore piuttosto che aggiunta come duplicato.

La function funziona preimpostando un colon al path per assicurarsi che tutte le voci abbiano un punto in fronte e quindi prepongano la nuova voce al path esistente con quella voce rimossa. L'ultima parte viene eseguita usando la notazione bash di ${var//pattern/sub} ; vedere il manuale della bash per ulteriori dettagli.

Un semplice alias come questo sotto dovrebbe fare il trucco:

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

Tutto quello che fa è dividere il path del: carattere e confrontare each componente con l'argomento in cui passate. Grep controlla per una corrispondenza completa della linea e printing il count.

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 

Sostituire il command echo con addToPath o qualche alias / function simile.

Vedere Come mantenere la duplicazione della variabile di path in csh? su StackOverflow per un insieme di risposte a questa domanda.

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 commento su come il mio originale non gestirà directory con spazi (grazie a questa domanda per indicarmi l'utilizzo di 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 } 
 function __path_add(){ if [ -d "$1" ] ; then local D=":${PATH}:"; [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1"; PATH="${PATH/#:/}"; export PATH="${PATH/%:/}"; fi } 

Questo funziona bene:

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

Le mie versioni sono less attenti nei routes vuoti e insistono sui routes validi e le directory rispetto a quelle pubblicate qui, ma trovo una grande collezione di prepend / append / clean / unique-ify / etc. le funzioni di shell per essere utili per la manipolazione del path. Tutto il lotto, nel loro stato attuale, è qui: http://pastebin.com/xS9sgQsX (feedback e miglioramenti benvenuti!)

È ansible utilizzare un perl one liner:

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

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

Ho leggermente modificato la risposta di Gordon Davisson per utilizzare l'attuale dir se nessuno è fornito. Quindi puoi solo fare il 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 } 

Puoi controllare se è stata impostata una variabile personalizzata, altrimenti impostarla e 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 essere duplicate se aggiunte da un altro script, ad esempio /etc/profile .

Ecco un modo conforms a POSIX.

 # add_to_path [prepend|append] "directory1" "directory2" ... # prepend: add/move to beginning # append: add/move to end # without prepend or append: add to end of PATH if not already included add_to_path() { # use subshell to create "local" variables export PATH="$(add_to_path_do "[email protected]")" } add_to_path_do() { case "$1" in 'prepend'|'append') action="$1" shift ;; *) action='append' ;; esac for dir in "[email protected]"; do [ -d "$dir" ] || continue path=":$PATH:" pre="${path%:$dir:*}" if [ "$path" = "$pre" ]; then path="$PATH" else post="${path#*:$dir:}" path="$pre:$post" path="${path#:}" path="${path%:}" fi case "$action" in 'prepend') PATH="$dir:$path" ;; 'append') PATH="$path:$dir" ;; esac done echo "$PATH" } 

E 'cribbata dalla risposta di Guillaume Perrault-Archambault a questa domanda e dalla risposta di mike511 qui .

Sono un po 'sorpreso che nessuno lo abbia mai citato, ma puoi usare il readlink -f per convertire i routes relativi in ​​routes assoluti e aggiungerli al PATH 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. Le basi – cosa fa questo bene?

Il command readlink -f (tra le altre cose) converte un path relativo in un path assoluto. Questo ti permette di fare qualcosa di simile

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

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

Beh, consideri l'esempio precedente. Se l'utente dice che il pathappend . dalla directory /path/to/my/bin/dir una seconda volta, ARG sarà . . Certo,. non sarà presente in PATH . Ma allora ARGA sarà impostato su /path/to/my/bin/dir (l'equivalente path assoluto di . ), Che è già in PATH . Quindi dobbiamo evitare di aggiungere /path/to/my/bin/dir a PATH una seconda volta.

Forse più importnte, lo scopo principale di readlink è, come suggerisce il suo nome, guardare un collegamento simbolico e leggere il path che contiene (cioè, punti 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 si dice che pathappend /usr/lib/perl/5.14 , e già hai /usr/lib/perl/5.14 nel tuo PATH, beh, va bene; possiamo lasciarlo come è. Ma, se /usr/lib/perl/5.14 non è già nel tuo PATH, chiamiamo readlink e riceviamo ARGA = /usr/lib/perl/5.14.2 e poi lo aggiungiamo in PATH . Ma aspetta un minuto – se hai già detto che pathappend /usr/lib/perl/5.14 , allora hai già /usr/lib/perl/5.14.2 nel tuo PATH e, di nuovo, dobbiamo verificare che per evitare di aggiungere per PATH una seconda volta.

3. Che cosa è il Deal With if ARGA=$(readlink -f "$ARG") ?

Nel caso in cui non sia chiaro, questa linea verifica se il readlink riesce. Questa è solo una buona pratica di programmazione difensiva. Se usiamo l'output dal command m come parte del command n (where m < n ), è prudente verificare se il command m sia fallito e gestito in qualche modo. Non credo sia probabile che il readlink fallisca, ma, come descritto in Come recuperare il path 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. Installando una networking di sicurezza, rendiamo il nostro codice un po 'più porttile.

Naturalmente, se sei in un sistema che non dispone di readlink , non vuoi fare il pathappend . .

Il secondo test -d ( [ -d "$ARGA" ] ) è veramente probabilmente inutile. Non riesco a pensare ad alcun scenario in cui $ARG sia una directory e il readlink riesce, ma $ARGA non è una directory. Ho appena copiato e incollato la prima istruzione if per creare la terza e ho lasciato il test -d fuori dalla pigrizia.

4. Qualsiasi altro commento?

Si. Come molte delle altre risposte qui, questo prova se each argomento è una directory, lo elabora se lo è e ignora se non lo è. Questo può (o no) essere adeguato se stai utilizzando il pathappend solo in " . "File (come .bash_profile e .bashrc ) e altri script. Ma, come indicato da questa risposta (sopra), è perfettamente ansible utilizzare intertriggersmente. Sarai molto perplesso se lo farai

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

Avete notato che ho detto n ysql nel command pathappend , piuttosto che m ysql ? E quel pathappend non ha detto niente; ha ignorato silenziosamente l'argomento non corretto?

Come ho già detto, è buona norma gestire 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 }