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.
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.
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
}
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.
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.
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.
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
}
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/
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.
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
}
Voir Comment éviter de dupliquer la variable de chemin dans csh? sur StackOverflow pour un ensemble de réponses à cette question.
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
}
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
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.
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.
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 commandepathappend
, plutôt quemysql
? Et cettepathappend
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
}
Cela fonctionne bien:
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
}
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!)
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
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
}
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
.
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
}
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
}