martes, 15 de marzo de 2016

Modificación de Variables Globales en BASH

[Lenguajes: BASH]

Problema

En Linux/BASH, en ocasiones es necesario modificar temporalmente alguna de las variables globales del sistema, y, a menudo esta operación se quiere hacer de manera automática empleando un programa de BASH, guardado en un archivo, que es lo que se conoce como un Script y que se invocará manualmente desde una terminal.

Para dar un poco más de contexto real al problema, supongamos que se desea cambiar una de las entradas en la variable global PATH, por otra, sin modificar ninguna otra de las entradas. Sea, por ejemplo, el siguiente el valor de la variable PATH:

# El valor de la variable PATH
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

Supongamos que en esta variable se quiere cambiar solamente la entrada /usr/games por una semejante a /disco0/usr/games, dejando intactas todas las demás entradas.

Aquí entonces el problema se subdivide en dos, a saber:

  • ¿Cómo producimos, a partir de la variable PATH, el nuevo valor que debe tener ésta?
  • ¿Cómo se establece ese resultado como el nuevo valor de la variable global PATH?

Solución

Calculando el nuevo valor que debe tener la variable PATH

Aquí proponemos dos estrategias para resolver este problema:

  1. Utilizar la substitución nativa de BASH para filtrar la variable.
  2. Emplear el editor de flujos de textos sed para filtrar la variable.

Empleo de la sustitución nativa de BASH

El formato para esta instrucción es ${ variable / que-sustituir / reemplazo }. Como tanto el que-sustituir, como el reemplazo, contienen, en este caso, la diagonal (/) que también forma parte de la instrucción, se tiene que distinguir en esos textos de entrada, justamente como texto y no como parte de la instrucción, mediante el prefijo del caracter backslash (\). De este modo, las instrucciones en BASH, para ejecutar la sustitución es como sigue:

PATH=${PATH/\/usr\/games/\/disco0\/usr\/games}
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Empleo de sed

Típicamente, sed toma como entrada un archivo, que, en caso de omisión es el stdin o standard input. Una variable común, que contenga texto, se puede convertir en el stdin, mediante el operador de indirección <<<. Por otra parte, el comando de sustitución de sed, es parecido al nativo de BASH: “s*/ que-sustituir / reemplazo /*”. La diferencia es que, en este caso, para evitar confusiones en el comando, el caracter de separación no es necesariamente la diagonal, y puede ser cualquiera que no aparezca en los textos de entrada. Así, para producir una salida como la que deseamos se podría hacer así:

sed "s:/usr/games:/disco0/usr/games:" <<<$PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Lo que hay que notar aquí, es que el resultado no se ha guardado en ninguna variable, sino que se ha mandado directamente al stdout. Para guardar el resultado en una variable se hace mediante la instrucción de expansión de comandos, $(comando ). Así que, lo que queremos lograr se hace con:

PATH=$(sed "s:/usr/games:/disco0/usr/games:" <<<$PATH)
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Guardando el valor en la variable global PATH

En lo que se ha descrito anteriormente, las instrucciones se han dado en línea, directamente en una terminal. Sin embargo, la tarea se tiene que hacer mediante un archivo script que contenga las instrucciones.

Aparte, supuestamente la instrucción export de BASH, sirve para modificar variables globales. Así que el contenido de nuestro archivo al que llamaremos "cambia.sh", será el que se muestra a continuación:

#! /bin/bash
PATH=$(sed "s:/usr/games:/disco0/usr/games:" <<<$PATH)
export PATH
echo $PATH

Intencionalmente hemos dejado la instrucción echo $PATH en el script, para monitorear
el valor de la variable en el interior del script. Se ha hecho además que el script sea ejecutable mediante la instrucción chmod +x cambia.sh, de modo que se pueda invocar su ejecución fácilmente. Veamos pues su comportamiento:

./cambia.sh
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

La primer impresión de la variable corresponde a la que se hizo en el interior del script "cambia.sh", la segunda, que como se puede ver es diferente y no manifiesta el cambio que se intentaba, corresponde al comando echo $PATH que se ha ejecutado desde la terminal. Esto indica que la variable gobal PATH no se ha cambiado.

Este resultado es producto de la forma como se ha invocado la ejecución de "cambia.sh". En este caso, se ha creado un proceso independiente, si bien no separado, del que se está ejecutando en la terminal desde donde lo hemos invocado. Para establecer su ejecución dentro del mismo proceso, se hace con la instrucción source Script, o bien . Script. La llamada queda como sigue:

source cambia.sh
echo $PATH
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games
## /home/checo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/disco0/usr/games:/usr/local/games

Finalmente, si queremos convertir esta llamada en un comando se puede hacer mediante la instrucción alias, que se puede poner en archivos de inicialización tales como el “.bashrc”, así:

alias cambia='source /aqui-path-completo-hacia/cambia.sh'

Problema resuelto.