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.
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.
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
}
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.
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.
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.
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
}
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/
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.
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
}
Vedi Come evitare di duplicare la variabile del percorso in csh? su StackOverflow per un set di risposte a questa domanda.
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
}
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
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.
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.
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 comandopathappend
, piuttosto chemysql
? E chepathappend
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
}
In questo modo funziona bene:
if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
function __path_add(){
if [ -d "$1" ] ; then
local D=":${PATH}:";
[ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
}
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!)
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
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
}
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
.
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
}
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
}