Quelle est la manière la plus élégante de supprimer un chemin de la variable $ PATH dans Bash?

114

Ou plus généralement, comment supprimer un élément d'une liste séparée par deux-points dans une variable d'environnement Bash?

Je pensais avoir vu un moyen simple de le faire il y a des années, en utilisant les formes plus avancées d'expansion variable de Bash, mais si c'est le cas, j'en ai perdu la trace. Une recherche rapide sur Google a produit étonnamment peu de résultats pertinents et aucun que j'appellerais «simple» ou «élégant». Par exemple, deux méthodes utilisant respectivement sed et awk:

PATH=$(echo $PATH | sed -e 's;:\?/home/user/bin;;' -e 's;/home/user/bin:\?;;')
PATH=!(awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PATH)

N'existe-t-il rien de simple? Y a-t-il quelque chose d'analogue à une fonction split () dans Bash?

Mise à jour:
il semble que je doive m'excuser pour ma question intentionnellement vague; J'étais moins intéressé à résoudre un cas d'utilisation spécifique qu'à provoquer une bonne discussion. Heureusement, je l'ai!

Il existe ici des techniques très intelligentes. En fin de compte, j'ai ajouté les trois fonctions suivantes à ma boîte à outils. La magie opère dans path_remove, qui repose en grande partie sur l'utilisation intelligente par Martin York de la awkvariable RS de 's.

path_append ()  { path_remove $1; export PATH="$PATH:$1"; }
path_prepend () { path_remove $1; export PATH="$1:$PATH"; }
path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; }

Le seul vrai problème ici est l'utilisation de sedpour supprimer les deux points de fin. Compte tenu de la simplicité du reste de la solution de Martin, je suis tout à fait disposé à vivre avec!


Question connexe: Comment manipuler les éléments $ PATH dans les scripts shell?

Ben Blank
la source
Pour toute variable: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"mais vous devez l'appeler comme func $VAR VAR pattern(basé sur @ martin-york et @ andrew-aylett)
vesperto

Réponses:

51

Une minute avec awk:

# Strip all paths with SDE in them.
#
export PATH=`echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}'`

Edit: Il répond aux commentaires ci-dessous:

$ export a="/a/b/c/d/e:/a/b/c/d/g/k/i:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i"
$ echo ${a}
/a/b/c/d/e:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i

## Remove multiple (any directory with a: all of them)
$ echo ${a} | awk -v RS=: -v ORS=: '/a/ {next} {print}'
## Works fine all removed

## Remove multiple including last two: (any directory with g)
$ echo ${a} | awk -v RS=: -v ORS=: '/g/ {next} {print}'
/a/b/c/d/e:/a/b/c/d/f:
## Works fine: Again!

Modifier en réponse à un problème de sécurité: (qui n'est pas pertinent pour la question)

export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}' | sed 's/:*$//')

Cela supprime tous les deux-points de fin en supprimant les dernières entrées, ce qui ajouterait effectivement .à votre chemin.

Martin York
la source
1
Échec lors de la tentative de suppression du dernier élément ou de plusieurs éléments: dans le premier cas, il ajoute le répertoire actuel (en tant que chaîne vide; un trou de sécurité potentiel), dans le second cas, il ajoute `` comme élément de chemin.
Fred Foo
1
@larsmans: Fonctionne bien pour moi. Remarque: vide n'est pas le même que le répertoire actuel qui est "./"
Martin York
2
Une chaîne vide en tant que "membre" de la PATHvariable indique , comme règle spéciale, le répertoire courant dans tous les shells Unix depuis au moins V7 Unix de 1979. C'est toujours le cas bash. Consultez le manuel ou essayez par vous-même.
Fred Foo
1
@Martin: POSIX n'exige pas ce comportement, mais le documente et l'autorise: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo
1
Il y a un problème encore plus subtil lors de la suppression du dernier élément avec ceci: awk semble ajouter un octet nul à la fin de la chaîne . Cela signifie que si vous ajoutez un autre répertoire à PATH plus tard, il ne sera en fait pas recherché.
sschuberth
55

Mon sale hack:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Martin York
la source
18
Ce n'est jamais bon signe lorsque la solution la plus évidente est de désautomatiser le processus. :-D
Ben Blank
TELLEMENT plus sûr que les alternatives "élégantes".
Jortstek
44

Étant donné que le gros problème de la substitution concerne les cas finaux, pourquoi ne pas différencier les cas finaux des autres cas? Si le chemin avait déjà des deux-points au début et à la fin, nous pourrions simplement rechercher la chaîne souhaitée entourée de deux points. Dans l'état actuel des choses, nous pouvons facilement ajouter ces deux points et les supprimer par la suite.

# PATH => /bin:/opt/a dir/bin:/sbin
WORK=:$PATH:
# WORK => :/bin:/opt/a dir/bin:/sbin:
REMOVE='/opt/a dir/bin'
WORK=${WORK/:$REMOVE:/:}
# WORK => :/bin:/sbin:
WORK=${WORK%:}
WORK=${WORK#:}
PATH=$WORK
# PATH => /bin:/sbin

Pure bash :).

Andrew Aylett
la source
2
J'ajouterais cette section de tutoriel pour un glaçage supplémentaire: tldp.org/LDP/abs/html/string-manipulation.html
Cyber ​​Oliveira
1
J'ai utilisé cela parce que cela ressemblait à la solution la plus simple. C'était super rapide et facile, et vous pouvez facilement vérifier votre travail avec echo $ WORK juste avant la dernière ligne où vous modifiez réellement la variable PATH.
Phil Gran
2
Absolument un petit bijou. Exactement ce que j'essayais de faire lorsque j'ai trouvé ce message. -Merci Andrew! BTW: Vous aimeriez peut-être ajouter des guillemets doubles autour de ": $ PATH:", juste au cas où il devrait contenir des espaces (idem pour "/ usr / bin") et la dernière ligne "$ WORK".
2
Merci, @ PacMan-- :). Je suis à peu près sûr (juste essayé) que vous n'avez pas besoin d'espaces pour les affectations WORKet PATHque l'expansion des variables se produit après que la ligne est analysée en sections pour l'affectation de variables et l'exécution de commandes. REMOVEpeut avoir besoin d'être cité, ou vous pouvez simplement mettre votre chaîne directement dans le remplacement s'il s'agit d'une constante.
Andrew Aylett
Je ne savais toujours pas quand les chaînes étaient conservées, donc j'ai toujours commencé à faire des guillemets doubles. Merci encore d'avoir clarifié cela. :)
26

Voici la solution la plus simple que je puisse concevoir:

#!/bin/bash
IFS=:
# convert it to an array
t=($PATH)
unset IFS
# perform any array operations to remove elements from the array
t=(${t[@]%%*usr*})
IFS=:
# output the new array
echo "${t[*]}"

L'exemple ci-dessus supprimera tout élément de $ PATH contenant "usr". Vous pouvez remplacer "* usr *" par "/ home / user / bin" pour supprimer uniquement cet élément.

mise à jour par sschuberth

Même si je pense que les espaces dans un $PATHsont une idée horrible , voici une solution qui le gère:

PATH=$(IFS=':';t=($PATH);n=${#t[*]};a=();for ((i=0;i<n;i++)); do p="${t[i]%%*usr*}"; [ "${p}" ] && a[i]="${p}"; done;echo "${a[*]}");

ou

IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
  p="${t[i]%%*usr*}"
  [ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
Nicerobot
la source
2
Comme une seule ligne: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
nicerobot
1
Cette solution ne fonctionne pas avec les chemins dans PATH qui contiennent des espaces; il les remplace par des deux points.
sschuberth
11

Voici une ligne unique qui, malgré les réponses actuellement acceptées et les mieux notées , n'ajoute pas de caractères invisibles à PATH et peut gérer les chemins contenant des espaces:

export PATH=$(p=$(echo $PATH | tr ":" "\n" | grep -v "/cygwin/" | tr "\n" ":"); echo ${p%:})

Personnellement, je trouve également cela facile à lire / à comprendre, et cela n'implique que des commandes courantes au lieu d'utiliser awk.

sschuberth
la source
2
... et si vous voulez quelque chose qui puisse faire face même aux nouvelles lignes dans les noms de fichiers, vous pouvez utiliser ceci: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (bien que sans doute, vous voudrez peut-être vous demander pourquoi vous en avez besoin, si vous le faites :))
ehdr
Cela supprimera les correspondances partielles, ce qui n'est probablement pas ce que vous voulez; J'utiliserais grep -v "^/path/to/remove\$"ougrep -v -x "/path/to/remove"
ShadSterling
Belle solution, mais pensez-vous vraiment que trc'est plus commun que awk? ;)
K.-Michael Aye
1
Absolument. Les environnements légers, comme Git Bash sous Windows, sont plutôt fournis avec un outil simple comme trun interprète comme awk.
sschuberth
1
@ehdr: Vous devez remplacer echo "..."par printf "%s" "..."pour qu'il fonctionne sur des chemins similaires -eet similaires. Voir stackoverflow.com/a/49418406/102441
Eric
8

Voici une solution qui:

  • est du pur Bash,
  • n'invoque pas d'autres processus (comme 'sed' ou 'awk'),
  • ne change pas IFS,
  • ne fourche pas un sous-shell,
  • gère les chemins avec des espaces, et
  • supprime toutes les occurrences de l'argument dans PATH.

    removeFromPath () {
       pd local
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {d // $ p /:}
       d = $ {d / #: /}
       CHEMIN = $ {d /%: /}
    }
robinbb
la source
4
J'aime cette solution. Peut-être rendre les noms de variables plus descriptifs?
Anukool
très agréable. Cependant, ne fonctionne pas pour les segments de chemin contenant un astérisque (*). Parfois, ils y arrivent accidentellement.
Jörg le
6

function __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:}";
CHEMIN = "$ {CHEMIN / #: /}";
export PATH = "$ {PATH /%: /}";
}

Extrayez-le de mon fichier .bashrc. Lorsque vous jouez avec PATH et qu'il se perd, awk / sed / grep devient indisponible :-)

GreenFox
la source
1
C'est un très bon point. (Je n'ai jamais aimé exécuter des utilitaires externes pour des choses simples comme celle-ci).
6

La meilleure option pure bash que j'ai trouvée jusqu'à présent est la suivante:

function path_remove {
  PATH=${PATH/":$1"/} # delete any instances in the middle or at the end
  PATH=${PATH/"$1:"/} # delete any instances at the beginning
}

Ceci est basé sur la réponse pas tout à fait correcte à Ajouter un répertoire à $ PATH s'il n'est pas déjà là sur Superuser.

Mark Booth
la source
C'est assez bien aussi. Je l'ai testé. S'il y a un chemin en double (par exemple, deux qui sont exactement identiques) dans PATH, alors un seul d'entre eux est supprimé. Vous pouvez également en faire un one-liner:removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }
Cette solution échoue lorsque le $PATHcontient un sous-dossier du chemin cible (c'est-à-dire à supprimer). Par exemple: a:abc/def/bin:b-> a/bin:b, quand abc/defdoit être supprimé.
Robin Hsu
5

Je viens d'utiliser les fonctions de la distribution bash, qui existent apparemment depuis 1991. Celles-ci sont toujours dans le paquet bash-docs sur Fedora, et étaient utilisées dans /etc/profile, mais pas plus ...

$ rpm -ql bash-doc |grep pathfunc
/usr/share/doc/bash-4.2.20/examples/functions/pathfuncs
$ cat $(!!)
cat $(rpm -ql bash-doc |grep pathfunc)
#From: "Simon J. Gerraty" <[email protected]>
#Message-Id: <[email protected]>
#Subject: Re: a shell idea?
#Date: Mon, 09 Oct 1995 21:30:20 +1000


# NAME:
#       add_path.sh - add dir to path
#
# DESCRIPTION:
#       These functions originated in /etc/profile and ksh.kshrc, but
#       are more useful in a separate file.
#
# SEE ALSO:
#       /etc/profile
#
# AUTHOR:
#       Simon J. Gerraty <[email protected]>

#       @(#)Copyright (c) 1991 Simon J. Gerraty
#
#       This file is provided in the hope that it will
#       be of use.  There is absolutely NO WARRANTY.
#       Permission to copy, redistribute or otherwise
#       use this file is hereby granted provided that
#       the above copyright notice and this notice are
#       left intact.

# is $1 missing from $2 (or PATH) ?
no_path() {
        eval "case :\$${2-PATH}: in *:$1:*) return 1;; *) return 0;; esac"
}
# if $1 exists and is not in path, append it
add_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="\$${2:-PATH}:$1"
}
# if $1 exists and is not in path, prepend it
pre_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="$1:\$${2:-PATH}"
}
# if $1 is in path, remove it
del_path () {
  no_path $* || eval ${2:-PATH}=`eval echo :'$'${2:-PATH}: |
    sed -e "s;:$1:;:;g" -e "s;^:;;" -e "s;:\$;;"`
}
M. Wacky
la source
4

J'ai écrit une réponse à cela ici (en utilisant aussi awk). Mais je ne suis pas sûr que ce soit ce que vous recherchez? Il me semble au moins clair ce qu'il fait, au lieu d'essayer de rentrer dans une seule ligne. Pour une simple doublure, cependant, qui ne supprime que des choses, je recommande

echo $PATH | tr ':' '\n' | awk '$0 != "/bin"' | paste -sd:

Le remplacement est

echo $PATH | tr ':' '\n' | 
    awk '$0 != "/bin"; $0 == "/bin" { print "/bar" }' | paste -sd:

ou (plus court mais moins lisible)

echo $PATH | tr ':' '\n' | awk '$0 == "/bin" { print "/bar"; next } 1' | paste -sd:

Quoi qu'il en soit, pour la même question, et un tas de réponses utiles, voir ici .

Johannes Schaub - litb
la source
Et si vous souhaitez supprimer une ligne contenant une chaîne partielle, utilisez awk '$0 !~ "/bin"'. C'est-à-dire conserver les lignes qui ne contiennent pas '/ bin' avec l'opérateur awk !~.
thoni56
3

Eh bien, dans bash, comme il prend en charge les expressions régulières, je ferais simplement:

PATH=${PATH/:\/home\/user\/bin/}
tapis
la source
N'est-ce pas seulement l'expansion des chemins, pas les expressions régulières?
dreamlax
2
Bien que bash prenne en charge les expressions régulières (à partir de bash 3), ce n'est pas un exemple, il s'agit d'une substitution de variable.
Robert Gamble
4
Il s'agit d'une expantion de variable de modèle et la solution présente plusieurs problèmes. 1) il ne correspondra pas au premier élément. 2) il correspondra à tout ce qui commence par "/ home / user / bin", pas seulement "/ home / user / bin". 3) il faut échapper des caractères spéciaux. Au mieux, je dirais que c'est un exemple incomplet.
nicerobot
2

J'aime les trois fonctions présentées dans la mise à jour de @ BenBlank à sa question d'origine. Pour les généraliser, j'utilise un formulaire à 2 arguments, qui me permet de définir PATH ou toute autre variable d'environnement que je souhaite:

path_append ()  { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend () { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove ()  { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }

Exemples d'utilisation:

path_prepend PATH /usr/local/bin
path_append PERL5LIB "$DEVELOPMENT_HOME/p5/src/perlmods"

Notez que j'ai également ajouté des guillemets pour permettre le bon traitement des chemins d'accès contenant des espaces.

Cary Millsap
la source
2

Quelle est la manière la plus élégante de supprimer un chemin de la variable $ PATH dans Bash?

Quoi de plus élégant que awk?

path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; 

Python! C'est une solution plus lisible et maintenable, et il est facile d'inspecter pour voir qu'elle fait vraiment ce que vous voulez.

Voulez-vous supprimer le premier élément de chemin?

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"

(Au lieu de la tuyauterie de echo , os.getenv['PATH']serait un peu plus court et fournirait le même résultat que ci-dessus, mais je crains que Python puisse faire quelque chose avec cette variable d'environnement, il est donc probablement préférable de le canaliser directement depuis l'environnement qui vous tient à cœur. .)

De même pour supprimer de la fin:

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"

Pour rendre ces fonctions shell réutilisables que vous pouvez, par exemple, coller dans votre fichier .bashrc:

strip_path_first () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"
}

strip_path_last () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"
}
Salle Aaron
la source
1

Oui, mettre un deux-points à la fin de PATH, par exemple, rend la suppression d'un chemin un peu moins maladroite et sujette aux erreurs.

path_remove ()  { 
   declare i newPATH
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      #echo ${@:${i}:1}
      newPATH="${newPATH//${@:${i}:1}:/}" 
   done
   export PATH="${newPATH%:}" 
   return 0; 
} 

path_remove_all ()  {
   declare i newPATH
   shopt -s extglob
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//+(${@:${i}:1})*([^:]):/}" 
      #newPATH="${newPATH//+(${@:${i}:1})*([^:])+(:)/}" 
   done
   shopt -u extglob 
   export PATH="${newPATH%:}" 
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
Cyrill
la source
1

Si vous êtes préoccupé par la suppression des doublons dans $ PATH, le moyen le plus élégant, à mon humble avis, serait de ne pas les ajouter en premier lieu. En 1 ligne:

if ! $( echo "$PATH" | tr ":" "\n" | grep -qx "$folder" ) ; then PATH=$PATH:$folder ; fi

Le dossier $ peut être remplacé par n'importe quoi et peut contenir des espaces ("/ home / user / my documents")

MestreLion
la source
1

La solution pure bash la plus élégante que j'ai trouvée à ce jour:

pathrm () {                                                                      
  local IFS=':'                                                                  
  local newpath                                                                  
  local dir                                                                      
  local pathvar=${2:-PATH}                                                       
  for dir in ${!pathvar} ; do                                                    
    if [ "$dir" != "$1" ] ; then                                                 
      newpath=${newpath:+$newpath:}$dir                                          
    fi                                                                           
  done                                                                           
  export $pathvar="$newpath"                                                        
}

pathprepend () {                                                                 
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="$1${!pathvar:+:${!pathvar}}"                                  
}

pathappend () {                                                                    
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="${!pathvar:+${!pathvar}:}$1"                                  
} 
TriangleTodd
la source
1

La plupart des autres solutions proposées reposent uniquement sur la correspondance de chaîne et ne prennent pas en compte les segments de chemin contenant des noms spéciaux comme ., ..ou ~. La fonction bash ci-dessous résout les chaînes de répertoire dans son argument et dans les segments de chemin pour trouver des correspondances de répertoire logique ainsi que des correspondances de chaîne.

rm_from_path() {
  pattern="${1}"
  dir=''
  [ -d "${pattern}" ] && dir="$(cd ${pattern} && pwd)"  # resolve to absolute path

  new_path=''
  IFS0=${IFS}
  IFS=':'
  for segment in ${PATH}; do
    if [[ ${segment} == ${pattern} ]]; then             # string match
      continue
    elif [[ -n ${dir} && -d ${segment} ]]; then
      segment="$(cd ${segment} && pwd)"                 # resolve to absolute path
      if [[ ${segment} == ${dir} ]]; then               # logical directory match
        continue
      fi
    fi
    new_path="${new_path}${IFS}${segment}"
  done
  new_path="${new_path/#${IFS}/}"                       # remove leading colon, if any
  IFS=${IFS0}

  export PATH=${new_path}
}

Tester:

$ mkdir -p ~/foo/bar/baz ~/foo/bar/bif ~/foo/boo/bang
$ PATH0=${PATH}
$ PATH=~/foo/bar/baz/.././../boo/././../bar:${PATH}  # add dir with special names
$ rm_from_path ~/foo/boo/../bar/.  # remove same dir with different special names
$ [ ${PATH} == ${PATH0} ] && echo 'PASS' || echo 'FAIL'
jwfearn
la source
plus un pour l'extérieur
Martin York
1

Linux from Scratch définit trois fonctions Bash dans /etc/profile:

# Functions to help us manage paths.  Second argument is the name of the
# path variable to be modified (default: PATH)
pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
                if [ "$DIR" != "$1" ] ; then
                  NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
                fi
        done
        export $PATHVARIABLE="$NEWPATH"
}

pathprepend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
}

pathappend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
}

export -f pathremove pathprepend pathappend

Réf: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html

kevinarpe
la source
1

Je sais que cette question concerne BASH, ce que tout le monde devrait préférer, mais comme j'aime la symétrie et que parfois je suis obligé d'utiliser "csh", j'ai construit l'équivalent de "path_prepend ()", "path_append ()" et "path_remove () "solution élégante ci-dessus.

L'essentiel est que "csh" n'a pas de fonctions, donc j'ai mis de petits scripts shell dans mon répertoire bin personnel qui agissent comme les fonctions. Je crée des alias pour SOURCE ces scripts pour apporter les modifications de variable d'environnement désignées.

~ / bin / _path_remove.csh:

set _resolve = `eval echo $2`
setenv $1 `eval echo -n \$$1 | awk -v RS=: -v ORS=: '$1 != "'${_resolve}'"' | sed 's/:$//'`;
unset _resolve

~ / bin / _path_append.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_base}:${_resolve}
unset _base _resolve

~ / bin / _path_prepend.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_resolve}:${_base}
unset _base _resolve

~ / bin / .cshrc:


alias path_remove  "source ~/bin/_path_remove.csh  '\!:1' '\!:2'"
alias path_append  "source ~/bin/_path_append.csh  '\!:1' '\!:2'"
alias path_prepend "source ~/bin/_path_prepend.csh '\!:1' '\!:2'"

Vous pouvez les utiliser comme ça ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles
Lance ET Compte
la source
0

Étant donné que cela a tendance à être assez problématique, car il n'y a AUCUN moyen élégant, je recommande d'éviter le problème en réorganisant la solution: augmentez votre PATH plutôt que d'essayer de le démolir.

Je pourrais être plus précis si je connaissais votre véritable problème. En attendant, j'utiliserai une version logicielle comme contexte.

Un problème courant avec les versions logicielles est qu'il se brise sur certaines machines, en fin de compte en raison de la façon dont quelqu'un a configuré son shell par défaut (PATH et autres variables d'environnement). La solution élégante est de rendre vos scripts de construction immunisés en spécifiant entièrement l'environnement shell. Codez vos scripts de construction pour définir le PATH et d'autres variables d'environnement en fonction des éléments d'assemblage que vous contrôlez, tels que l'emplacement du compilateur, des bibliothèques, des outils, des composants, etc. Faites de chaque élément configurable quelque chose que vous pouvez définir, vérifier et puis utilisez de manière appropriée dans votre script.

Par exemple, j'ai une version Java ciblée WebLogic basée sur Maven dont j'ai hérité chez mon nouvel employeur. Le script de construction est connu pour être fragile, et un autre nouvel employé et moi avons passé trois semaines (pas à plein temps, juste ici et là, mais encore de nombreuses heures) à le faire fonctionner sur nos machines. Une étape essentielle a été de prendre le contrôle du PATH afin de savoir exactement quel Java, quel Maven et quel WebLogic était appelé. J'ai créé des variables d'environnement pour pointer vers chacun de ces outils, puis j'ai calculé le PATH en fonction de ceux-ci et de quelques autres. Des techniques similaires ont apprivoisé les autres paramètres configurables, jusqu'à ce que nous ayons finalement créé une version reproductible.

Au fait, n'utilisez pas Maven, Java est correct, et n'achetez WebLogic que si vous avez absolument besoin de son clustering (mais sinon non, et surtout pas de ses fonctionnalités propriétaires).

Meilleurs vœux.

Rob Williams
la source
Parfois, vous n'avez pas d'accès root et votre administrateur gère votre fichier PATH. Bien sûr, vous pouvez créer vous-même, mais chaque fois que votre administrateur déplace quelque chose, vous devez déterminer où il l'a mis. Cela va à l'encontre de l'objectif d'avoir un administrateur.
Shep
0

Comme avec @litb, j'ai répondu à la question " Comment manipuler les éléments $ PATH dans les scripts shell ", donc ma réponse principale est là.

La fonctionnalité «fractionnée» dans bashet d'autres dérivés du Bourne shell est obtenue de la manière la plus nette avec $IFSle séparateur inter-champs. Par exemple, pour définir les arguments de position ( $1, $2, ...) aux éléments de PATH, utilisez:

set -- $(IFS=":"; echo "$PATH")

Cela fonctionnera bien tant qu'il n'y aura pas d'espace dans $ PATH. Le faire fonctionner pour les éléments de chemin contenant des espaces est un exercice non trivial - laissé pour le lecteur intéressé. Il est probablement plus simple de le gérer en utilisant un langage de script tel que Perl.

J'ai aussi un script, clnpathque j'utilise beaucoup pour définir mon PATH. Je l'ai documenté dans la réponse à " Comment éviter de dupliquer la variable PATH dans csh ".

Jonathan Leffler
la source
IFS =: a = ($ CHEMIN); IFS = le fractionnement est également agréable. fonctionne s'ils contiennent également des espaces. mais alors vous avez un tableau, et devez jouer avec des boucles for et autres pour supprimer les noms.
Johannes Schaub - litb
Oui; cela devient fastidieux - comme avec mon commentaire mis à jour, il est probablement plus simple d'utiliser un langage de script à ce stade.
Jonathan Leffler
0

Ce qui rend ce problème ennuyeux, ce sont les cas de clôture parmi les premier et dernier éléments. Le problème peut être résolu avec élégance en changeant IFS et en utilisant un tableau, mais je ne sais pas comment réintroduire les deux points une fois que le chemin est converti en tableau.

Voici une version légèrement moins élégante qui supprime un répertoire de l' $PATHutilisation de la manipulation de chaînes uniquement. Je l'ai testé.

#!/bin/bash
#
#   remove_from_path dirname
#
#   removes $1 from user's $PATH

if [ $# -ne 1 ]; then
  echo "Usage: $0 pathname" 1>&2; exit 1;
fi

delendum="$1"
NEWPATH=
xxx="$IFS"
IFS=":"
for i in $PATH ; do
  IFS="$xxx"
  case "$i" in
    "$delendum") ;; # do nothing
    *) [ -z "$NEWPATH" ] && NEWPATH="$i" || NEWPATH="$NEWPATH:$i" ;;
  esac
done

PATH="$NEWPATH"
echo "$PATH"
Norman Ramsey
la source
0

Voici un one-liner Perl:

PATH=`perl -e '$a=shift;$_=$ENV{PATH};s#:$a(:)|^$a:|:$a$#$1#;print' /home/usr/bin`

La $avariable obtient le chemin à supprimer. Les commandes s(substitut) et printopèrent implicitement sur la $_variable.

JA Faucett
la source
0

Bon truc ici. J'utilise celui-ci pour éviter d'ajouter des dupes en premier lieu.

#!/bin/bash
#
######################################################################################
#
# Allows a list of additions to PATH with no dupes
# 
# Patch code below into your $HOME/.bashrc file or where it
# will be seen at login.
#
# Can also be made executable and run as-is.
#
######################################################################################

# add2path=($HOME/bin .)                  ## uncomment space separated list 
if [ $add2path ]; then                    ## skip if list empty or commented out
for nodup in ${add2path[*]}
do
    case $PATH in                 ## case block thanks to MIKE511
    $nodup:* | *:$nodup:* | *:$nodup ) ;;    ## if found, do nothing
    *) PATH=$PATH:$nodup          ## else, add it to end of PATH or
    esac                          ## *) PATH=$nodup:$PATH   prepend to front
done
export PATH
fi
## debug add2path
echo
echo " PATH == $PATH"
echo
ongoto
la source
1
Vous pouvez simplifier votre déclaration de cas en ajoutant un deux-points de case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
début
0

Avec le globbing étendu activé, il est possible d'effectuer les opérations suivantes:

# delete all /opt/local paths in PATH
shopt -s extglob 
printf "%s\n" "${PATH}" | tr ':' '\n' | nl
printf "%s\n" "${PATH//+(\/opt\/local\/)+([^:])?(:)/}" | tr ':' '\n' | nl 

man bash | less -p extglob
carlo
la source
0

Un-liner globbing étendu (enfin, en quelque sorte):

path_remove ()  { shopt -s extglob; PATH="${PATH//+(${1})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 

Il ne semble pas nécessaire d'échapper aux barres obliques dans $ 1.

path_remove ()  { shopt -s extglob; declare escArg="${1//\//\\/}"; PATH="${PATH//+(${escArg})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 
carlo
la source
0

En ajoutant des deux points à PATH, nous pourrions également faire quelque chose comme:

path_remove ()  { 
   declare i newPATH
   # put a colon at the beginning & end AND double each colon in-between
   newPATH=":${PATH//:/::}:"   
   for ((i=1; i<=${#@}; i++)); do
       #echo ${@:${i}:1}
       newPATH="${newPATH//:${@:${i}:1}:/}"   # s/:\/fullpath://g
   done
   newPATH="${newPATH//::/:}"
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 


path_remove_all ()  {
   declare i newPATH extglobVar
   extglobVar=0
   # enable extended globbing if necessary
   [[ ! $(shopt -q extglob) ]]  && { shopt -s extglob; extglobVar=1; }
   newPATH=":${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}"     # s/:\/path[^:]*//g
   done
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   # disable extended globbing if it was enabled in this function
   [[ $extglobVar -eq 1 ]] && shopt -u extglob
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
proxxy
la source
0

Dans path_remove_all (par proxxy):

-newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}" 
+newPATH="${newPATH//:${@:${i}:1}*([^:])/}"        # s/:\/path[^:]*//g 
marius
la source
0

Bien que ce soit un fil très ancien, j'ai pensé que cette solution pourrait être intéressante:

PATH="/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
REMOVE="ccache" # whole or part of a path :)
export PATH=$(IFS=':';p=($PATH);unset IFS;p=(${p[@]%%$REMOVE});IFS=':';echo "${p[*]}";unset IFS)
echo $PATH # outputs /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

trouvé sur ce billet de blog . Je pense que j'aime le plus celui-ci :)

mjc
la source
0

J'ai adopté une approche légèrement différente de celle de la plupart des gens ici et je me suis concentré spécifiquement sur la manipulation des cordes, comme ceci:

path_remove () {
    if [[ ":$PATH:" == *":$1:"* ]]; then
        local dirs=":$PATH:"
        dirs=${dirs/:$1:/:}
        export PATH="$(__path_clean $dirs)"
    fi
}
__path_clean () {
    local dirs=${1%?}
    echo ${dirs#?}
}

Ce qui précède est un exemple simplifié des fonctions finales que j'utilise. J'ai également créé path_add_beforeet path_add_aftervous permet d'insérer un chemin avant / après un chemin spécifié déjà dans PATH.

L'ensemble complet des fonctions est disponible dans path_helpers.sh dans mes dotfiles . Ils prennent entièrement en charge la suppression / l'ajout / l'ajout / l'insertion au début / au milieu / à la fin de la chaîne PATH.

jimeh
la source