unix - example - ¿Cómo manipulo los elementos $ PATH en scripts de shell?
shell script linux pdf (11)
Abordar la solución propuesta de dmckee:
- Mientras que algunas versiones de Bash pueden permitir guiones en nombres de funciones, otras (MacOS X) no lo hacen.
- No veo la necesidad de utilizar el retorno inmediatamente antes del final de la función.
- No veo la necesidad de todos los puntos y comas.
- No veo por qué tiene un valor de ruta-elemento-por-patrón de exportación. Piense en
export
como equivalente a establecer (o incluso crear) una variable global, algo que debe evitarse siempre que sea posible. - No estoy seguro de qué esperas que haga ''
replace-path PATH $PATH /usr
'', pero no hace lo que esperaría.
Considere un valor PATH que comience que contenga:
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
El resultado que obtuve (de '' replace-path PATH $PATH /usr
'') es:
.
/Users/jleffler/bin
/local/postgresql/bin
/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/local/bin
/bin
/bin
/sw/bin
/sbin
/sbin
Hubiera esperado recuperar mi ruta original ya que / usr no aparece como un elemento de ruta (completo), solo como parte de un elemento de ruta.
Esto se puede solucionar en replace-path
modificando uno de los comandos sed
:
export $path=$(echo -n $list | tr ":" "/n" | sed "s:^$removestr/$:$replacestr:" |
tr "/n" ":" | sed "s|::|:|g")
Utilicé '':'' en lugar de ''|'' para separar partes del sustituto ya que ''|'' podría (en teoría) aparecer en un componente de ruta, mientras que por definición de RUTA, un colon no puede. Observé que el segundo sed
podría eliminar el directorio actual desde la mitad de una RUTA. Es decir, un valor legítimo (aunque perverso) de PATH podría ser:
PATH=/bin::/usr/local/bin
Después del procesamiento, el directorio actual ya no estaría en la RUTA.
Un cambio similar para anclar la coincidencia es apropiado en path-element-by-pattern
:
export $target=$(echo -n $list | tr ":" "/n" | grep -m 1 "^$pat/$")
Noto de paso que grep -m 1
no es estándar (es una extensión de GNU, también disponible en MacOS X). Y, de hecho, la opción -n
para echo
tampoco es estándar; Sería mejor que simplemente borre los puntos finales que se agregan en virtud de convertir la nueva línea del eco en dos puntos. Como path-element-by-pattern se usa solo una vez, tiene efectos secundarios indeseables (anula cualquier variable exportada preexistente llamada $removestr
), puede ser reemplazado sensiblemente por su cuerpo. Esto, junto con un uso más liberal de las comillas para evitar problemas con espacios o la expansión no deseada del nombre de archivo, conduce a:
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace_path PATH $PATH /exact/path/to/remove
# replace_path_pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
# replace_path PATH $PATH /exact/path/to/remove /replacement/path
# replace_path_pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a ":" delimited list to work from (e.g. $PATH)
# $3 the precise string to be removed/replaced
# $4 the replacement string (use "" for removal)
function replace_path () {
path=$1
list=$2
remove=$3
replace=$4 # Allowed to be empty or unset
export $path=$(echo "$list" | tr ":" "/n" | sed "s:^$remove/$:$replace:" |
tr "/n" ":" | sed ''s|:$||'')
}
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a ":" delimited list to work from (e.g. $PATH)
# $3 a grep pattern identifying the element to be removed/replaced
# $4 the replacement string (use "" for removal)
function replace_path_pattern () {
path=$1
list=$2
removepat=$3
replacestr=$4 # Allowed to be empty or unset
removestr=$(echo "$list" | tr ":" "/n" | grep -m 1 "^$removepat/$")
replace_path "$path" "$list" "$removestr" "$replacestr"
}
Tengo una secuencia de comandos Perl llamada echopath
que me parece útil para la depuración de problemas con variables similares a PATH:
#!/usr/bin/perl -w
#
# "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"
#
# Print the components of a PATH variable one per line.
# If there are no colons in the arguments, assume that they are
# the names of environment variables.
@ARGV = $ENV{PATH} unless @ARGV;
foreach $arg (@ARGV)
{
$var = $arg;
$var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/;
$var = $arg unless $var;
@lst = split /:/, $var;
foreach $val (@lst)
{
print "$val/n";
}
}
Cuando ejecuto la solución modificada en el código de prueba a continuación:
echo
xpath=$PATH
replace_path xpath $xpath /usr
echopath $xpath
echo
xpath=$PATH
replace_path_pattern xpath $xpath /usr/bin /work/bin
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin
echopath xpath
El resultado es:
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/work/bin
/bin
/sw/bin
/usr/sbin
/sbin
.
/Users/jleffler/bin
/work/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
Esto me parece correcto, al menos para mi definición de cuál es el problema.
echopath LD_LIBRARY_PATH
que echopath LD_LIBRARY_PATH
evalúa $LD_LIBRARY_PATH
. Sería bueno si sus funciones fueran capaces de hacer eso, entonces el usuario podría escribir:
replace_path PATH /usr/bin /work/bin
Eso se puede hacer usando:
list=$(eval echo ''$''$path)
Esto lleva a esta revisión del código:
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace_path PATH /exact/path/to/remove
# replace_path_pattern PATH <grep pattern for target path>
#
# To replace a path:
# replace_path PATH /exact/path/to/remove /replacement/path
# replace_path_pattern PATH <target pattern> /replacement/path
#
###############################################################################
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 the precise string to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace_path () {
path=$1
list=$(eval echo ''$''$path)
remove=$2
replace=$3 # Allowed to be empty or unset
export $path=$(echo "$list" | tr ":" "/n" | sed "s:^$remove/$:$replace:" |
tr "/n" ":" | sed ''s|:$||'')
}
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a grep pattern identifying the element to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace_path_pattern () {
path=$1
list=$(eval echo ''$''$path)
removepat=$2
replacestr=$3 # Allowed to be empty or unset
removestr=$(echo "$list" | tr ":" "/n" | grep -m 1 "^$removepat/$")
replace_path "$path" "$removestr" "$replacestr"
}
La siguiente prueba revisada ahora también funciona:
echo
xpath=$PATH
replace_path xpath /usr
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath /usr/bin /work/bin
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath "/usr/.*/bin" /work/bin
echopath xpath
Produce la misma salida que antes.
¿Existe una forma idiomática de eliminar elementos de las variables de shell parecidas a PATH?
Es decir, quiero tomar
PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.
y quite o reemplace /path/to/app/bin
sin bloquear el resto de la variable. Puntos extra por permitirme poner nuevos elementos en posiciones arbitrarias. El objetivo será reconocible por una cadena bien definida, y puede ocurrir en cualquier punto de la lista.
Sé que he visto esto hecho, y probablemente pueda improvisar algo solo, pero estoy buscando un enfoque agradable. Portabilidad y estandarización a plus.
Uso bash, pero también son bienvenidos los ejemplos en tu shell favorito.
El contexto aquí es uno de necesitar cambiar convenientemente entre múltiples versiones (una para hacer análisis, otra para trabajar en el framework) de un gran paquete de análisis científico que produce un par de docenas de ejecutables, tiene datos escondidos alrededor del sistema de archivos, y usa variables de entorno para ayudar a encontrar todo esto. Me gustaría escribir un script que seleccione una versión, y necesite poder eliminar los elementos $PATH
relacionados con la versión actualmente activa y reemplazarlos por los mismos elementos relacionados con la nueva versión.
Esto está relacionado con el problema de evitar elementos $PATH
repetidos al volver a ejecutar scripts de inicio de sesión y similares.
- Pregunta similar anterior: Cómo evitar la duplicación de la variable de ruta en csh
- Pregunta posterior similar: ¿Cuál es la manera más elegante de eliminar una ruta de la variable $ PATH en Bash?
En línea con la respuesta de dj_segfault , hago esto en scripts que añaden / anteponen variables de entorno que pueden ejecutarse varias veces:
ld_library_path=${ORACLE_HOME}/lib
LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/}
export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
Usar esta misma técnica para eliminar, reemplazar o manipular entradas en PATH es trivial dada la coincidencia de patrón similar a la expansión de nombre de archivo y la compatibilidad con la lista de patrones de la expansión de parámetros de shell.
Esto es fácil usando awk.
Reemplazar
{
for(i=1;i<=NF;i++)
if($i == REM)
if(REP)
print REP;
else
continue;
else
print $i;
}
Comience usando
function path_repl {
echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd:
}
$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_repl /bin /baz
/baz:/usr/bin:/home/js/usr/bin
$ path_repl /bin
/usr/bin:/home/js/usr/bin
Adjuntar
Inserta en la posición dada. Por defecto, se agrega al final.
{
if(IDX < 1) IDX = NF + IDX + 1
for(i = 1; i <= NF; i++) {
if(IDX == i)
print REP
print $i
}
if(IDX == NF + 1)
print REP
}
Comience usando
function path_app {
echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd:
}
$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_app /baz 0
/bin:/usr/bin:/home/js/usr/bin:/baz
$ path_app /baz -1
/bin:/usr/bin:/baz:/home/js/usr/bin
$ path_app /baz 1
/baz:/bin:/usr/bin:/home/js/usr/bin
Eliminar duplicados
Este mantiene las primeras ocurrencias.
{
for(i = 1; i <= NF; i++) {
if(!used[$i]) {
print $i
used[$i] = 1
}
}
}
Comience así:
echo $PATH | awk -F: -f rem_dup.awk | paste -sd:
Validar si todos los elementos existen
Lo siguiente imprimirá un mensaje de error para todas las entradas que no existen en el sistema de archivos, y devolverá un valor distinto de cero.
echo -n $PATH | xargs -d: stat -c %n
Para comprobar simplemente si todos los elementos son rutas y obtener un código de retorno, también puede usar la test
:
echo -n $PATH | xargs -d: -n1 test -d
Hay un par de programas relevantes en las respuestas a " Cómo evitar la duplicación de la variable de ruta en csh ". Se concentran más en garantizar que no haya elementos repetidos, pero el script que proporciono se puede usar como:
export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs)
Suponiendo que tiene uno o más directorios en $ head_dirs y uno o más directorios en $ tail_dirs y uno o más directorios en $ remove_dirs, usa el shell para concatenar las partes principal, actual y cola en un valor masivo, y luego elimina cada de los directorios listados en $ remove_dirs del resultado (no es un error si no existen), así como la eliminación de las segundas y subsecuentes ocurrencias de cualquier directorio en la ruta.
Esto no aborda la colocación de componentes de ruta en una posición específica (que no sea al principio o al final, y solo de forma indirecta). Notablemente, especificar dónde quieres agregar el nuevo elemento o qué elemento deseas reemplazar es complicado.
Lo primero que me viene a la cabeza para cambiar solo una parte de una cuerda es una sustitución sed.
ejemplo: si echo $ PATH => "/ usr / pkg / bin: / usr / bin: / bin: / usr / pkg / games: / usr / pkg / X11R6 / bin" luego para cambiar "/ usr / bin" a "/ usr / local / bin" podría hacerse así:
## produce archivo de salida estándar
## el carácter "=" se usa en lugar de barra ("/") ya que sería desordenado, # el carácter de comillas alternativo debería ser poco probable en PATH
## el carácter de separador de ruta ":" se elimina y se vuelve a agregar aquí, # podría querer un dos puntos extra después de la última ruta
echo $ PATH | sed ''= / usr / bin: = / usr / local / bin: =''
Esta solución reemplaza un elemento path completo, por lo que podría ser redundante si el elemento nuevo es similar.
Si las nuevas PATH''-s no son dinámicas, pero siempre dentro de un conjunto constante, puede guardarlas en una variable y asignarlas según sea necesario:
RUTA = $ TEMP_PATH_1; # comandos ...; / n PATH = $ TEMP_PATH_2; # comandos, etc ...;
Puede que no sea lo que estabas pensando. algunos de los comandos relevantes en bash / unix serían:
pushd popd cd ls # quizás l -1A para una sola columna; encuentra grep, que # podría confirmar que el archivo es de donde crees que proviene; tipo env
..y todo eso y más tienen algo que ver con PATH o directorios en general. ¡La parte que altera el texto se puede hacer de muchas maneras!
Cualquiera que sea la solución elegida tendrá 4 partes:
1) buscar la ruta tal como está 2) decodificar la ruta para encontrar la parte que necesita cambios 3) determinar qué cambios se necesitan / integrar esos cambios 4) validación / integración final / establecer la variable
OK, gracias a todos los que respondieron. He preparado una versión encapsulada de la respuesta de Florin. El primer pase se ve así:
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace-path PATH $PATH /exact/path/to/remove
# replace-path-pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
# replace-path PATH $PATH /exact/path/to/remove /replacement/path
# replace-path-pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################
# Finds the _first_ list element matching $2
#
# $1 name of a shell variable to be set
# $2 name of a variable with a path-like structure
# $3 a grep pattern to match the desired element of $1
function path-element-by-pattern (){
target=$1;
list=$2;
pat=$3;
export $target=$(echo -n $list | tr ":" "/n" | grep -m 1 $pat);
return
}
# Removes or replaces an element of $1
#
# $1 name of the shell variable to set (i.e. PATH)
# $2 a ":" delimited list to work from (i.e. $PATH)
# $2 the precise string to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace-path () {
path=$1;
list=$2;
removestr=$3;
replacestr=$4; # Allowed to be ""
export $path=$(echo -n $list | tr ":" "/n" | sed "s|$removestr|$replacestr|" | tr "/n" ":" | sed "s|::|:|g");
unset removestr
return
}
# Removes or replaces an element of $1
#
# $1 name of the shell variable to set (i.e. PATH)
# $2 a ":" delimited list to work from (i.e. $PATH)
# $2 a grep pattern identifying the element to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace-path-pattern () {
path=$1;
list=$2;
removepat=$3;
replacestr=$4; # Allowed to be ""
path-element-by-pattern removestr $list $removepat;
replace-path $path $list $removestr $replacestr;
}
Todavía necesita captura de errores en todas las funciones, y probablemente me quede en una solución de ruta repetida mientras estoy en ello.
Lo usas haciendo a . /include/path/path_tools.bash
. /include/path/path_tools.bash
en el script de trabajo y la invocación de las funciones replace-path*
.
Todavía estoy abierto a nuevas y / o mejores respuestas.
Para eliminar un elemento puedes usar sed:
#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "/n" | sed "/foo/d" | tr "/n" ":")
export PATH=$NEW_PATH
eliminará las rutas que contienen "foo" de la ruta.
También podría usar sed para insertar una nueva línea antes o después de una línea determinada.
Editar: puede eliminar duplicados pasando por sort y uniq:
echo -n $PATH | tr ":" "/n" | sort | uniq -c | sed -n "/ 1 / s/.*1 /(.*/)//1/p" | sed "/foo/d" | tr "/n" ":"
Reposicionando mi respuesta a ¿Cuál es la forma más elegante de eliminar una ruta de la variable $ PATH en Bash? :
#!/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[*]}"
o el one-liner:
PATH=$(IFS='':'';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS='':'';echo "${t[*]}");
Solo una nota que bash puede buscar y reemplazar. Puede hacer todas las opciones normales "una vez o todas", casos [en] sensibles que cabría esperar.
Desde la página man:
$ {parameter / pattern / string}
El patrón se expande para producir un patrón al igual que en la expansión del nombre de ruta. El parámetro se expande y la coincidencia más larga del patrón con su valor se reemplaza por una cadena. Si Ipattern comienza con /, todas las coincidencias de patrón se reemplazan con una cadena. Normalmente, solo se reemplaza la primera coincidencia. Si el patrón comienza con #, debe coincidir al comienzo del valor expandido del parámetro. Si el patrón comienza con%, debe coincidir al final del valor expandido del parámetro. Si la cadena es nula, las coincidencias del patrón se eliminan y el patrón / siguiente puede omitirse. Si el parámetro es @ o *, la operación de sustitución se aplica a cada parámetro posicional por turno, y la expansión es la lista resultante. Si el parámetro es una variable de matriz suscrita con @ o *, la operación de sustitución se aplica sucesivamente a cada miembro de la matriz, y la expansión es la lista resultante.
También puede dividir el campo estableciendo $ IFS (separador de campo de entrada) en el delimitador deseado.
suponer
echo $PATH
/usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin
Si desea eliminar /lib/jvm/java-1.6.0/bin/ hacer como se muestra a continuación
export PATH=$(echo $PATH | sed ''s///lib//jvm//java-1.6.0//bin//://g'')
sed
tomará la entrada de echo $PATH
y reemplazará /lib/jvm/java-1.6.0/bin/: con el vacío
de esta forma puedes eliminar
- El orden de PATH no está distribuido
- Maneja casos de esquina como camino vacío, espacio en camino con gracia
- Parcial coincidencia de dir no da falsos positivos
- Trata el camino a la cabeza y la cola de PATH de manera adecuada. No : basura y tal.
Digamos que tiene / foo: / some / path: / some / path / dir1: / some / path / dir2: / bar y quiere reemplazar / some / path Entonces reemplaza correctamente "/ some / path" pero se va "/ some / path / dir1 "o" / some / path / dir2 ", como es de esperar.
function __path_add(){
if [ -d "$1" ] ; then
local D=":${PATH}:";
[ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
}
function __path_remove(){
local D=":${PATH}:";
[ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
}
# Just for the shake of completeness
function __path_replace(){
if [ -d "$2" ] ; then
local D=":${PATH}:";
if [ "${D/:$1:/:}" != "$D" ] ; then
PATH="${D/:$1:/:$2:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
fi
}
Publicación relacionada ¿Cuál es la forma más elegante de eliminar una ruta de la variable $ PATH en Bash?