Agregue el directorio a $ PATH si aún no está allí


126

¿Alguien ha escrito una función bash para agregar un directorio a $ PATH solo si aún no está allí?

Normalmente agrego a PATH usando algo como:

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

Si construyo mi RUTA en .bash_profile, entonces no se lee a menos que la sesión en la que estoy sea una sesión de inicio de sesión, lo que no siempre es cierto. Si construyo mi RUTA en .bashrc, entonces se ejecuta con cada subshell. Entonces, si inicio una ventana de Terminal y luego ejecuto la pantalla y luego ejecuto un script de shell, obtengo:

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

Intentaré construir una función bash llamada add_to_path()que solo agrega el directorio si no está allí. Pero, si alguien ya ha escrito (o encontrado) algo así, no pasaré el tiempo en ello.


Consulte stackoverflow.com/questions/273909/… para obtener información sobre la infraestructura que puede ayudar.
dmckee


Si enmarca el problema como "solo agregando si aún no está allí", se sorprenderá bruscamente cuando llegue el día en que sea importante que el elemento insertado esté al principio pero no termine allí. Un mejor enfoque sería insertar el elemento y luego eliminar los duplicados, por lo que si la nueva entrada ya estaba allí, se moverá efectivamente al principio.
Don Hatch

Respuestas:


125

De mi .bashrc:

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

Tenga en cuenta que PATH ya debería estar marcado como exportado, por lo que no es necesario volver a exportar. Esto verifica si el directorio existe y es un directorio antes de agregarlo, lo cual puede no importarle.

Además, esto agrega el nuevo directorio al final de la ruta; para poner al principio, use en PATH="$1${PATH:+":$PATH"}"lugar de la PATH=línea anterior .


26
Me importa.
Dennis Williamson

44
@Neil: Funciona, porque se compara en ":$PATH:"lugar de solo"$PATH"
Gordon Davisson

3
@GordonDavisson: Pido disculpas, mi prueba fue incorrecta y estás en lo correcto.
Neil

2
@GordonDavisson ¿Cuál es el punto de las cosas entre llaves? Parece que no puedo descifrarlo " ${PATH:+"$PATH:"}$ 1"
boatcoder

55
@ Mark0978: Eso es lo que hice para solucionar el problema que señaló bukzor. ${variable:+value}significa verificar si variableestá definido y tiene un valor no vacío, y si lo hace da el resultado de la evaluación value. Básicamente, si PATH no está en blanco para empezar, lo establece en "$PATH:$1"; si está en blanco, lo establece en solo "$1"(tenga en cuenta la falta de dos puntos).
Gordon Davisson

19

Ampliando la respuesta de Gordon Davisson, esto admite múltiples argumentos

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

Entonces puedes hacer pathappend ruta1 ruta2 ruta3 ...

Por anteponer,

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

Similar a pathappend, puedes hacer

pathprepend ruta1 ruta2 ruta3 ...


3
¡Esto es genial! Hice un pequeño cambio. Para la función 'pathprepend', es conveniente leer los argumentos al revés, por lo que puede decir, por ejemplo, pathprepend P1 P2 P3y terminar con PATH=P1:P2:P3. Para obtener este comportamiento, cambie for ARG in "$@" doafor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael

Gracias @ishmael, buena sugerencia, edité la respuesta. Me doy cuenta de que tu comentario tiene más de dos años, pero no he vuelto desde entonces. ¡Tengo que descubrir cómo hacer que los correos electrónicos de intercambio de pila lleguen a mi bandeja de entrada!
Guillaume Perrault-Archambault

14

Aquí hay algo de mi respuesta a esta pregunta combinada con la estructura de la función de Doug Harris. Utiliza expresiones regulares de Bash:

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

Esto funcionó para mí solo usando en $1lugar de${1}
Andrei

@Andrei: Sí, las llaves no son necesarias en este caso. No estoy seguro de por qué los incluí.
Dennis Williamson

10

Ponga esto en los comentarios a la respuesta seleccionada, pero los comentarios no parecen admitir el formato PRE, por lo tanto, agregue la respuesta aquí:

@ Gordon-Davisson No soy un gran fanático de las citas y concatenaciones innecesarias. Suponiendo que está utilizando una versión bash> = 3, en su lugar, puede usar expresiones regulares integradas de bash y hacer:

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

Esto maneja correctamente los casos en los que hay espacios en el directorio o la RUTA. Hay algunas dudas sobre si el motor de expresiones regulares integrado de bash es lo suficientemente lento como para que esto sea menos eficiente que la concatenación e interpolación de cadenas que su versión, pero de alguna manera me parece más estéticamente limpio.


1
Los comentarios formatting using the backticksolo son compatibles, pero no obtienes ningún control de párrafo decente.
Boatcoder

Esto pone la adición al final. A menudo es deseable agregar al principio para anular las ubicaciones existentes.
Dennis Williamson

@DennisWilliamson Ese es un punto justo, aunque no lo recomendaría como el comportamiento predeterminado. No es difícil descubrir cómo cambiar para un prepend.
Christopher Smith

@ChristopherSmith - re: unnecessary quotingimplica que sabe de antemano que $PATHno es nulo. "$PATH"hace que sea correcto si PATH es nulo o no. Del mismo modo, si $1contiene caracteres que pueden confundir el analizador de comandos. Poner la expresión regular entre comillas "(^|:)$1(:|$)"evita eso.
Jesse Chisholm

@JesseChisholm: En realidad, creo que el punto de Christopher es que las reglas son diferentes entre [[y ]]. Yo prefiero citar todo lo que pueda necesitar para ser citado, a menos que lo causa a fallar, pero creo que tiene razón, y que las comillas no son realmente necesarios alrededor $PATH. Por otro lado, me parece que tienes razón $1.
Scott

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

Cuando necesite que $ HOME / bin aparezca exactamente una vez al comienzo de su $ PATH y en ningún otro lugar, no acepte sustitutos.


Gracias, es una solución elegante y agradable, pero descubrí que tenía que hacer PATH=${PATH/"... en lugar de PATH=${PATH//"... para que funcionara.
Mark Booth

La forma de doble barra debe coincidir con cualquier número de coincidencias; la barra simple solo coincide con la primera (busque "Sustitución de patrones" en la página del manual de bash). No sé por qué no funcionó ...
andybuckley

Esto falla en el caso inusual de que $1es la única entrada (sin dos puntos). La entrada se duplica.
Dennis Williamson

También se elimina de forma demasiado agresiva como lo señala PeterS6g .
Dennis Williamson

6

Aquí hay una solución alternativa que tiene la ventaja adicional de eliminar entradas redundantes:

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

El argumento único para esta función se antepone a la RUTA, y la primera instancia de la misma cadena se elimina de la ruta existente. En otras palabras, si el directorio ya existe en la ruta, se promueve al frente en lugar de agregarse como un duplicado.

La función funciona al anteponer dos puntos a la ruta para garantizar que todas las entradas tengan dos puntos al frente, y luego anteponer la nueva entrada a la ruta existente con esa entrada eliminada. La última parte se realiza utilizando la ${var//pattern/sub}notación de bash ; vea el manual de bash para más detalles.


Buen pensamiento; implementación defectuosa. Considera lo que sucede si ya tienes /home/roberten tu PATHy en ti pathadd /home/rob.
Scott

5

Aquí está el mío (creo que fue escrito hace años por Oscar, el administrador del sistema de mi antiguo laboratorio, todo el crédito para él), ha estado en mi bashrc durante siglos. Tiene el beneficio adicional de permitirle anteponer o agregar el nuevo directorio como desee:

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/

5

Para anteponer, me gusta la solución de @ Russell, pero hay un pequeño error: si intenta anteponer algo como "/ bin" a una ruta de "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "reemplaza" / bin: "3 veces (cuando realmente no coincide en absoluto). Combinando una solución para eso con la solución anexa de @ gordon-davisson, obtengo esto:

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
}

4

Un simple alias como este a continuación debería hacer el truco:

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

Todo lo que hace es dividir la ruta en el carácter: y comparar cada componente con el argumento que pasa. Grep busca una coincidencia de línea completa e imprime el recuento.

Uso de la muestra:

$ 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

Reemplace el comando echo con addToPath o algún alias / función similar.


Usar "grep -x" es probablemente más rápido que el bucle que puse en mi función bash.
Doug Harris


2

Esto es lo que preparé:

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
}

Ahora en .bashrc tengo:

add_to_path /usr/local/mysql/bin

Versión actualizada después del comentario sobre cómo mi original no manejará directorios con espacios (gracias a esta pregunta por indicarme que use 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
}

1
Esto puede fallar si cualquier nombre de directorio ya en PATHcontiene espacios en blanco, *, ?, o [... ].
Scott

Buen punto ... pero soy un chico de la vieja escuela de Linux y nunca usaría un camino con espacios en blanco :-)
Doug Harris

Bien por ti, por no crear cosas con espacios en blanco en sus nombres. Que pena por escribir código que no puede manejarlos cuando existen. ¿Y qué tiene que ver ser "un chico de Linux de la vieja escuela"? Windoze puede haber popularizado la idea (gracias Documents and Settingsy Program Files), pero Unix ha admitido nombres de ruta que contienen espacios en blanco desde antes de que existiera Windoze.
Scott

2

Estoy un poco sorprendido de que nadie haya mencionado esto todavía, pero puede usarlo readlink -fpara convertir rutas relativas en rutas absolutas y agregarlas a la RUTA como tal.

Por ejemplo, para mejorar la respuesta de Guillaume Perrault-Archambault,

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

se convierte

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
}

1. Lo básico: ¿de qué sirve esto?

El readlink -fcomando convertirá (entre otras cosas) una ruta relativa en una ruta absoluta. Esto te permite hacer algo como

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

2. ¿Por qué hacemos pruebas para estar en PATH dos veces?

Bueno, considere el ejemplo anterior. Si el usuario dice desde el directorio por segunda vez, será . Por supuesto, no estará presente en . Pero luego se establecerá en (el equivalente de ruta absoluta de ), que ya está en . Por lo tanto, debemos evitar agregar a una segunda vez.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Quizás lo más importante, el propósito principal de readlinkes, como su nombre lo indica, mirar un enlace simbólico y leer el nombre de ruta que contiene (es decir, apunta a). Por ejemplo:

$ 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

Ahora, si dices pathappend /usr/lib/perl/5.14, y ya tienes /usr/lib/perl/5.14en tu RUTA, bueno, está bien; podemos dejarlo como está. Pero, si /usr/lib/perl/5.14no está ya en su PATH, llamamos readlinky nos ARGA= /usr/lib/perl/5.14.2, y luego añadimos que a PATH. Pero espere un minuto: si ya lo dijo pathappend /usr/lib/perl/5.14, ya lo tiene /usr/lib/perl/5.14.2en su RUTA y, nuevamente, debemos verificarlo para evitar agregarlo por PATHsegunda vez.

3. ¿Cuál es el trato con if ARGA=$(readlink -f "$ARG")?

En caso de que no esté claro, esta línea prueba si readlinktiene éxito. Esto es simplemente una buena práctica de programación defensiva. Si vamos a utilizar la salida del comando  m como parte del comando  n (donde m  <  n ), es prudente verificar si el comando  m falló y manejarlo de alguna manera. No creo que sea probable que readlinkfalle, pero, como se discutió en Cómo recuperar la ruta absoluta de un archivo arbitrario desde OS X y en otros lugares, readlinkes una invención de GNU. No se especifica en POSIX, por lo que su disponibilidad en Mac OS, Solaris y otros Unix no Linux es cuestionable. (De hecho, acabo de leer un comentario que dice "readlink -fno parece funcionar en Mac OS X 10.11.6, pero realpathfunciona fuera de la caja ". Entonces, si está en un sistema que no tiene readlink, o donde readlink -fno funciona, puede modificar esto script para usar realpath.) Al instalar una red de seguridad, hacemos que nuestro código sea algo más portátil.

Por supuesto, si está en un sistema que no tiene readlink(o  realpath), no querrá hacerlo .pathappend .

La segunda -dprueba ( [ -d "$ARGA" ]) probablemente sea realmente innecesaria. No puedo pensar en ningún escenario donde $ARGsea ​​un directorio y readlinktenga éxito, pero  $ARGAno es un directorio. Simplemente copié y pegué la primera ifdeclaración para crear la tercera, y dejé la  -dprueba allí por flojera.

4. ¿Algún otro comentario?

Sí. Como muchas de las otras respuestas aquí, esta prueba si cada argumento es un directorio, lo procesa si lo es, y lo ignora si no lo es. Esto puede (o no) ser adecuado si está utilizando pathappend solo en " ." archivos (como .bash_profiley .bashrc) y otros scripts. Pero, como mostró esta respuesta (arriba), es perfectamente factible usarlo de forma interactiva. Estarás muy perplejo si lo haces

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

¿Te diste cuenta de que dije nysql en el pathappendcomando, en lugar de mysql? Y eso pathappendno dijo nada; ¿simplemente ignoró en silencio el argumento incorrecto?

Como dije anteriormente, es una buena práctica manejar los errores. Aquí hay un ejemplo:

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
}

(1) Se deben agregar las cotizaciones: readlink -f "$ARG". (2) No sé por qué sucedería (especialmente después de que la -d "$ARG"prueba tuvo éxito), pero es posible que desee probar si readlinkfalló. (3) Parece que está pasando por alto readlinkla función principal: asignar un nombre de enlace simbólico a un "nombre de archivo real". Si (por ejemplo) /bines un enlace simbólico a /bin64, entonces las llamadas repetidas a pathappend /binpodrían dar lugar a la palabra PATH …:/bin64:/bin64:/bin64:/bin64:…. Probablemente debería (también) verificar si el nuevo valor de $ARGya está en PATH.
Scott

(1) Buena observación, lo arreglé. (2) ¿en qué caso fallaría el enlace de lectura? Suponiendo que una ruta es correcta y se refiere a una ubicación válida. (3) No estoy seguro de lo que dicta la función principal de readlink, creo que la mayoría de las rutas (¿si no todas?) En un sistema de archivos unix se pueden dividir en 2 tipos de enlaces, enlaces simbólicos y enlaces duros. En cuanto a la entrada de ruta duplicada, tiene razón, pero el propósito de mi respuesta no fue agregar eso (como he notado que otras respuestas ya lo han mencionado). La única razón por la que estoy agregando otra respuesta es para contribuir con algo que pensé que ya no había contribuido
qwertyzw

(2) Quiero decir, si al menos el nombre del comando implicaba / insinuaba su propósito, entonces 'enlace' en readlink puede referirse a enlaces duros o blandos. Sin embargo, tiene razón: man readlink dice 'readlink - imprimir enlaces simbólicos resueltos o nombres de archivos canónicos', que .en mi ejemplo creo que puede considerarse como un nombre de archivo canónico. ¿Correcto?
qwertyzw

(1) Para las personas que entienden los enlaces simbólicos, readlinkel nombre claramente implica su propósito: mira un enlace simbólico y lee el nombre de la ruta que contiene (es decir, señala). (2) Bueno, dije que no sabía por readlinkqué fallaría. Estaba señalando que si un script o función contiene múltiples comandos, y se garantiza que el comando  n fallará catastróficamente (o no tiene ningún sentido) si el comando  m falló (donde m  <  n ), es una buena práctica prudente compruebe si el comando m falló y maneje eso de alguna manera, como mínimo, ... (Continúa)
Scott

(Cont.) ... aborta el script / función con un diagnóstico. Hipotéticamente, readlinkpodría fallar si (a) el directorio fue renombrado o eliminado (por otro proceso) entre sus llamadas a testy readlink, o (b) si /usr/bin/readlinkfue eliminado (o dañado). (3) Parece que te estás perdiendo mi punto. No te estoy animando a duplicar otras respuestas; Estoy diciendo que, al verificar si el original ARG(desde la línea de comando) ya está en PATH, pero no repetir la verificación para el nuevo ARG(la salida de readlink), su respuesta es incompleta y, por lo tanto, incorrecta. … (Continúa)
Scott

1
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  

1

De esta manera funciona bien:

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

0

Mis versiones tienen menos cuidado con las rutas vacías e insisten en que las rutas sean válidas y directorios que algunas publicadas aquí, pero encuentro una gran colección de prepend / append / clean / unique-ify / etc. las funciones de shell son útiles para la manipulación de rutas. Todo el lote, en su estado actual, está aquí: http://pastebin.com/xS9sgQsX (¡comentarios y mejoras muy bienvenidos!)


0

Puedes usar un perl one liner:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

Aquí está 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
}

0

Modifiqué ligeramente la respuesta de Gordon Davisson para usar el directorio actual si no se proporciona ninguno. Entonces, puede hacerlo padddesde el directorio que desea agregar a su RUTA.

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}

0

Puede verificar si se ha configurado una variable personalizada; de lo contrario, configúrela y luego agregue las nuevas entradas:

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

Por supuesto, estas entradas aún podrían duplicarse si las agrega otro script, como /etc/profile.


0

Este script le permite agregar al final de $PATH:

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

O agregue al principio 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
}

0

Aquí hay una manera compatible con 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"
        }
}

Se cribbed de Guillaume Perrault-Archambault 's respuesta a esta pregunta y mike511 ' s respuesta aquí .

ACTUALIZACIÓN 2017-11-23: Error corregido por @Scott


Iba a votar esto por proporcionar una opción de línea de comandos para elegir entre prepending y postpending (anexar), con un valor predeterminado. Pero luego pensé: este es un montón de código críptico sin explicación. (Y el hecho de que tenga dos funciones, donde una cambia PATH y hace eco de su nuevo valor, y la otra captura esa salida y la asigna nuevamente a PATH , es solo una complejidad innecesaria.) ... (Continúa)
Scott,

(Continúa) ... Y luego noté que los enlaces estaban mal. (Y no estoy seguro de por qué estás culpando a esos tipos; no parece que hayas copiado mucho de sus respuestas). Y luego noté que el código estaba equivocado. Creo que hace un trabajo bien de mantener un camino bien formada, pero no hay garantía de que es ya bien formado, sobre todo si tiene un ignorante /etc/profile. Es posible que el directorio que está intentando agregar a PATH ya esté allí más de una vez , y su código se ahoga con eso. … (Continúa)
Scott

(Continuación) ... Por ejemplo, si intenta anteponer /a/cka /b:/a/ck:/tr:/a/ck, se obtiene /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.