¿Cuál es la forma más elegante de eliminar una ruta de la variable $ PATH en Bash?

114

O de manera más general, ¿cómo elimino un elemento de una lista separada por dos puntos en una variable de entorno Bash?

Pensé que había visto una forma sencilla de hacer esto hace años, utilizando las formas más avanzadas de expansión variable Bash, pero si es así, lo he perdido. Una búsqueda rápida en Google arrojó sorprendentemente pocos resultados relevantes y ninguno que yo llamaría "simple" o "elegante". Por ejemplo, dos métodos que usan sed y awk, respectivamente:

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)

¿No existe nada sencillo? ¿Hay algo análogo a una función split () en Bash?

Actualización:
Parece que necesito disculparme por mi pregunta intencionalmente vaga; Estaba menos interesado en resolver un caso de uso específico que en provocar una buena discusión. ¡Afortunadamente lo conseguí!

Aquí hay algunas técnicas muy inteligentes. Al final, agregué las siguientes tres funciones a mi caja de herramientas. La magia ocurre en path_remove, que se basa en gran medida en el uso inteligente de awkla variable RS de Martin York .

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/:$//'`; }

El único problema real que hay es el uso de sedpara eliminar el colon posterior. Sin embargo, teniendo en cuenta lo sencillo que es el resto de la solución de Martin, ¡estoy bastante dispuesto a vivir con ella!


Pregunta relacionada: ¿Cómo manipulo los elementos $ PATH en los scripts de shell?

Ben Blank
fuente
Para cualquier variable: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"pero debe llamarlo como func $VAR VAR pattern(basado en @ martin-york y @ andrew-aylett)
vesperto

Respuestas:

51

Un minuto con awk:

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

Editar: responde a los comentarios a continuación:

$ 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!

Editar en respuesta a un problema de seguridad: (eso no es relevante para la pregunta)

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

Esto elimina los dos puntos finales que quedan al eliminar las últimas entradas, lo que efectivamente se agregaría .a su ruta.

Martin York
fuente
1
Falla al intentar eliminar el último elemento o varios elementos: en el primer caso, agrega el directorio actual (como la cadena vacía; un potencial agujero de seguridad), en el segundo caso agrega `` como elemento de ruta.
Fred Foo
1
@larsmans: Funciona bien para mí. Nota: Vacío no es lo mismo que el directorio actual que es "./"
Martin York
2
Una cadena vacía como un "miembro" de la PATHvariable de hace , como una norma especial, denotan directorio actual en todas las cáscaras de Unix desde al menos V7 Unix de 1979. Todavía hace en bash. Consulte el manual o pruébelo usted mismo.
Fred Foo
1
@Martin: POSIX no requiere este comportamiento, pero lo documenta y lo permite: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo
1
Hay un problema aún más sutil al eliminar el último elemento con esto: awk parece agregar un byte nulo al final de la cadena . Lo que significa que si agrega otro directorio a PATH más tarde, de hecho no se buscará.
sschuberth
55

Mi sucio truco:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Martin York
fuente
18
Nunca es una buena señal cuando la solución más obvia es de -Automatice el proceso. :-D
Ben Blank
Mucho más seguro que las alternativas "elegantes".
Jortstek
44

Dado que el gran problema con la sustitución son los casos finales, ¿qué tal si los casos finales no son diferentes de los otros casos? Si la ruta ya tiene dos puntos al principio y al final, simplemente podríamos buscar la cadena deseada envuelta con dos puntos. Tal como está, podemos agregar fácilmente esos dos puntos y eliminarlos después.

# 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
fuente
2
Agregaría
Cyber ​​Oliveira
1
Usé esto porque parecía la solución más simple. Fue súper rápido y fácil, y puede verificar fácilmente su trabajo con echo $ WORK justo antes de la última línea donde realmente cambia la variable PATH.
Phil Gran
2
Absolutamente una pequeña joya. Exactamente lo que estaba tratando de hacer cuando encontré esta publicación. -¡Gracias Andrew! Por cierto: tal vez le gustaría agregar comillas dobles alrededor de ": $ PATH:", por si acaso debería contener espacios (lo mismo sobre "/ usr / bin") y la última línea "$ WORK".
2
Gracias, @ PacMan-- :). Estoy bastante seguro (acabo de probarlo) de que no necesita espacios para las asignaciones WORKy, PATHya que la expansión de la variable ocurre después de que la línea se analiza en secciones para la asignación de variables y la ejecución de comandos. REMOVEes posible que deba citarse, o simplemente puede colocar su cadena directamente en el reemplazo si es una constante.
Andrew Aylett
Siempre me confundía cuándo se conservaban las cadenas, por lo que siempre comencé a comillas dobles. Gracias nuevamente por aclarar esto. :)
26

Aquí está la solución más simple que puedo idear:

#!/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[*]}"

El ejemplo anterior eliminará cualquier elemento en $ PATH que contenga "usr". Puede reemplazar "* usr *" con "/ home / user / bin" para eliminar solo ese elemento.

actualizar por sschuberth

Aunque creo que los espacios en un $PATHson una idea horrible , aquí hay una solución que lo maneja:

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[*]}");

o

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
fuente
2
Como una línea: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
nicerobot
1
Esta solución no funciona con rutas en PATH que contienen espacios; los reemplaza por dos puntos.
sschuberth
11

Aquí hay un resumen que, a pesar de las respuestas actualmente aceptadas y mejor calificadas , no agrega caracteres invisibles a PATH y puede hacer frente a rutas que contienen espacios:

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

Personalmente, también encuentro esto fácil de leer / entender, y solo involucra comandos comunes en lugar de usar awk.

sschuberth
fuente
2
... y si desea algo que pueda hacer frente incluso a las nuevas líneas en los nombres de archivo, puede usar esto: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (aunque podría decirse, es posible que desee preguntarse por qué necesita esto, si lo necesita :))
ehdr
Esto eliminará las coincidencias parciales, que probablemente no sea lo que desea; Me gustaría utilizar grep -v "^/path/to/remove\$"ogrep -v -x "/path/to/remove"
ShadSterling
Buena solución, pero ¿realmente crees que tres más común que awk? ;)
K.-Michael Aye
1
Absolutamente. Los entornos livianos, como Git Bash en Windows, vienen con una herramienta simple en trlugar de un intérprete awk.
sschuberth
1
@ehdr: debe reemplazarlo echo "..."con printf "%s" "..."para que funcione en rutas -esimilares. Ver stackoverflow.com/a/49418406/102441
Eric
8

Aquí hay una solución que:

  • es puro Bash,
  • no invoca otros procesos (como 'sed' o 'awk'),
  • no cambia IFS,
  • no bifurca una sub-capa,
  • maneja caminos con espacios, y
  • elimina todas las apariciones del argumento en PATH.

    removeFromPath () {
       pd local
       p = ": $ 1:"
       d = ": $ RUTA:"
       d = $ {d // $ p /:}
       d = $ {d / #: /}
       RUTA = $ {d /%: /}
    }
robinbb
fuente
4
Me gusta esta solucion ¿Quizás hacer que los nombres de las variables sean más descriptivos?
Anukool
muy agradable. Sin embargo, no funciona para los segmentos de ruta que contienen un asterisco (*). A veces llegan accidentalmente.
Jörg
6

función __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && RUTA = "$ {D /: $ 1: /:}";
RUTA = "$ {RUTA / #: /}";
export PATH = "$ {PATH /%: /}";
}

Lo saqué de mi archivo .bashrc. Cuando juegas con PATH y se pierde, awk / sed / grep deja de estar disponible :-)

GreenFox
fuente
1
Ese es un muy buen punto. (Nunca me gustó ejecutar utilidades externas para cosas simples como esta).
6

La mejor opción de bash puro que he encontrado hasta ahora es la siguiente:

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
}

Esto se basa en la respuesta no del todo correcta para Agregar directorio a $ PATH si aún no está en Superusuario.

Mark Booth
fuente
Esto también es bastante bueno. Lo probé. Si hay una ruta duplicada (por ejemplo, dos que son exactamente iguales) en PATH, solo se elimina una de ellas. También puede convertirlo en una sola línea:removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }
Esta solución falla cuando $PATHcontiene una subcarpeta de la ruta de destino (es decir, que se eliminará). Por ejemplo: a:abc/def/bin:b-> a/bin:b, cuándo abc/defse eliminará.
Robin Hsu
5

Acabo de usar las funciones en la distribución bash, que aparentemente han estado ahí desde 1991. Estas todavía están en el paquete bash-docs en Fedora, y solían usarse /etc/profile, pero ya no ...

$ 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;:\$;;"`
}
Sr. loco
fuente
4

Escribí una respuesta a esto aquí (usando awk también). ¿Pero no estoy seguro de que sea eso lo que estás buscando? Al menos me parece claro lo que hace, en lugar de intentar encajar en una línea. Sin embargo, para un delineador simple que solo elimina cosas, recomiendo

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

Reemplazar es

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

o (más corto pero menos legible)

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

De todos modos, para la misma pregunta y muchas respuestas útiles, consulte aquí .

Johannes Schaub - litb
fuente
Y si desea eliminar una línea que contiene una cadena parcial, utilice awk '$0 !~ "/bin"'. Es decir, mantenga las líneas que no contengan '/ bin' con el operador awk !~.
thoni56
3

Bueno, en bash, ya que admite expresiones regulares, simplemente haría:

PATH=${PATH/:\/home\/user\/bin/}
estera
fuente
¿No es solo expansión de nombre de ruta, no expresiones regulares?
dreamlax
2
Si bien bash admite expresiones regulares (a partir de bash 3), esto no es un ejemplo, es una sustitución de variable.
Robert Gamble
4
Esta es la expansión de la variable de patrón y la solución tiene varios problemas. 1) no coincidirá con el primer elemento. 2) coincidirá con cualquier cosa que comience con "/ home / user / bin", no solo con "/ home / user / bin". 3) requiere escapar de caracteres especiales. En el mejor de los casos, diría que este es un ejemplo incompleto.
nicerobot
2

Me gustan las tres funciones que se muestran en la actualización de @ BenBlank a su pregunta original. Para generalizarlos, utilizo una forma de 2 argumentos, lo que me permite establecer PATH o cualquier otra variable de entorno que desee:

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/:$//'`"; }

Ejemplos de uso:

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

Tenga en cuenta que también agregué algunas comillas para permitir el procesamiento adecuado de los nombres de ruta que contienen espacios.

Cary Millsap
fuente
2

¿Cuál es la forma más elegante de eliminar una ruta de la variable $ PATH en Bash?

¿Qué es más elegante que awk?

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

¡Pitón! Es una solución más legible y fácil de mantener, y es fácil de inspeccionar para ver que realmente está haciendo lo que quieres.

Digamos que desea eliminar el primer elemento de ruta.

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

(En lugar de canalizar desde echo, os.getenv['PATH']sería un poco más corto y proporcionaría el mismo resultado que el anterior, pero me preocupa que Python pueda hacer algo con esa variable de entorno, por lo que probablemente sea mejor canalizarlo directamente desde el entorno que le interesa. .)

De manera similar para eliminar del final:

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

Para hacer estas funciones de shell reutilizables que puede, por ejemplo, pegar en su archivo .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))")"
}
Aaron Hall
fuente
1

Sí, poner dos puntos al final de PATH, por ejemplo, hace que eliminar una ruta sea un poco menos torpe y propenso a errores.

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
fuente
1

Si le preocupa eliminar duplicados en $ PATH, la forma más elegante, en mi humilde opinión, sería no agregarlos en primer lugar. En 1 línea:

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

$ carpeta puede ser reemplazada por cualquier cosa y puede contener espacios ("/ inicio / usuario / mis documentos")

MestreLion
fuente
1

La solución bash pura más elegante que he encontrado hasta la fecha:

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"                                  
} 
TriánguloTodd
fuente
1

La mayor parte de las otras soluciones propuestas se basan únicamente en la coincidencia de cadenas y no toman en cuenta los segmentos de trazado que contienen nombres especiales como ., ..o ~. La función bash siguiente resuelve cadenas de directorio en su argumento y en segmentos de ruta para encontrar coincidencias de directorio lógicas así como coincidencias de cadenas.

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

Prueba:

$ 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
fuente
más uno por fuera de la caja
Martin York
1

Linux from Scratch define tres funciones Bash en /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

Ref: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html

kevinarpe
fuente
1

Sé que esta pregunta se refiere a BASH, que todos deberían preferir, pero como disfruto de la simetría y, a veces, debo usar "csh", construí el equivalente a "path_prepend ()", "path_append ()" y "path_remove () "elegante solución anterior.

La esencia es que "csh" no tiene funciones, así que pongo pequeños scripts de shell en mi directorio bin personal que actúan como las funciones. Creo alias a FUENTE esos scripts para hacer los cambios de variable de entorno designados.

~ / 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'"

Puedes usarlos así ...

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

Dado que esto tiende a ser bastante problemático, ya que NO HAY una forma elegante, recomiendo evitar el problema reorganizando la solución: construya su PATH en lugar de intentar derribarlo.

Podría ser más específico si supiera el contexto real de su problema. Mientras tanto, usaré una compilación de software como contexto.

Un problema común con las compilaciones de software es que se rompe en algunas máquinas, en última instancia debido a cómo alguien ha configurado su shell predeterminado (PATH y otras variables de entorno). La solución elegante es hacer que sus scripts de compilación sean inmunes especificando completamente el entorno de shell. Codifique sus scripts de compilación para establecer la RUTA y otras variables de entorno en función de las piezas de ensamblaje que controle, como la ubicación del compilador, bibliotecas, herramientas, componentes, etc. Haga que cada elemento configurable sea algo que pueda configurar, verificar y luego utilícelo de manera apropiada en su guión.

Por ejemplo, tengo una compilación de Java dirigida a WebLogic basada en Maven que heredé de mi nuevo empleador. El script de compilación es conocido por ser frágil, y otro empleado nuevo y yo pasamos tres semanas (no a tiempo completo, solo aquí y allá, pero todavía muchas horas) haciendo que funcione en nuestras máquinas. Un paso esencial fue que tomé el control de PATH para saber exactamente qué Java, qué Maven y qué WebLogic se estaba invocando. Creé variables de entorno para señalar cada una de esas herramientas, luego calculé la RUTA en función de esas y algunas otras. Técnicas similares domesticaron las otras configuraciones configurables, hasta que finalmente creamos una compilación reproducible.

Por cierto, no use Maven, Java está bien y solo compre WebLogic si absolutamente necesita su agrupación en clústeres (pero por lo demás no, y especialmente no sus características propietarias).

Los mejores deseos.

Rob Williams
fuente
a veces no tiene acceso de root y su administrador administra su PATH. Claro, podría construir uno propio, pero cada vez que su administrador mueva algo, debe averiguar dónde lo puso. Eso frustra el propósito de tener un administrador.
Shep
0

Al igual que con @litb, contribuí con una respuesta a la pregunta " ¿Cómo manipulo los elementos $ PATH en scripts de shell? ", Así que mi respuesta principal está ahí.

La funcionalidad 'dividir' en bashy otros derivados del shell Bourne se logra de la manera más prolija con $IFSel separador entre campos. Por ejemplo, para establecer los argumentos posicionales ( $1, $2, ...) a los elementos de PATH, uso:

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

Funcionará bien siempre que no haya espacios en $ PATH. Hacer que funcione para elementos de ruta que contienen espacios no es un ejercicio trivial, dejado para el lector interesado. Probablemente sea más sencillo tratar con un lenguaje de programación como Perl.

También tengo un script, clnpathque uso mucho para configurar mi PATH. Lo documenté en la respuesta a " Cómo evitar duplicar la variable PATH en csh ".

Jonathan Leffler
fuente
IFS =: a = ($ RUTA); IFS = dividir también es bueno. funciona si también contienen espacios. pero luego tienes una matriz, y tienes que jugar con bucles for y demás para eliminar los nombres.
Johannes Schaub - litb
Si; se vuelve complicado, al igual que con mi comentario actualizado, probablemente sea más sencillo usar un lenguaje de secuencias de comandos en este punto.
Jonathan Leffler
0

Lo que hace que este problema sea molesto son los casos de poste de cerca entre el primer y el último elemento. El problema se puede resolver elegantemente cambiando IFS y usando una matriz, pero no sé cómo volver a introducir los dos puntos una vez que la ruta se convierte en forma de matriz.

Aquí hay una versión un poco menos elegante que elimina un directorio del $PATHuso exclusivo de la manipulación de cadenas. Lo he probado.

#!/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
fuente
0

Aquí hay un resumen de Perl:

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

La $avariable obtiene la ruta a eliminar. Los comandos s(sustitutos) y printoperan implícitamente en la $_variable.

JA Faucett
fuente
0

Buenas cosas aquí. Yo uso este para evitar agregar incautos en primer lugar.

#!/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
fuente
1
Puede simplificar la declaración de su caso agregando dos puntos al principio y al final a la cadena PATH:case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
glenn jackman
0

Con el globbing extendido habilitado, es posible hacer lo siguiente:

# 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
fuente
0

Una sola línea de globbing extendido (bueno, más o menos):

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

Parece que no hay necesidad de escapar de las barras en $ 1.

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

Añadiendo dos puntos a PATH también podríamos hacer algo como:

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 
apoderado
fuente
0

En path_remove_all (por proxy):

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

Si bien este es un hilo muy antiguo, pensé que esta solución podría ser de interés:

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

lo encontré en esta publicación de blog . Creo que este es el que más me gusta :)

mjc
fuente
0

Adopté un enfoque ligeramente diferente al de la mayoría de las personas aquí y me centré específicamente en la manipulación de cuerdas, así:

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#?}
}

Lo anterior es un ejemplo simplificado de las funciones finales que uso. También he creado path_add_beforeypath_add_after permitiéndote insertar una ruta antes / después de una ruta especificada que ya está en PATH.

El conjunto completo de funciones está disponible en path_helpers.sh en mis archivos de puntos . Son totalmente compatibles con la eliminación / adición / anteposición / inserción al principio / medio / final de la cadena PATH.

jimeh
fuente