Aprendiendo_Korn_Shell/Secciones/Capitulo4.tex

1368 lines
114 KiB
TeX

Si te has familiarizado con las técnicas de personalización que presentamos en el capítulo anterior, probablemente te habrás encontrado con varias modificaciones en tu entorno que quieres hacer pero no puedes - todavía. La programación Shell las hace posibles.
El shell de Korn tiene algunas de las capacidades de programación más avanzadas de cualquier intérprete de comandos de su tipo. Aunque su sintaxis no es ni de lejos tan elegante o consistente como la de la mayoría de los lenguajes de programación convencionales, su potencia y flexibilidad son comparables. De hecho, el shell de Korn puede utilizarse como un entorno completo para escribir prototipos de software.
Algunos aspectos de la programación en el shell de Korn son realmente extensiones de las técnicas de personalización que ya hemos visto, mientras que otros se asemejan a las características de los lenguajes de programación tradicionales. Hemos estructurado este capítulo de forma que si no eres programador, puedes leer este capítulo y hacer bastante más de lo que podrías hacer con la información del capítulo anterior. La experiencia con un lenguaje de programación convencional como Pascal o C es útil (aunque no estrictamente necesaria) para los capítulos siguientes. A lo largo del resto del libro, nos encontraremos ocasionalmente con problemas de programación, llamados <<tareas>>, cuyas soluciones hacen uso de los conceptos que cubrimos.
\section{Scripts y Funciones del Shell}\label{sec:4.1}
Un \emph{script}, o archivo que contiene comandos del shell, es un programa del shell. Sus archivos \emph{.profile} y de entorno, discutidos en el \hyperref[sec:Chapter3]{Capítulo 3}, son scripts de shell.
Puedes crear un script utilizando el editor de texto que prefieras. Una vez que hayas creado uno, hay varias maneras de ejecutarlo. Una, que ya hemos cubierto, es escribir \texttt{. scriptname} (es decir, el comando es un punto). Esto hace que los comandos del script sean leídos y ejecutados como si los hubieras tecleado.
Otras dos formas son escribir \texttt{ksh script} o \texttt{ksh < script}. Estos invocan explícitamente el shell de Korn en el script, requiriendo que usted (y sus usuarios) sean conscientes de que son scripts.
La última forma de ejecutar un script es simplemente escribir su nombre y pulsar ENTER, como si estuviera invocando un comando integrado. Esta, por supuesto, es la forma más conveniente. Este método hace que el script se parezca a cualquier otro comando Unix, y de hecho varios comandos <<normales>> se implementan como scripts de shell (es decir, no como programas escritos originalmente en C o algún otro lenguaje), incluyendo \emph{spell}, \emph{man} en algunos sistemas, y varios comandos para administradores de sistemas. La consiguiente falta de distinción entre <<archivos de comandos de usuario>> y <<comandos incorporados>> es uno de los factores que explican la extensibilidad de Unix y, por tanto, su favoritismo entre los programadores.
Puede ejecutar un script tecleando su nombre sólo si . (el directorio actual) forma parte de su ruta de búsqueda de comandos, es decir, si está incluido en su variable PATH (como se explica en el \hyperref[sec:Chapter3]{Capítulo 3}). Si . no está en su ruta, debe teclear \texttt{./ nombredelscript}, que es realmente lo mismo que teclear la ruta relativa del script (ver \hyperref[sec:Chapter1]{Capítulo 1}).
Antes de que pueda invocar el script de shell por su nombre, también debe darle permiso de <<ejecución>>. Si está familiarizado con el sistema de archivos Unix, sabrá que los archivos tienen tres tipos de permisos (lectura, escritura y ejecución) y que esos permisos se aplican a tres categorías de usuarios (el propietario del archivo, un grupo de usuarios y todos los demás). Normalmente, cuando creas un archivo con un editor de texto, el archivo se configura con permiso de lectura y escritura para ti y permiso de sólo lectura para todos los demás\footnote{En realidad, esto depende de la configuración de tu \emph{umask}, una función avanzada que se describe en el \hyperref[sec:Chapter10]{Capítulo 10}.}.
Por lo tanto, debe dar a su script permiso de ejecución explícitamente, utilizando el comando \emph{chmod(1)}. La forma más sencilla de hacerlo es así:
\begin{lstlisting}[language=bash]
chmod +x scriptname
\end{lstlisting}
Su editor de texto conserva este permiso si realiza cambios posteriores en el script. Si no añade permiso de ejecución al script, e intenta invocarlo, el shell imprime el mensaje:
\begin{lstlisting}[language=bash]
ksh: scriptname: cannot execute [Permission denied]
\end{lstlisting}
Pero hay una diferencia más importante entre las dos formas de ejecutar scripts de shell. Mientras que el método \emph{punto} <<dot>> hace que los comandos del script se ejecuten como si fueran parte de tu sesión de inicio de sesión, el método <<sólo el nombre>> hace que el shell haga una serie de cosas. En primer lugar, ejecuta otra copia del shell como subproceso. El subproceso del intérprete de comandos toma los comandos del script, los ejecuta y termina, devolviendo el control al intérprete de comandos principal.
La Figura \ref{shell script} muestra cómo el shell ejecuta scripts. Suponga que tiene un script de shell simple llamado \emph{fred} que contiene los comandos \emph{bob} y \emph{dave}. En la Figura \ref{shell script}.a, escribir \texttt{. fred} hace que los dos comandos se ejecuten en el mismo shell, como si los hubieras escrito a mano. La Figura 4-1.b muestra lo que sucede cuando escribes sólo \texttt{fred:} los comandos se ejecutan en el subproceso del shell mientras el shell padre espera a que el subproceso termine.
Puede resultarle interesante comparar esta situación con la de la Figura \ref{shell script}.c que muestra lo que ocurre cuando escribe \texttt{fred \&}. Como recordará del \hyperref[sec:Chapter1]{Capítulo 1}, el \& hace que el comando se ejecute en segundo plano, que en realidad es sólo otro término para <<subproceso>>. Resulta que la única diferencia significativa entre la Figura \ref{shell script}.c y la Figura \ref{shell script}.b es que usted tiene el control de su terminal o estación de trabajo mientras se ejecuta el comando - no necesita esperar a que termine para poder introducir más comandos.
\begin{figure}[h!]
\centering
\includegraphics[scale=1]{fig_03}
\caption{\small{Formas de ejecutar un script de shell}}
\label{shell script}
\end{figure}
El uso de subprocesos de shell tiene muchas ramificaciones. Una importante es que las variables de entorno exportadas que vimos en el último capítulo (por ejemplo, \texttt{TERM, LOGNAME, PWD}) son conocidas en los subprocesos del shell, mientras que otras variables del shell (como cualquiera que definas en tu \emph{.profile} sin una sentencia \emph{export}) no lo son.
Otras cuestiones relacionadas con los subprocesos del shell son demasiado complejas para entrar en ellas ahora; consulte el \hyperref[sec:Chapter7]{Capítulo 7} y el \hyperref[sec:Chapter8]{Capítulo 8} para obtener más detalles sobre la E/S de los subprocesos y las características de los procesos, respectivamente. Por ahora, sólo tenga en cuenta que un script normalmente se ejecuta en un subproceso del shell.
\subsection{Funciones}
La característica de función del shell de Korn es una versión ampliada de una facilidad similar en el shell de Bourne del Sistema V y algunos otros shells. Una función es una especie de script dentro de un script; se usa para definir algún código del shell por su nombre y almacenarlo en la memoria del shell, para ser invocado y ejecutado más tarde.
Las funciones mejoran significativamente la programabilidad del shell, por dos razones principales. En primer lugar, cuando invoca una función, ésta ya se encuentra en la memoria del shell (excepto en el caso de las funciones cargadas automáticamente; consulte la \hyperref[sec:4111]{Sección 4.1.1.1}, más adelante en este capítulo); por lo tanto, una función se ejecuta más rápidamente. Los ordenadores modernos tienen mucha memoria, así que no hay necesidad de preocuparse por la cantidad de espacio que ocupa una función típica. Por esta razón, la mayoría de la gente define tantas funciones como sea posible en lugar de tener muchos scripts a su alrededor.
La otra ventaja de las funciones es que son ideales para organizar largos scripts de shell en <<trozos>> modulares de código que son más fáciles de desarrollar y mantener. Si no eres programador, pregúntale a uno cómo sería la vida sin funciones (también llamadas \emph{procedimientos} o \emph{subrutinas} en otros lenguajes) y probablemente te echarán la bronca.
Para definir una función, puede utilizar una de estas dos formas:
\begin{lstlisting}[language=bash]
function functname { Semántica del shell de Korn
Comandos de shell
}
\end{lstlisting}
O
\begin{lstlisting}[language=bash]
function () { Semántica POSIX
Comandos de shell
}
\end{lstlisting}
La primera forma proporciona acceso a toda la potencia y programabilidad del shell de Korn. La segunda es compatible con la sintaxis para funciones de shell introducida en el shell de Bourne de la versión 2 del sistema V. Esta forma obedece a la semántica del estándar POSIX, que es menos potente que las funciones completas estilo shell de Korn. (En este libro utilizaremos siempre la primera forma. Puede borrar una definición de función con el comando \texttt{unset -f nombrefuncion}.
Cuando define una función, le indica al shell que almacene su nombre y definición (es decir, los comandos del shell que contiene) en la memoria. Si desea ejecutar la función más tarde, sólo tiene que escribir su nombre seguido de los argumentos, como si se tratara de un script del shell.
Puede averiguar qué funciones están definidas en su sesión de inicio de sesión escribiendo \texttt{functions}\footnote{Se trata en realidad de un alias de \emph{typeset -f}; véase el capítulo 6.}. (Observe la s al final del nombre del comando.) El shell imprimirá no sólo los nombres sino también las definiciones de todas las funciones, en orden alfabético por nombre de función. Dado que esto puede resultar en una salida larga, es posible que desee canalizar la salida a través de más o redirigirla a un archivo para su examen con un editor de texto.
Aparte de las ventajas, hay dos diferencias importantes entre las funciones y los scripts. En primer lugar, las funciones no se ejecutan en procesos separados, como hacen los scripts cuando los invocas por su nombre; la <<semántica>> de la ejecución de una función es más parecida a la de tu perfil \emph{.profile} cuando te conectas o a la de cualquier script cuando se invoca con el comando <<dot>>. En segundo lugar, si una función tiene el mismo nombre que un script o programa ejecutable, la función tiene preferencia.
Este es un buen momento para mostrar el orden de precedencia de las distintas fuentes de comandos. Cuando escribes un comando en el shell, éste busca en los siguientes lugares hasta que encuentra una coincidencia:
\begin{enumerate}
\item{Palabras clave, como \emph{function} y algunas otras (por ejemplo, \emph{if} y \emph{for}) que veremos en el \hyperref[sec:Chapter5]{Capítulo 5}.}
\item{Alias (aunque no puede definir un alias cuyo nombre sea una palabra clave del shell, puede definir un alias que se expanda a una palabra clave, por ejemplo, \texttt{alias aslongas=while}; consulte el \hyperref[sec:Chapter7]{Capítulo 7} para obtener más detalles)}
\item{Complementos especiales, como \emph{break} y \emph{continue} (la lista completa es \texttt{. (punto), :, alias, break, continue, eval, exec, exit, export, login, newgrp, readonly, return, set, shift, trap, typeset, unalias, y unset)}}
\item{Funciones}
\item{Funciones incorporadas no especiales, como \emph{cd} y \emph{whence}}
\item{Scripts y programas ejecutables, que el shell busca en los directorios enumerados en la variable de entorno PATH.}
\end{enumerate}
Examinaremos este proceso con más detalle en la sección sobre procesamiento desde la línea de comandos del \hyperref[sec:Chapter7]{Capítulo 7}.
Si necesita saber la fuente exacta de un comando, hay una opción para el comando incorporado \emph{whence} que vimos en el \hyperref[sec:Chapter3]{Capítulo 3}. \emph{whence} por sí mismo imprimirá la ruta de un comando si el comando es un script o un programa ejecutable, pero sólo repetirá el nombre del comando si es cualquier otra cosa. Pero si escribe \texttt{whence -v nombredelcomando}, obtendrá información más completa, como por ejemplo:
\begin{lstlisting}[language=bash]
$ whence -v cd
cd is a shell builtin
$ whence -v function
function is a keyword
$ whence -v man
man is a tracked alias for /usr/bin/man
$ whence -v ll
ll is an alias for 'ls -l'
\end{lstlisting}
Por compatibilidad con el shell de Bourne del Sistema V, el shell de Korn predefine el alias \texttt{type='whence -v'}. Esto definitivamente hace la transición al shell de Korn más fácil para los antiguos usuarios del shell de Bourne; \emph{type} es similar a \emph{whence}. El comando whence en realidad tiene varias opciones, descritas en la Tabla \ref{Tab: whence}.
\begin{table}[h]
\center
\caption{Opciones del comando whence}
\label{Tab: whence}
\begin{tabular}{|m{2cm}|m{12cm}|} \hline
\textbf{Opción} & \textbf{Significado} \\ \hline
-a & Imprime todas las interpretaciones del nombre dado. \\\hline
-f & Omitir funciones en la búsqueda del nombre. \\\hline
-p & Busca en \$PATH, incluso si el nombre es un \emph{built-in} o una función. \\\hline
-v & Imprime una descripción más detallada del nombre. \\\hline
\end{tabular}
\end{table}
En el resto de este libro nos referiremos principalmente a los scripts, pero a menos que indiquemos lo contrario, debes asumir que todo lo que digamos se aplica igualmente a las funciones.
\subsubsection{Carga Automática de Funciones}\label{sec:4111}
A primera vista, parecería que el mejor lugar para poner sus propias definiciones de funciones es en su archivo \emph{.profile} o de entorno. Esto es genial para el uso interactivo, ya que su shell de inicio de sesión lee \texttt{\~{}/.profile}, y otros shells interactivos leen el fichero de entorno. Sin embargo, cualquier script de shell que escriba no lee ninguno de los dos archivos. Además, a medida que tu colección de funciones crece, también lo hacen tus archivos de inicialización, haciendo que sea difícil trabajar con ellos.
\emph{ksh93} soluciona estos dos problemas integrando la búsqueda de funciones con la búsqueda de comandos. Así es como funciona:
\begin{enumerate}
\item{Cree un directorio para guardar las definiciones de sus funciones. Este puede ser su directorio bin privado, o puede que desee tener un directorio separado, como \texttt{\~}\texttt{/funcs}. Por el bien de la discusión, supongamos esto último.}
\item{En su archivo \emph{.profile}, añada este directorio a las variables PATH y FPATH:
\begin{lstlisting}[language=bash]
PATH=$PATH:~/funcs
FPATH=~/funcs
export PATH FPATH
\end{lstlisting}
}
\item{En \texttt{\~{}}\texttt{/funcs}, coloque la definición de cada una de sus funciones en un archivo separado. El archivo de cada función debe tener el mismo nombre que la función:
\begin{lstlisting}[language=bash]
$ mkdir ~/funcs
$ cd ~/funcs
$ cat > whoson
# whoson --- create a sorted list of logged-on users
function whoson {
who | awk '{ print $1 }' | sort -u
}
^D
\end{lstlisting}
}
\end{enumerate}
Ahora, la primera vez que escriba \texttt{whoson}, el shell buscará un comando llamado whoson usando el orden de búsqueda descrito anteriormente. No se encontrará como un \emph{built-in} especial, como una función o como un \emph{built-in} normal. El intérprete de comandos inicia entonces una búsqueda a lo largo de \texttt{\$PATH}. Cuando finalmente encuentra \emph{\~}\emph{/funcs/whoson}, el shell se da cuenta de que \emph{\~/funcs} también está en \texttt{\$FPATH}. (''¡Ajá!'' dice el shell.) Cuando este es el caso, el shell espera encontrar la definición de la función llamada \emph{whoson} dentro del archivo. Lee y ejecuta todo el contenido del archivo y sólo entonces ejecuta la función \emph{whoson}, con cualquier argumento suministrado. (Si el archivo encontrado tanto en \texttt{\$PATH} como en \texttt{\$FPATH} no define realmente la función, obtendrá un mensaje de error <<no encontrado>>).
La próxima vez que escriba \emph{whoson}, la función ya estará definida, por lo que el shell la encontrará inmediatamente, sin necesidad de buscar la ruta.
Tenga en cuenta que los directorios listados en FPATH pero no en PATH no serán buscados por funciones, y que a partir de \emph{ksh93l}, el directorio actual debe ser listado en FPATH mediante un punto explícito; dos puntos iniciales o finales no hacen que se busque en el directorio actual.
Por último, a partir de \emph{ksh93m}, cada directorio nombrado en PATH puede contener un archivo llamado \emph{.paths}. Este archivo puede contener comentarios y líneas en blanco, y asignaciones de variables especializadas. La primera asignación permitida es a FPATH, donde el valor debe nombrar un directorio existente. Si ese directorio contiene un archivo cuyo nombre coincide con la función que se está buscando, ese archivo se lee y se ejecuta como si fuera a través del comando . (dot), y luego se ejecuta la función.
Además, se puede asignar otra variable de entorno. Su uso previsto es especificar una ruta relativa o absoluta para un directorio de bibliotecas que contenga las bibliotecas compartidas para los ejecutables en el directorio bin actual. En muchos sistemas Unix, esta variable es \texttt{LD\_LIBRARY\_PATH}, pero algunos sistemas tienen una variable diferente - consulte su documentación local. El valor dado se añade al valor existente de la variable cuando se ejecuta la orden. (Este mecanismo puede abrir brechas de seguridad. Los administradores de sistemas deben utilizarlo con precaución).
Por ejemplo, el grupo AT\&T Advanced Software Tools que distribuye \emph{ksh93} también tiene muchas otras herramientas, a menudo instaladas en un directorio \emph{ast/bin} separado. Esta característica permite a los programas ast encontrar sus bibliotecas compartidas, sin que el usuario tenga que ajustar manualmente \texttt{LD\_LIBRARY\_PATH} en el archivo \emph{.profile}\footnote{Las versiones h a l+ de \emph{ksh93} utilizaban un mecanismo similar pero más restringido, a través de un fichero llamado \emph{.fpath}, e incorporaban la configuración de la variable de ruta de la biblioteca. Como esta característica no estaba muy extendida, se generalizó en un único archivo a partir de la versión m.}. Por ejemplo, si un comando se encuentra en \emph{/usr/local/ast/bin}, y el archivo \emph{.paths} en ese directorio contiene la asignación \texttt{LD\_LIBRARY\_PATH=../lib}, el shell añade \texttt{/usr/local/ast/lib:} al valor de \texttt{LD\_LIBRARY\_PATH} antes de ejecutar el comando.
Los lectores familiarizados con \emph{ksh88} notarán que esta parte del comportamiento del shell ha cambiado significativamente. Dado que \emph{ksh88} siempre leía el fichero de entorno, tanto si el shell era interactiva como si no, lo más sencillo era limitarse a poner allí las definiciones de las funciones. Sin embargo, esto podía dar lugar a un archivo grande y difícil de manejar. Para evitar esto, puedes crear archivos en uno o más directorios listados en \texttt{\$FPATH}. Luego, en el archivo de entorno, se marcarían las funciones como de \emph{carga automática}:
\begin{lstlisting}[language=bash]
autoload whoson
...
\end{lstlisting}
Marcar una función con \emph{autoload}\footnote{\emph{autoload} es en realidad un alias de \emph{typeset -fu}.} le dice al shell que este nombre es una función, y que encuentre la definición buscando en \texttt{\$FPATH}. La ventaja de esto es que la función no se carga en la memoria del shell si no se necesita. La desventaja es que tienes que listar explícitamente todas tus funciones en tu fichero de entorno.
La integración de \emph{ksh93} de la búsqueda en PATH y FPATH simplifica así la forma de añadir funciones del intérprete de órdenes a su <<biblioteca>> personal de funciones del intérprete de órdenes.
\subsubsection{Funciones POSIX}
Como se mencionó anteriormente, las funciones definidas usando la sintaxis POSIX obedecen a la semántica POSIX y no a la semántica del shell de Korn:
\begin{lstlisting}[language=bash]
functname () {
comandos de shell
}
\end{lstlisting}
La mejor manera de entender esto es pensar en una función POSIX como si fuera un punto script. Las acciones dentro del cuerpo de la función afectan a todo el estado del script actual. En contraste, las funciones del shell de Korn tienen mucho menos estado compartido con el shell padre, aunque no son idénticas a scripts totalmente separados.
Los detalles técnicos siguen; incluyen información que aún no hemos cubierto. Así que vuelva y relea esta sección después de haber aprendido sobre el comando \emph{typeset} en el \hyperref[sec:Chapter6]{Capítulo 6} y sobre traps en el \hyperref[sec:Chapter8]{Capítulo 8}.
\begin{itemize}
\item{Las funciones POSIX comparten variables con el script padre. Las funciones del shell de Korn pueden tener sus propias variables locales.}
\item{Las funciones POSIX comparten trampas con el script padre. Las funciones del shell de Korn pueden tener sus propias trampas locales.}
\item{Las funciones POSIX no pueden ser recursivas (llamarse a sí mismas)\footnote{Esta es una restricción impuesta por el shell de Korn, no por el estándar POSIX.}. Las funciones del shell de Korn sí pueden.}
\item{Cuando se ejecuta una función POSIX, \texttt{\$0} no se cambia por el nombre de la función.}
\end{itemize}
Si utiliza el comando \emph{dot} con el nombre de una función del shell de Korn, dicha función obedecerá la semántica POSIX, afectando a todo el estado (variables y traps) del shell padre:
\begin{lstlisting}[language=bash]
$ function demo { # Define una función de Korn Shell
> typeset myvar=3 # Establece una variable local llamada myvar
> print "demo: myvar is $myvar"
> }
$ myvar=4 # Establece una variable global llamada myvar
$ demo ; print "global: myvar is $myvar" # Ejecuta la función
demo: myvar is 3
global: myvar is 4
$ . demo # Ejecuta la función con semantica POSIX
demo: myvar is 3
$ print "global: myvar is $myvar" # Observa los resultados
global: myvar is 3
\end{lstlisting}
\section{Variables del Shell}
Una parte importante de la funcionalidad de programación del shell de Korn está relacionada con las variables del shell. Ya hemos visto los conceptos básicos de las variables. Para recapitular brevemente: son lugares con nombre para almacenar datos, normalmente en forma de cadenas de caracteres, y sus valores pueden obtenerse precediendo sus nombres con signos de dólar (\$). Ciertas variables, llamadas variables de entorno, se nombran convencionalmente en mayúsculas, y sus valores se dan a conocer (con la sentencia export) a los subprocesos.
Esta sección presenta los conceptos básicos de las variables del shell. La discusión de ciertas características avanzadas se retrasa hasta más adelante en el capítulo, después de cubrir las expresiones regulares.
Si es programador, ya sabe que casi todos los lenguajes de programación importantes utilizan variables de alguna manera; de hecho, una forma importante de caracterizar las diferencias entre lenguajes es comparar sus facilidades para las variables.
La principal diferencia entre el esquema de variables del shell de Korn y los de los lenguajes convencionales es que el esquema del shell de Korn pone mucho énfasis en las cadenas de caracteres. (Por tanto, tiene más en común con un lenguaje de propósito especial como SNOBOL que con uno de propósito general como Pascal). Esto también es cierto para el shell de Bourne y el shell C, pero el shell de Korn va más allá de ellos al tener mecanismos adicionales para manejar enteros y números de coma flotante de doble precisión explícitamente, así como arrays simples.
\subsection{Parámetros de Posición}
Como ya hemos visto, puedes definir valores para variables con sentencias de la forma \texttt{varname = value}, por ejemplo:
\begin{lstlisting}[language=bash]
$ fred=bob
$ print ''$fred>>
bob
\end{lstlisting}
Algunas variables de entorno son predefinidas por el shell cuando te conectas. Hay otras variables incorporadas que son vitales para la programación del shell. Ahora veremos algunas de ellas y dejaremos las otras para más adelante.
Las variables incorporadas especiales más importantes se denominan \emph{parámetros posicionales}. Contienen los argumentos de la línea de comandos de los scripts cuando son invocados. Los parámetros posicionales tienen los nombres \texttt{1, 2, 3,} etc., lo que significa que sus valores se denotan por \texttt{\$1, \$2, \$3,} etc. También hay un parámetro posicional \texttt{0}, cuyo valor es el nombre del script (es decir, el comando tecleado para invocarlo).
Dos variables especiales contienen todos los parámetros posicionales (excepto el parámetro posicional \texttt{0}): * y @. La diferencia entre ellas es sutil pero importante, y sólo es evidente cuando están entre comillas dobles.
\texttt{''\$*''} es una sola cadena que consta de todos los parámetros posicionales, separados por el primer carácter de la variable IFS (separador interno de campos), que por defecto es un espacio, TAB y una nueva línea. Por otra parte, \texttt{''\$@''} es igual a \texttt{''\$1>> ''\$2>> ... ''\$ N ''}, donde \emph{N} es el número de parámetros posicionales. Es decir, es igual a \emph{N} cadenas separadas por comillas dobles, separadas por espacios. Dentro de un rato exploraremos las ramificaciones de esta diferencia.
La variable \# contiene el número de parámetros posicionales (como una cadena de caracteres). Todas estas variables son de <<sólo lectura>>, lo que significa que no puede asignarles nuevos valores dentro de los scripts. (Pueden cambiarse, pero no mediante asignación. Consulte la \hyperref[sec:4.2.1.2]{Sección 4.2.1.2}, más adelante en este capítulo).
Por ejemplo, suponga que tiene el siguiente script de shell simple:
\begin{lstlisting}[language=bash]
print "fred: $*''
print ''$0: $1 and $2>>
print ''$# arguments>>
\end{lstlisting}
Supongamos además que el script se llama \emph{fred}. Entonces, si escribe \texttt{fred bob dave}, verá la siguiente salida:
\begin{lstlisting}[language=bash]
b dave
fred: bob and dave
2 arguments
\end{lstlisting}
En este caso, \texttt{\$3, \$4,} etc., no están definidos, lo que significa que el shell los sustituye por la cadena vacía (o nula) (a menos que la opción \emph{nounset} esté activada).
\subsubsection{Parámetros de Posición en las Funciones}
Las funciones de shell utilizan parámetros posicionales y variables especiales como * y \# exactamente del mismo modo que los scripts de shell. Si quisieras definir \emph{fred} como una función, podrías poner lo siguiente en tu archivo \emph{.profile} o de entorno:
\begin{lstlisting}[language=bash]
function fred {
print "fred: $*"
print ''$0: $1 and $2''
print ''$# arguments''
}
\end{lstlisting}
Obtendrás el mismo resultado si escribes \texttt{fred bob dave}.
Normalmente, se definen varias funciones de shell dentro de un único script de shell. Por lo tanto, cada función necesita manejar sus propios argumentos, lo que a su vez significa que cada función necesita hacer un seguimiento de los parámetros posicionales por separado. Por supuesto, cada función tiene sus propias copias de estas variables (aunque las funciones no se ejecuten en su propio subproceso, como los scripts); decimos que tales variables son locales a la función.
Otras variables definidas dentro de las funciones no son locales; son globales, lo que significa que sus valores son conocidos a través de todo el script de shell\footnote{Sin embargo, en la sección sobre composición tipográfica del \hyperref[sec:Chapter6]{Capítulo 6} se explica cómo hacer que las variables sean locales a las funciones.}. Por ejemplo, suponga que tiene un script de shell llamado \emph{ascript} que contiene esto:
\begin{lstlisting}[language=bash]
function afunc {
print in function $0: $1 $2
var1="in function"
}
var1="outside of function"
print var1: $var1
print $0: $1 $2
afunc funcarg1 funcarg2
print var1: $var1
print $0: $1 $2
\end{lstlisting}
Si invocas este script escribiendo \texttt{ascript arg1 arg2}, verás esta salida:
\begin{lstlisting}[language=bash]
var1: outside of function
ascript: arg1 arg2
in function afunc: funcarg1 funcarg2
var1: in function
ascript: arg1 arg2
\end{lstlisting}
En otras palabras, la función \emph{afunc} cambia el valor de la variable i\texttt{var1} de <<fuera de la función>> a <<en la función>>, y ese cambio se conoce fuera de la función, mientras que \texttt{\$0, \$1} y \texttt{\$2} tienen valores diferentes en la función y en el script principal. La Figura \ref{fig4} muestra esto gráficamente.
\begin{figure}[h!]
\centering
\includegraphics[scale=1]{fig_04}
\caption{\small{Las funciones tienen suspropios parámetros de posición}}
\label{fig4}
\end{figure}
Es posible hacer otras variables locales a funciones usando el comando \emph{typeset}, que veremos en el \hyperref[sec:Chapter6]{Capítulo 6}. Ahora que tenemos estos antecedentes, echemos un vistazo más de cerca a ''\$@'' y ''\$*''. Estas variables son dos de las mayores idiosincrasias del shell, así que discutiremos algunas de las fuentes más comunes de confusión.
\begin{itemize}
\item{¿Por qué los elementos de \texttt{''\$*''} están separados por el primer carácter de IFS en lugar de sólo por espacios? Para darle flexibilidad de salida. Como ejemplo simple, digamos que desea imprimir una lista de parámetros posicionales separados por comas. Este script lo haría:
\begin{lstlisting}[language=bash]
FS=,
print "$*"
\end{lstlisting}
Cambiar IFS en un script es bastante arriesgado, pero probablemente esté bien mientras nada más en el script dependa de ello. Si este script se llamara \emph{arglist}, el comando \texttt{arglist bob dave ed} produciría la salida \texttt{bob,dave,ed}. El \hyperref[sec:Chapter10]{Capítulo 10} contiene otro ejemplo de cambio de IFS.}
\item{¿Por qué \texttt{''\$@''} actúa como \emph{N} cadenas separadas entre comillas dobles? Para permitirle utilizarlas de nuevo como valores separados. Por ejemplo, digamos que desea llamar a una función dentro de su script con la misma lista de parámetros posicionales, así:
\begin{lstlisting}[language=bash]
function countargs {
print "$# args."
}
\end{lstlisting}
Asuma que su script es llamado con los mismos argumentos que \emph{arglist} arriba. Entonces si contiene el comando \texttt{countargs ''\$*''}, la función imprime \texttt{1 args}. Pero si el comando es \texttt{countargs ''\$@''}, la función imprime \texttt{3 args}.
Poder recuperar los argumentos tal y como entraron también es importante en caso de que necesite preservar cualquier espacio en blanco incrustado. Si tu script fue invocado con los argumentos <<hi>>, <<howdy>>, y <<hello there>>, aquí están los diferentes resultados que podrías obtener:
\begin{lstlisting}[language=bash]
$ countargs $*
4 args
$ countargs "$*"
1 args
$ countargs $@
4 args
$ countargs "$@"
3 args
\end{lstlisting}
Dado que ''\$@'' siempre conserva exactamente los argumentos, lo utilizamos en casi todos los programas de ejemplo de este libro.}
\end{itemize}
\subsubsection{Modificación de los Parámetros de Posición}\label{sec:4.2.1.2}
En ocasiones, resulta útil modificar los parámetros de posición. Ya hemos mencionado que no se pueden establecer directamente, utilizando una asignación como \texttt{1="primero"}. Sin embargo, el conjunto de comandos incorporado puede utilizarse para este propósito.
El comando \emph{set} es quizás el comando más complicado y sobrecargado del shell. Toma un gran número de opciones, las cuales se discuten en el \hyperref[sec:Chapter9]{Capítulo 9}. Lo que nos importa por el momento es que los argumentos adicionales no opcionales para \emph{set} reemplazan a los parámetros posicionales. Supongamos que nuestro script fue invocado con los tres argumentos <<bob>>, <<fred>>, y <<dave>>. Entonces \texttt{countargs "\$@"} nos dice que tenemos tres argumentos. Al usar \emph{set} para cambiar los parámetros posicionales, \texttt{\$\#} se actualiza también.
\begin{lstlisting}[language=bash]
$ set one two three "four not five" # Modificar los parametros de posicion
$ countargs "$@" # Verificar el cambio
4 args
\end{lstlisting}
El comando \emph{set} también funciona dentro de una función shell. Los parámetros de posición de la función shell se modifican, pero no los del script de llamada:
\begin{lstlisting}[language=bash]
$ function testme {
> countargs "$@" # Mostrar el numero original de parametros
> set a b c # Ahora cambiarlos
> countargs "$@" # Imprimir el nuevo recuento
> }
$ testme 1 2 3 4 5 6 # Ejecuta la funcion
6 args # Recuento original
3 args # Nuevo recuento
$ countargs "$@" # Sin cambios en los parametros del shell invocadora
4 args
\end{lstlisting}
\subsection{Más Sobre Sintaxis de Variables}
Antes de mostrar las muchas cosas que puedes hacer con las variables del shell, tenemos que hacer una confesión: la sintaxis de \texttt{\$ varname} para tomar el valor de una variable no es del todo exacta. En realidad, es la forma simple de la sintaxis más general, que es \texttt{\${varname}}.
¿Por qué dos sintaxis? Por un lado, la sintaxis más general es necesaria si su código se refiere a más de nueve parámetros posicionales: debe usar \texttt{\${10}} para el décimo en lugar de \texttt{\$10}. (Esto asegura la compatibilidad con el shell de Bourne, donde \texttt{\$10} significa \texttt{\${1}0}.) Aparte de eso, considere el ejemplo del \hyperref[sec:Chapter3]{Capítulo 3} de establecer su variable prompt primaria (PS1) a su nombre de usuario:
\begin{lstlisting}[language=bash]
PS1="($LOGNAME)-> "
\end{lstlisting}
Esto funciona porque el paréntesis derecho que sigue inmediatamente a \texttt{LOGNAME} no es un carácter válido para un nombre de variable, por lo que el shell no lo confunde con parte del nombre de la variable. Ahora suponga que, por alguna razón, quiere que su prompt sea su nombre de usuario seguido de un guión bajo. Si escribe:
\begin{lstlisting}[language=bash]
PS1="$LOGNAME_ "
\end{lstlisting}
entonces el shell intenta utilizar \texttt{''LOGNAME\_'' }como nombre de la variable, es decir, tomar el valor de \texttt{\$LOGNAME\_}. Como no existe tal variable, el valor por defecto es \emph{null} (la cadena vacía, ''''), y PS1 se establece sólo con un espacio.
Por esta razón, la sintaxis completa para tomar el valor de una variable es \texttt{\${ varname }}. Así que si usamos:
\begin{lstlisting}[language=bash]
PS1="${LOGNAME}_ "
\end{lstlisting}
obtendríamos el deseado \texttt{tunombre\_}. Es seguro omitir las llaves (\{\}) si el nombre de la variable va seguido de un carácter que no sea una letra, un dígito o un guión bajo.
\subsection{Añadir una Variable}
Como se ha mencionado, las variables del shell de Korn tienden a estar orientadas a cadenas. Una operación muy común es añadir un nuevo valor a una variable existente. (Por ejemplo, reunir un conjunto de opciones en una sola cadena.) Desde tiempos inmemoriales, esto se hacía aprovechando la sustitución de variables dentro de comillas dobles:
\begin{lstlisting}[language=bash]
myopts="$myopts $newopt"
\end{lstlisting}
Los valores de \texttt{myopts} y \texttt{newopt} se concatenan en una sola cadena, y el resultado se asigna de nuevo a \texttt{myopts}. A partir de \emph{ksh93j}, el shell de Korn proporciona un mecanismo más eficiente e intuitivo para hacer esto:
\begin{lstlisting}[language=bash]
myopts+=" $newopt"
\end{lstlisting}
Esto consigue lo mismo, pero es más eficiente, y además deja claro que el nuevo valor se está añadiendo a la cadena. (En C, el operador += añade el valor de la derecha a la variable de la izquierda; \texttt{x += 42} es lo mismo que \texttt{ = x + 42}).
\section{Variables Compuestas}
\emph{ksh93} introduce una nueva característica, llamada \emph{variables compuestas}. Son similares en naturaleza a un registro de Pascal o Ada o a una estructura de C, y le permiten agrupar elementos relacionados bajo el mismo nombre. He aquí algunos ejemplos:
\begin{lstlisting}[language=bash]
now="May 20 2001 19:44:57" # Asignar la fecha actual a la variable now
now.hour=19 # Establecer la hora
now.minute=44 # Establecer el minuto
\end{lstlisting}
Observe el uso del punto en el nombre de la variable. Aquí, \texttt{now} se denomina variable padre, y debe existir (es decir, tener un valor) antes de que pueda asignar un valor a un componente individual (como \texttt{hour} o \texttt{minute}). Para acceder a una variable compuesta, debe encerrar el nombre de la variable entre llaves. Si no lo hace, el punto finaliza la búsqueda del nombre de la variable por parte del shell:
\begin{lstlisting}[language=bash]
$ print ${now.hour}
19
$ print $now.hour
May 20 2001 19:44:57.hour
\end{lstlisting}
\subsection{Asignación de Variables Compuestas}
La asignación a elementos individuales de una variable compuesta es tediosa. En particular, el requisito de que la variable padre exista previamente conduce a un estilo de programación incómodo:
\begin{lstlisting}[language=bash]
person="John Q. Public"
person.firstname=John
person.initial=Q.
person.lastname=Public
\end{lstlisting}
Afortunadamente, puedes utilizar una asignación compuesta para hacerlo todo de una sola vez:
\begin{lstlisting}[language=bash]
person=(firstname=John initial=Q. lastname=Public
\end{lstlisting}
Puede recuperar el valor de toda la variable, o de un componente, utilizando \emph{print}.
\begin{lstlisting}[language=bash]
$ print $person # Impresion sencilla
( lastname=Public initial=Q. firstname=John )
$ print -r "$person" # Impresion a todo su esplendor
(
lastname=Public
initial=Q.
firstname=John
)
$ print ${person.initial} # Imprimir solo la inicial del segundo nombre
Q.
\end{lstlisting}
El segundo comando \emph{print} preserva los espacios en blanco que el shell de Korn proporciona cuando devuelve el valor de una variable compuesta. La opción \emph{-r} para imprimir se discute en el \hyperref[sec:Chapter7]{Capítulo 7}.
\textbf{NOTA:} El orden de los componentes es diferente del utilizado en la asignación inicial. Este orden depende de cómo el shell de Korn gestiona internamente las variables compuestas y no puede ser controlado por el programador.
Existe una segunda sintaxis de asignación, similar a la primera:
\begin{lstlisting}[language=bash]
person=(typeset firstname=John initial=Q. lastname=Public ;
typeset -i age=42)
\end{lstlisting}
Utilizando el comando \emph{typeset}, puede especificar que una variable es un número en lugar de una cadena. Aquí, \texttt{person.age} es una variable entera. El resto siguen siendo cadenas. El comando \emph{typeset} y sus opciones se presentan en el \hyperref[sec:Chapter6]{Capítulo 6}. (También puede utilizar \emph{readonly} para declarar que una variable componente no puede modificarse).
Del mismo modo que puede utilizar += para añadir a una variable normal, también puede añadir componentes a una variable compuesta:
\begin{lstlisting}[language=bash]
person+= (typeset spouse=Jane)
\end{lstlisting}
Se permite un espacio después del = pero no antes. Esto se aplica a las asignaciones compuestas con = y +=.
El shell de Korn tiene sintaxis adicionales para asignaciones compuestas que se aplican sólo a variables de array; también se discuten en el \hyperref[sec:Chapter6]{Capítulo 6}.
Finalmente, mencionaremos que el shell de Korn tiene una variable compuesta especial llamada \texttt{.sh}. Los diversos componentes se relacionan casi todos con características que aún no hemos cubierto, excepto \texttt{\${.sh.version}}, que le indica la versión del shell de Korn que tiene:
\begin{lstlisting}[language=bash]
$ print ${.sh.version}
Version M 1993-12-28 m
\end{lstlisting}
Veremos otro componente de \texttt{.sh} más adelante en este capítulo, y los otros componentes se cubren a medida que introducimos las características con las que se relacionan.
\section{Referencias Indirectas a Variables (namerefs)}\label{sec:4.4}
La mayoría de las veces, como hemos visto hasta ahora, se manipulan variables directamente, por su nombre (x=1, por ejemplo). El shell de Korn le permite manipular variables indirectamente, usando algo llamado \emph{nameref}. Puedes crear un \emph{nameref} usando \texttt{typeset -n}, o el más conveniente alias predefinido, \emph{nameref}. He aquí un ejemplo sencillo:
\begin{lstlisting}[language=bash]
$ name="bill" # Establecer valor inicial
$ nameref firstname=name # Configura nameref
$ print $firstname # En realidad hace referencia a la variable nameref
bill
$ firstname="arnold" # Ahora cambia la referencia indirecta
$ print $name # Se cambia la variable original
arnold
\end{lstlisting}
Para averiguar el nombre de la variable real a la que hace referencia el \emph{nameref}, utilice \texttt{\${! variable }}:
\begin{lstlisting}[language=bash]
$ print ${!firstname}
name
\end{lstlisting}
A primera vista, esto no parece muy útil. El poder de \emph{namerefs} entra en juego cuando pasas el nombre de una variable a una función, y quieres que esa función pueda actualizar el valor de esa variable. El siguiente ejemplo ilustra cómo funciona:
\begin{lstlisting}[language=bash]
$ date # Dia y horas actuales
Wed May 23 17:49:44 IDT 2001
$ function getday { # Definir una funcion
> typeset -n day=$1 # Configurar el nombre
> day=$(date | awk '{ print $1 }') # Cambiarlo realmente
> }
$ today=now # Establece el valor inicial
$ getday today # Ejecutar la funcion
$ print $today # Mostrar el valor nuevo
Wed
\end{lstlisting}
La salida por defecto de \emph{date(1)} tiene este aspecto:
\begin{lstlisting}[language=bash]
$ date
Wed Nov 14 11:52:38 IST 2001
\end{lstlisting}
La función \emph{getday} utiliza \emph{awk} para imprimir el primer campo, que es el día de la semana. El resultado de esta operación, que se realiza dentro de la sustitución de comandos (descrita más adelante en este capítulo), se asigna a la variable local \texttt{day}. Pero \texttt{day} es una referencia de nombre; la asignación en realidad actualiza la variable global \texttt{today}. Sin la función \emph{nameref}, tendrá que recurrir a trucos avanzados como el uso de \emph{eval} (véase el capítulo 7) para hacer que ocurra algo como esto.
Para eliminar un \emph{nameref}, utilice \texttt{unset -n}, que elimina el propio \emph{nameref}, en lugar de eliminar la variable a la que el \emph{nameref} hace referencia. Por último, tenga en cuenta que las variables que son \emph{namerefs} no pueden tener puntos en sus nombres (es decir, ser componentes de una variable compuesta). Sin embargo, pueden ser referencias a una variable compuesta.
\section{Operadores de Cadena}
La sintaxis de llaves permite utilizar los \emph{operadores de cadena} del shell. Los operadores de cadena le permiten manipular valores de variables de varias formas útiles sin tener que escribir programas completos o recurrir a utilidades externas de Unix. Puede hacer mucho con los operadores de manejo de cadenas incluso si todavía no domina las características de programación que veremos en capítulos posteriores.
En particular, los operadores de cadena te permiten hacer lo siguiente:
\begin{itemize}
\item{Asegurarse de que las variables existen (es decir, están definidas y tienen valores no nulos).}
\item{Establecer valores por defecto para las variables.}
\item{Detectar errores debidos a variables no definidas.}
\item{Eliminar partes de los valores de las variables que coincidan con patrones.}
\end{itemize}
\subsection{Sintaxis de los Operadores de Cadena}
La idea básica detrás de la sintaxis de los operadores de cadena es que los caracteres especiales que denotan operaciones se insertan entre el nombre de la variable y la llave derecha. Cualquier argumento que el operador pueda necesitar se inserta a la derecha del operador.
El primer grupo de operadores de cadena comprueba la existencia de variables y permite sustituirlas por valores por defecto en determinadas condiciones. Se enumeran en la Tabla \ref{Tab: sustitución}.
\newpage
\begin{table}[h]
\center
\caption{Operadores de sustitución}
\label{Tab: sustitución}
\begin{tabular}{|m{4cm}|m{11cm}|} \hline
\textbf{Operador} & \textbf{Sustitución} \\ \hline
\texttt{\$\{varname:-word\}} & Si \emph{varname} existe y no es nulo, devuelve su valor; en caso contrario devuelve word. \\
\textbf{Propósito:} & Devolver un valor por defecto si la variable no está definida. \\
\textbf{Ejemplo:} & \texttt{\${count:-0}} se evalúa a 0 si \emph{count} no está definido. \\ \hline
\texttt{\$\{varname:=word\}} & Si \emph{varname} existe y no es nulo, devuelve su valor; en caso contrario, lo establece en word y luego devuelve su valor.\tablefootnote{Los programadores de Pascal, Modula y Ada pueden encontrar útil reconocer la similitud de esto con los operadores de asignación en esos lenguajes.} \\
\textbf{Propósito:} & Asignar a una variable un valor por defecto si no está definida. \\
\textbf{Ejemplo:} & \texttt{\${count:=0}} establece el recuento a 0 si no está definido. \\ \hline
\texttt{\$\{varname:?word\}} & Si \emph{varname} existe y no es \emph{null}, devuelve su valor; de lo contrario imprime \texttt{varname : message}, y aborta el comando o script actual. Omitir mensaje produce el parámetro mensaje por defecto nulo o no establecido. Tenga en cuenta, sin embargo, que los shells interactivos no abortan. \\
\textbf{Propósito:} & Atrapar errores que resultan de variables no definidas. \\
\textbf{Ejemplo:} & \texttt{\$\{count:? ''undefined!''\}} imprime \texttt{count: ¡undefined!} y sale si count es undefined. \\ \hline
\texttt{\$\{varname:+word\}} & Si \emph{varname} existe y no es \emph{null}, devuelve word; en caso contrario devuelve null. \\
\textbf{Objetivo} & Comprobar la existencia de una variable. \\
\textbf{Ejemplo:} & \texttt{\$\{count:+1\}} devuelve 1 (que podría significar "verdadero") si \emph{count} está definido. \\\hline
\end{tabular}
\end{table}
Los dos puntos \texttt{(:)} en cada uno de estos operadores son opcionales. Si se omiten los dos puntos, cambie <<existe y no es nulo>> por <<existe>> en cada definición, es decir, el operador sólo comprueba la existencia.
Los dos primeros operadores son ideales para establecer los valores predeterminados de los argumentos de la línea de comandos en caso de que el usuario los omita. De hecho, utilizaremos los cuatro en la \hyperref[box:4-1]{Tarea 4-1}, que es nuestra primera tarea de programación.
\begin{mybox}[Tarea 4-1]\label{box:4-1}
Tienes una gran colección de álbumes y quieres escribir un programa para llevar un registro de ella. Supongamos que tienes un fichero de datos sobre cuántos álbumes tienes de cada artista. Las líneas del fichero tienen este aspecto:
\begin{lstlisting}[language=bash]
14 Bach, J.S.
1 Balachander, S.
21 Beatles
6 Blakey, Art
\end{lstlisting}
Escriba un programa que imprima las \emph{N} líneas más altas, es decir, los \emph{N} artistas de los que tiene más álbumes. El valor por defecto para \emph{N} debería ser 10. El programa debe tomar un argumento para el nombre del archivo de entrada y un segundo argumento opcional para el número de líneas a imprimir.
\end{mybox}
Con diferencia, el mejor enfoque para este tipo de script es utilizar las utilidades integradas de Unix, combinándolas con redireccionadores de E/S y tuberías. Esta es la clásica filosofía <<building-block>> de Unix que es otra de las razones de su gran popularidad entre los programadores. La técnica del bloque de construcción nos permite escribir una primera versión del script de una sola línea:
\begin{lstlisting}[language=bash]
sort -nr "$1" | head -${2:-10}
\end{lstlisting}
Así es como funciona: el programa \emph{sort(1)} ordena los datos del fichero cuyo nombre se da como primer argumento \texttt{(\$1)}. (Las comillas dobles permiten espacios u otros caracteres inusuales en los nombres de fichero, y también evitan la expansión de comodines). La opción \emph{-n} indica a \emph{sort} que interprete la primera palabra de cada línea como un número (en lugar de como una cadena de caracteres); la opción \emph{-r} le indica que invierta las comparaciones, para ordenar en orden descendente.
La salida de \emph{sort} se envía a la utilidad \emph{head(1)}, que, cuando recibe el argumento \emph{-N}, imprime las primeras N líneas de su entrada en la salida estándar. La expresión \texttt{-\${2:-10}} se evalúa como un guión (-) seguido del segundo argumento, si se da, o como \texttt{10} si no se da; observe que la variable en esta expresión es \texttt{2}, que es el segundo parámetro posicional.
Supongamos que el script que queremos escribir se llama \emph{highest}. Entonces si el usuario escribe \texttt{highest myfile}, la línea que realmente se ejecuta es:
\begin{lstlisting}[language=bash]
sort -nr myfile | head -10
\end{lstlisting}
O si el usuario escribe highest \texttt{myfile 22}, la línea que se ejecuta es:
\begin{lstlisting}[language=bash]
sort -nr myfile | head -22
\end{lstlisting}
Asegúrese de que entiende cómo el operador de cadena \texttt{:-} proporciona un valor por defecto.
Este es un script perfectamente bueno y ejecutable, pero tiene algunos problemas. Primero, su única línea es un poco crítica. Aunque esto no es un gran problema para un script tan pequeño, no es aconsejable escribir scripts largos y elaborados de esta manera. Unos pequeños cambios hacen que el código sea más legible.
Primero, podemos añadir comentarios al código; cualquier cosa entre \# y el final de una línea es un comentario. Como mínimo, el script debería empezar con unas pocas líneas de comentario que indiquen lo que hace el script y los argumentos que acepta. A continuación, podemos mejorar los nombres de las variables asignando los valores de los parámetros posicionales a variables regulares con nombres mnemotécnicos. Por último, podemos añadir líneas en blanco para espaciar las cosas; las líneas en blanco, como los comentarios, se ignoran. He aquí una versión más legible:
\begin{lstlisting}[language=bash]
# highest filename [howmany]
#
# Print howmany highest-numbered lines in file filename.
# The input file is assumed to have lines that start with
# numbers. Default for howmany is 10.
filename=$1
howmany=${2:-10}
sort -nr "$filename" | head -$howmany
\end{lstlisting}
Los corchetes alrededor de \texttt{howmany} en los comentarios se adhieren a la convención en la documentación Unix de que los corchetes denotan argumentos opcionales.
Los cambios que acabamos de hacer mejoran la legibilidad del código, pero no su ejecución. ¿Qué pasaría si el usuario invocara el script sin ningún argumento? Recuerde que los parámetros posicionales por defecto son \emph{null} si no están definidos. Si no hay argumentos, entonces \texttt{\$1} y \texttt{\$2} son nulos. La variable howmany \texttt(\$2) está configurada por defecto a 10, pero no hay valor por defecto para \texttt{filename (\$1)}. El resultado sería que este comando se ejecuta:
\begin{lstlisting}[language=bash]
sort -nr | head -10
\end{lstlisting}
Si se llama a \emph{sort} sin un argumento de nombre de fichero, se espera que la entrada provenga de la entrada estándar, por ejemplo, una tubería (|) o el teclado del usuario. Como no tiene la tubería, esperará el teclado. Esto significa que el script parecerá colgarse. Aunque siempre puedes teclear CTRL-D o CTRL-C para salir del script, un usuario ingenuo podría no saberlo.
Por lo tanto, tenemos que asegurarnos de que el usuario proporciona al menos un argumento. Hay varias formas de hacerlo; una de ellas implica otro operador de cadena. Reemplazaremos la línea
\begin{lstlisting}[language=bash]
filename=$1
\end{lstlisting}
Con:
\begin{lstlisting}[language=bash]
filename=${1:?"filename missing."}
\end{lstlisting}
Esto hace que ocurran dos cosas si un usuario invoca el script sin ningún argumento: en primer lugar, el shell imprime el mensaje un tanto desafortunado en la salida de error estándar:
\begin{lstlisting}[language=bash]
highest: line 1: : filename missing.
\end{lstlisting}
En segundo lugar, el script sale sin ejecutar el código restante.
Con una modificación algo <<chapucera>>, podemos obtener un mensaje de error ligeramente mejor. Considere este código:
\begin{lstlisting}[language=bash]
filename=$1
filename=${filename:?"missing."}
\end{lstlisting}
El resultado es el mensaje:
\begin{lstlisting}[language=bash]
highest: line 2: filename: filename missing.
\end{lstlisting}
(Asegúrese de entender por qué.) Por supuesto, hay formas de imprimir cualquier mensaje que se desee; veremos cómo en el \hyperref[sec:Chapter5]{Capítulo 5}.
Antes de continuar, examinaremos más detenidamente los dos operadores restantes de la Tabla \ref{Tab: sustitución} y veremos cómo podemos incorporarlos a la solución de nuestra tarea. El operador := hace aproximadamente lo mismo que :-, excepto que tiene el efecto secundario de establecer el valor de la variable a la palabra dada si la variable no existe.
Por lo tanto, nos gustaría utilizar := en nuestro script en lugar de :-, pero no podemos; estaríamos intentando establecer el valor de un parámetro posicional, lo que no está permitido. Pero si sustituimos:
\begin{lstlisting}[language=bash]
howmany=${2:-10}
\end{lstlisting}
Con solo:
\begin{lstlisting}[language=bash]
howmany=$2
\end{lstlisting}
y trasladamos la sustitución a la línea de órdenes real (como hicimos al principio), entonces podríamos utilizar el operador :=:
\begin{lstlisting}[language=bash]
sort -nr "$filename" | head -${howmany:=10}
\end{lstlisting}
El uso de := tiene la ventaja añadida de fijar el valor de \texttt{howmany} en 10 por si lo necesitamos posteriormente en versiones posteriores del script.
El último operador de sustitución es \texttt{:+}. Así es como podemos utilizarlo en nuestro ejemplo: digamos que queremos dar al usuario la opción de añadir una línea de cabecera a la salida del script. Si teclea la opción \emph{-h}, la salida irá precedida por la línea:
\begin{lstlisting}[language=bash]
ALBUMS ARTIST
\end{lstlisting}
Suponga además que esta opción termina en la variable \texttt{header}, es decir, \texttt{\$header} si la opción \emph{-h} está establecida o nula si no lo está. (Más adelante veremos cómo hacer esto sin alterar los otros parámetros posicionales).
La expresión:
\begin{lstlisting}[language=bash]
${header:+"ALBUMS ARTIST\n"}
\end{lstlisting}
produce \emph{null} si la variable \emph{header} es nula o \texttt{ALBUMS ARTIST\textbackslash{} n} si no es nula. Esto significa que podemos poner la línea:
\begin{lstlisting}[language=bash]
print -n ${header:+"ALBUMS ARTIST\n"}
\end{lstlisting}
justo antes de la línea de comando que hace el trabajo real. La opción \emph{-n} para imprimir hace que no se imprima una nueva línea después de imprimir sus argumentos. Por lo tanto, esta declaración de impresión no imprime nada, ni siquiera una línea en blanco, si \texttt{headeres} nulo; de lo contrario, imprime la línea de encabezado y una nueva línea (\textbackslash{} n).
\subsection{Patrones y Expresiones Regulares}
Continuaremos refinando nuestra solución a la \hyperref[box:4-1]{Tarea 4-1} más adelante en este capítulo. El siguiente tipo de operador de cadena se utiliza para comparar partes del valor de cadena de una variable con patrones. Los patrones, como vimos en el \hyperref[sec:Chapter1]{Capítulo 1}, son cadenas que pueden contener caracteres comodín \texttt{(*, ? y [ ]} para conjuntos y rangos de caracteres).
Los comodines han sido características estándar de todos los intérpretes de comandos Unix que se remontan (al menos) al intérprete de comandos Thompson de la versión 6.\footnote{El shell de la versión 6 fue escrito por Ken Thompson. Stephen Bourne escribió el intérprete de comandos Bourne para la versión 7.} Pero el intérprete de comandos Korn es el primero en añadir capacidades. Añade un conjunto de operadores, llamados \emph{operadores de expresión regular (o regexp para abreviar)}, que le dan mucho del poder de comparación de cadenas de utilidades Unix avanzadas como \emph{awk(1), egrep(1)} (\emph{grep(1)} extendido), y el editor Emacs, aunque con una sintaxis diferente. Estas capacidades van más allá de aquellas a las que puede estar acostumbrado en otras utilidades Unix como \emph{grep, sed(1)} y \emph{vi(1)}.
Los usuarios avanzados de Unix encontrarán útiles las capacidades de expresión regular del shell de Korn para la escritura de scripts, aunque rozan la exageración. (Parte del problema es el inevitable choque sintáctico con la miríada de otros caracteres especiales del shell). Por lo tanto, no entraremos aquí en grandes detalles sobre las expresiones regulares. Para una información más completa, la <<última palabra>> sobre expresiones regulares prácticas en Unix es \emph{Mastering Regular Expressions}, por Jeffrey E. F. Friedl. Una introducción más suave puede encontrarse en la segunda edición de \emph{sed \& awk}, por Dale Dougherty y Arnold Robbins. Ambos están publicados por O'Reilly \& Associates. Si ya se siente cómodo con \emph{awk} o \emph{egrep}, puede saltarse la siguiente sección introductoria y pasar a la \hyperref[sec:4523]{Sección 4.5.2.3}, más adelante en este capítulo, donde explicamos el mecanismo de expresiones regulares del intérprete de órdenes comparándolo con la sintaxis utilizada en esas dos utilidades. De lo contrario, siga leyendo.
\subsubsection{Conceptos Básicos de Expresiones Regulares}
Piense en las expresiones regulares como cadenas de caracteres que emparejan patrones de forma más potente que el esquema de comodines estándar del shell. Las expresiones regulares surgieron como una idea de la informática teórica, pero han llegado a muchos rincones de la informática práctica cotidiana. La sintaxis utilizada para representarlas puede variar, pero los conceptos son muy parecidos.
Una expresión regular de shell puede contener caracteres regulares, caracteres comodín estándar y operadores adicionales más potentes que los comodines. Cada uno de estos operadores tiene la forma \emph{x(exp)}, donde \emph{x} es el operador concreto y \emph{exp} es cualquier expresión regular (a menudo simplemente una cadena regular). El operador determina cuántas ocurrencias de \emph{exp} puede contener una cadena que coincida con el patrón. La Tabla \ref{Tab: expresiones} describe los operadores de expresión regular del shell y sus significados.
\newpage
\begin{table}[h]
\center
\caption{Operadores de expresiones regulares}
\label{Tab: expresiones}
\begin{tabular}{|m{5cm}|m{6cm}|} \hline
\textbf{Operador} & \textbf{Significado} \\ \hline
\texttt{*(exp)} & Cero ó más ocurrencias de \emph{exp} \\\hline
\texttt{+(exp)} & Uno ó más ocurrencias de \emph{exp} \\\hline
\texttt{?(exp)} & Cero o una ocurrencia de \emph{exp} \\\hline
\texttt{@(exp1|exp2|...)} & Exactamente uno de \emph{exp1} ó \emph{exp2} ó ... \\\hline
\texttt{!(exp)} & Todo lo que no coincida con exp \tablefootnote{En realidad, !( exp ) no es un operador de expresión regular según la definición técnica estándar, aunque es una extensión práctica.} \\\hline
\end{tabular}
\end{table}
Como se muestra para el patrón \texttt{@( exp1 | exp2 |...)}, un \emph{exp} dentro de cualquiera de los operadores del shell de Korn puede ser una serie de alternativas \emph{exp1|exp2|...}
Una notación alternativa poco conocida es separar cada \emph{exp} con el carácter ampersand, \&. En este caso, todas las expresiones alternativas deben coincidir. Piense que | significa <<o>>, mientras que \& significa <<y>>. (De hecho, puede utilizar ambos en la misma lista de patrones. El \& tiene mayor precedencia, con el significado <<coincide con esto y aquello, O coincide con lo siguiente>>). La Tabla \ref{Tab: ejemplos} proporciona algunos ejemplos de uso de los operadores de expresiones regulares del shell.
\begin{table}[h]
\center
\caption{Ejemplos de operadores de expresiones regulares}
\label{Tab: ejemplos}
\begin{tabular}{|p{3cm}|p{6cm}|} \hline
\textbf{Expresión} & \textbf{Coincidencia} \\ \hline
\emph{x} & x \\\hline
\emph{+(x)} & Cadena nula, \texttt{x, xx, xxx, ...} \\\hline
\emph{+(x)} & \texttt{x, xx, xxx, ...} \\\hline
\emph{?(X)} & Cadena nula, \emph{x} \\\hline
\emph{!(X)} & Cualquier cadena excepto \emph{x} \\\hline
\emph{@(X)} & \emph{x} (ver abajo) \\\hline
\end{tabular}
\end{table}
Las expresiones regulares son extremadamente útiles cuando se trata de texto arbitrario, como ya sabrá si ha usado \emph{grep} o las capacidades de expresión regular de cualquier editor de Unix. No son tan útiles para hacer coincidir nombres de archivo y otros tipos simples de información con los que los usuarios de shell suelen trabajar. Además, la mayoría de las cosas que puede hacer con los operadores de expresiones regulares del shell también se pueden hacer (aunque posiblemente con más pulsaciones de teclas y menos eficiencia) canalizando la salida de un comando del shell a través de \emph{grep} o \emph{egrep}.
Sin embargo, aquí hay algunos ejemplos de cómo las expresiones regulares de shell pueden resolver problemas de listado de nombres de archivo. Algunos de estos serán útiles en capítulos posteriores como piezas de soluciones para tareas más grandes.
\begin{enumerate}
\item{El editor de Emacs admite archivos de personalización cuyos nombres terminan en \emph{.el} (para Emacs LISP) o \emph{.elc} (para Emacs LISP compilado). Muestra todos los archivos de personalización de Emacs en el directorio actual.}
\item{En un directorio de código fuente C, enumere todos los archivos que no son necesarios. Suponga que los archivos <<necesarios>> terminan en \emph{.co .ho} se denominan \emph{Makefile} o \emph{README}.}
\item{Los nombres de archivo en el sistema operativo OpenVMS terminan en un punto y coma seguido de un número de versión, por ejemplo, \emph{fred.bob;23}. Muestra todos los nombres de archivo de estilo OpenVMS en el directorio actual.}
\end{enumerate}
Aquí están las soluciones:
\begin{enumerate}
\item{En el primero de ellos, buscamos archivos que terminen en .el con una c opcional . La expresión que coincide con esto es \emph{*.el?(c)}.}
\item{El segundo ejemplo depende de las cuatro subexpresiones estándar \emph{*.c, *.h, Makefile} y \emph{README}. La expresión completa es \texttt{!(*.c|*.h|Makefile|README)}, que coincide con cualquier cosa que no coincida con ninguna de las cuatro posibilidades.}
\item{La solución al tercer ejemplo comienza con \texttt{*\textbackslash{}};, el comodín de shell *seguido de un punto y coma con barra invertida. Entonces, podríamos usar la expresión regular \texttt{+([0-9])}, que coincide con uno o más caracteres en el rango [0-9], es decir, uno o más dígitos. Esto es casi correcto (y probablemente lo suficientemente cerca), pero no tiene en cuenta que el primer dígito no puede ser 0. Por lo tanto, la expresión correcta es \texttt{*\;[1-9]*([0-9])}, que coincide con cualquier cosa que termine con un punto y coma, un dígito del 1 al 9 y cero o más dígitos del 0 al 9.}
\end{enumerate}
\subsubsection{Adiciones de Clases de Caracteres POSIX}
El estándar POSIX formaliza el significado de los caracteres y operadores de expresiones regulares. El estándar define dos clases de expresiones regulares: expresiones regulares básicas (BRE), que son del tipo utilizado por \emph{grep} y \emph{sed} , y expresiones regulares extendidas, que son del tipo utilizado por \emph{egrep} y \emph{awk}.
Para adaptarse a entornos no ingleses, el estándar POSIX mejoró la capacidad de los rangos de juegos de caracteres (p. ej., \texttt{[a-z]}) para hacer coincidir caracteres que no están en el alfabeto inglés. Por ejemplo, el è francés es un carácter alfabético, pero la clase de carácter típica \texttt{[a-z]} no lo coincidiría. Además, el estándar proporciona secuencias de caracteres que deben tratarse como una sola unidad al hacer coincidir y cotejar (ordenar) datos de cadena. (Por ejemplo, hay lugares donde los dos caracteres \texttt{ch} se tratan como una unidad y deben coincidir y ordenarse de esa manera).
POSIX también cambió lo que había sido la terminología común. Lo que vimos anteriormente en el \hyperref[sec:Chapter1]{Capítulo 1} como una <<expresión de rango>> a menudo se denomina <<clase de caracteres>> en la literatura de Unix. Ahora se llama una <<expresión de paréntesis>> en el estándar POSIX. Dentro de las expresiones entre paréntesis, además de los caracteres literales como a, ;, etc., también puede tener componentes adicionales:
\begin{description}
\item[Tipo de carácter:] Una clase de caracteres POSIX consta de palabras clave entre paréntesis \texttt{[: y :]}. Las palabras clave describen diferentes clases de caracteres, como caracteres alfabéticos, caracteres de control, etc. (consulte la Tabla \ref{Tab: posix}).
\item[Clasificación de símbolos:] Un símbolo de clasificación es una secuencia de varios caracteres que debe tratarse como una unidad. Se compone de los caracteres entre paréntesis \texttt{[. y .]}.
\item[Clases de equivalencias:] Una clase de equivalencia enumera un conjunto de caracteres que deben considerarse equivalentes, como \texttt{e} y \texttt{è}. Consiste en un elemento con nombre de la configuración regional, entre paréntesis \texttt{[= y =]}.
\end{description}
Las tres construcciones deben aparecer dentro de los corchetes de una expresión entre corchetes. Por ejemplo \texttt{[[:alpha:]!]}, coincide con cualquier carácter alfabético o el signo de exclamación; \texttt{[[.ch.]]} coincide con el elemento de clasificación, \texttt{ch} pero no coincide solo con la letra \texttt{c} o la letra \texttt{h}. En una configuración regional francesa, \texttt{[[=e=]]} puede coincidir con cualquiera de \texttt{e, è} o \texttt{é}. Las clases y los caracteres coincidentes se muestran en la Tabla \ref{Tab: posix}.
\newpage
\begin{table}[h]
\center
\caption{Clases de caracteres POSIX}
\label{Tab: posix}
\begin{tabular}{|m{3cm}|m{10cm}|} \hline
\textbf{Caracter} & \textbf{Significado} \\ \hline
\texttt{[:alnum:]} & Caracteres alfanuméricos \\
\texttt{[:alpha:]} & Caracteres alfabéticos \\
\texttt{[:blank:]} & Caracteres de espacios y tabulación \\
\texttt{[:cntrl:]} & Caracteres de control \\
\texttt{[:digit:]} & Caracteres numéricos \\
\texttt{[:graph:]} & Caracteres imprimibles y visibles (sin espacios) \\
\texttt{[:lower:]} & Caracteres en minúsculas \\
\texttt{[:print:]} & Caracteres imprimibles (incluidos los espacios en blanco) \\
\texttt{[:punct:]} & Caracteres de puntuación \\
\texttt{[:space:]} & Caracteres de espacio en blanco \\
\texttt{[:upper:]} & Caracteres en mayúsculas \\
\texttt{[:xdigit:]} & Dígitos hexadecimales \\\hline
\end{tabular}
\end{table}
El shell de Korn soporta todas estas características dentro de sus facilidades de concordancia de patrones. Los nombres de clases de caracteres POSIX son los más útiles, porque funcionan en diferentes localizaciones.
La siguiente sección compara las expresiones regulares del shell de Korn con las características análogas de \emph{awk} y \emph{egrep}. Si no está familiarizado con ellas, vaya a la \hyperref[sec:453]{Sección 4.5.3}.
\subsubsection{Korn Shell Versus Expresiones Regulares awk/egrep}\label{sec:4523}
La Tabla \ref{Tab: operadores} es una expansión de la Tabla \ref{Tab: expresiones}: la columna del medio muestra los equivalentes en \emph{awk/egrep} de los operadores de expresiones regulares del shell.
\begin{table}[h]
\center
\caption{Operadores de expresión regular del shell frente a egrep/awk}
\label{Tab: operadores}
\begin{tabular}{|m{4cm}|m{4cm}|m{7cm}|} \hline
\textbf{Korn shell} & \textbf{egrep/awk} & \textbf{Significado} \\ \hline
\texttt{*(exp)} & \texttt{exp*} & Cero o más ocurrencias de \emph{exp} \\
\texttt{+(exp)} & \texttt{exp+} & Uno o más ocurrencias de \emph{exp} \\
\texttt{?(exp)} & \texttt{exp?} & Cero o una ocurrencia de \emph{exp} \\
\texttt{@(exp1|exp2|...)} & \texttt{exp1 | exp2 |...} & \emph{exp1} ó \emph{exp2} ó ... \\
\texttt{!(exp)} & \texttt{(none)} & Todo lo que no coincida con exp \\
\texttt{\textbackslash{} N} & \texttt{\textbackslash{} N (grep)} & Coincide con el mismo texto que la subexpresión anterior entre paréntesis número N \\\hline
\end{tabular}
\end{table}
Estos equivalentes son cercanos pero no del todo exactos. Debido a que el shell interpretaría una expresión \texttt{dave|fred|bob} como una canalización de comandos, debe usar \texttt{@(dave|fred|bob)} para alternativas por sí mismos.
El comando \emph{grep} tiene una función llamada \emph{backreferences} (o \emph{backrefs}, para abreviar). Esta función proporciona una abreviatura para repetir partes de una expresión regular como parte de un todo mayor. Funciona de la siguiente manera:
\begin{lstlisting}[language=bash]
grep '\(abc\).*\1' archivo1 archivo2
\end{lstlisting}
Esto coincide con \emph{abc}, seguido de cualquier número de caracteres, seguido de nuevo por \emph{abc}. De esta forma se pueden referenciar hasta nueve subexpresiones entre paréntesis. El intérprete de comandos Korn ofrece una función análoga. Si utiliza uno o más patrones de expresión regular dentro de un patrón completo, puede hacer referencia a los anteriores utilizando la notación \texttt{\textbackslash{} N} como para \emph{grep}.
Por ejemplo:
\begin{itemize}
\item{\texttt{@(dave|fred|bob)} coincide con \emph{dave, fred o bob}.}
\item{\texttt{@(*dave*\&*fred*)} coincide con \emph{davefred} y \emph{freddave}. (Observe la necesidad de los caracteres *).}
\item{\texttt{@(fred)*\textbackslash{}1} Coincide con \emph{freddavefred, fredbobfred}, etc.}
\item{\texttt{*(dave|fred|bob)} significa <<0 o más apariciones de \emph{dave, fred o bob}>>. Esta expresión coincide con cadenas como la cadena nula, \emph{dave, davedave, fred, bobfred, bobbobdavefredbobfred}, etc.}
\item{\texttt{+(dave|fred|bob)} coincide con cualquiera de las anteriores excepto con la cadena nula.}
\item{\texttt{?(dave|fred|bob)} coincide con la cadena nula, \emph{dave, fred o bob}.}
\item{\texttt{!(dave|fred|bob)} coincide con cualquier cosa excepto \emph{dave, fred o bob}.}
\end{itemize}
Vale la pena recalcar que las expresiones regulares del shell pueden contener comodines estándar del shell. Así, el comodín del shell \texttt{?} (coincide con cualquier carácter) es equivalente a \texttt{.} en \emph{egrep} o \emph{awk}, y el operador de conjunto de caracteres del shell \texttt{[...]} es el mismo que en esas utilidades.\footnote{Y, para el caso, lo mismo que en \emph{grep, sed, ed, vi,} etc. Una diferencia notable es que el shell utiliza ! dentro de [...] para la negación, mientras que las distintas utilidades utilizan todas \textasciicircum.} Por ejemplo, la expresión \texttt{+([[:dígito:]])} coincide con un número, es decir, uno o más dígitos. El carácter comodín del shell * es equivalente a la expresión regular del shell \texttt{*(?)}. Incluso puede anidar las expresiones regulares: \texttt{+([[:dígito:]]|!([[:mayúscula:]]))} coincide con uno o más dígitos o letras no mayúsculas.
Dos operadores regexp de x\emph{egrep} y \emph{awk} no tienen equivalentes en el shell de Korn:
\begin{itemize}
\item{Los operadores de principio y fin de línea \textasciicircum y \$.}
\item{Los operadores de principio y final de palabra \textbackslash{}< y \textbackslash{}>.}
\end{itemize}
Apenas son necesarios, ya que el intérprete de comandos Korn no opera normalmente con archivos de texto y analiza las cadenas en palabras por sí mismo. (Esencialmente, \textasciicircum y \$ están implícitos como si siempre estuvieran ahí. Rodee un patrón con caracteres * para desactivar esto). Siga leyendo para conocer más características de la última versión de \emph{ksh}.
\subsubsection{Coincidencia de Patrones con Expresiones Regulares}
A partir de \emph{ksh93l}, el intérprete de órdenes proporciona una serie de funciones adicionales de expresiones regulares. Las discutimos aquí por separado, porque es muy probable que su versión de \emph{ksh93} no las tenga, a menos que descargue un binario de \emph{ksh93} o construya \emph{ksh93} desde el código fuente. Las facilidades se desglosan de la siguiente manera.
\begin{description}
\item[Nuevos operadores de coincidencia de patrones:] Hay varias funciones nuevas de coincidencia de patrones disponibles. Se describen brevemente en la Tabla \ref{Tab: ksh93l}. Más discusión sigue después de la mesa.
\item[Subpatrones con opciones:] Los subpatrones especiales entre paréntesis pueden contener opciones que controlan la coincidencia dentro del subpatrón o el resto de la expresión.
\item[Nueva clase de carácter \texttt{:palabra:}:] La clase de carácter \texttt{[:word:]} dentro de una expresión entre paréntesis coincide con cualquier carácter que sea <<componente de palabra>>. Esto es básicamente cualquier carácter alfanumérico o el guión bajo (\_).
\item[Secuencias de escape reconocidas dentro de subpatrones:] Varias secuencias de escape se reconocen y tratan especialmente dentro de las expresiones entre paréntesis.
\end{description}
\begin{table}[h]
\center
\caption{Nuevos operadores de coincidencia de patrones en \emph{ksh93l} y versiones posteriores}
\label{Tab: ksh93l}
\begin{tabular}{|m{4cm}|m{10cm}|} \hline
\textbf{Operador} & \textbf{Significado} \\ \hline
\texttt{{N}(exp)} & Exactamente N ocurrencias de \emph{exp} \\\hline
\texttt{\{N,M\}(exp)} & Entre N y M ocurrencias de \emph{exp} \\\hline
\texttt{*-(exp)} & Cero o más ocurrencias de \emph{exp} , coincidencia más corta \\\hline
\texttt{+-(exp)} & Una o más ocurrencias de \emph{exp} , coincidencia más corta \\\hline
\texttt{?-(exp)} & Cero o una ocurrencia de \emph{exp} , coincidencia más corta \\\hline
\texttt{@-(exp1|exp2|...)} & Exactamente uno de \emph{exp1} o \emph{exp2} o ..., coincidencia más corta \\\hline
\texttt{\{N\}-(exp)} & Exactamente N ocurrencias de \emph{exp} , coincidencia más corta \\\hline
\texttt{\{N,M\}-(exp)} & Entre N y M ocurrencias de \emph{exp} , coincidencia más corta \\\hline
\end{tabular}
\end{table}
Los primeros dos operadores en esta tabla coinciden con las facilidades en \emph{egrep (1)}, llamadas \emph{expresiones de intervalo}. Le permiten especificar que desea hacer coincidir exactamente \emph{N} elementos, ni más ni menos, o que desea hacer coincidir \emph{N} y \emph{M} elementos.
El resto de los operadores realizan coincidencias más cortas o <<no codiciosas>>. Normalmente, las expresiones regulares coinciden con el texto más largo posible. Una coincidencia no codiciosa es uno de los textos más cortos posibles que coinciden. El emparejamiento no codicioso se popularizó por primera vez con el lenguaje \emph{perl}. Estos operadores funcionan con los operadores de coincidencia y sustitución de patrones descritos en la siguiente sección; retrasamos los ejemplos de emparejamiento voraz vs. no voraz hasta allí. El comodín de nombre de archivo efectivamente siempre hace una coincidencia codiciosa.
Dentro de operaciones como texttt{@(... )}, puede proporcionar un subpatrón especial que habilite o deshabilite opciones para coincidencias codiciosas e independientes de mayúsculas y minúsculas. Este subpatrón tiene una de las siguientes formas:
\begin{lstlisting}[language=bash]
~(+ options: pattern list) # Habilitar opciones
~(- options: pattern list) # Deshabilitar opciones
\end{lstlisting}
Las opciones son una o ambas \emph{i} para la coincidencia independiente de mayúsculas y minúsculas y \emph{g} para la coincidencia codiciosa. Si \texttt{: pattern list} se omite, las opciones se aplican al resto del patrón envolvente. Si se proporcionan, se aplican solo a esa lista de patrones. También es posible omitir las opciones , pero hacerlo realmente no le proporciona ningún valor nuevo.
La expresión entre paréntesis \texttt{[[:word:]]} es una abreviatura de \texttt{[[:alnum:]\_]}. Es una conveniencia notacional, pero que puede aumentar la legibilidad del programa.
Dentro de las expresiones entre paréntesis, \emph{ksh} reconoce todas las secuencias de escape estándar ANSI C y tienen su significado habitual. (Consulte la \hyperref[sec:7.3.3.1]{Sección 7.3.3.1}, en el \hyperref[sec:Chapter7]{Capítulo 7}). Además, las secuencias de escape enumeradas en la Tabla \ref{Tab_exp} se reconocen y se pueden usar para la comparación de patrones.
\begin{table}[h]
\center
\caption{Secuencias de escape de expresiones regulares}
\label{Tab_exp}
\begin{tabular}{m{3cm}|m{6cm}} \hline
\textbf{Secuencia de escape} & \textbf{Significado} \\ \hline
\textbackslash{} d & Igual que \texttt{[[:digit:]]} \\
\textbackslash{} D & Igual que \texttt{[![:digit:]]} \\
\textbackslash{} s & Igual que \texttt{[[:space:]]} \\
\textbackslash{} S & Igual que \texttt{[![:space:]]} \\
\textbackslash{} w & Igual que \texttt{[[:word:]]} \\
\textbackslash{} W & Igual que \texttt{[![:word:]]} \\
\end{tabular}
\end{table}
¡Uf! Todo esto es bastante embriagador. Si te sientes un poco abrumado, no te preocupes. A medida que aprendas más sobre expresiones regulares y programación en el shell y empieces a realizar tareas de procesamiento de texto cada vez más complejas, llegarás a apreciar el hecho de poder hacer todo esto dentro del propio shell, en lugar de tener que recurrir a programas externos como \texttt{sed, awk} o \emph{perl}.
\subsection{Operadores de Coincidencia de Patrones}\label{sec:453}
La Tabla \ref{Tab_Op} enumera los operadores de concordancia de patrones del shell de Korn.
\begin{table}[h]
\center
\caption{Operadores de concordancia de patrones}
\label{Tab_Op}
\begin{tabular}{|m{5cm}|m{10cm}|} \hline
\textbf{Operador} & \textbf{Significado} \\ \hline
\texttt{\$\{variable\#pattern\}} & Si el patrón coincide con el comienzo del valor de la variable, elimine la parte más corta que coincida y devuelva el resto. \\\hline
\texttt{\$\{variable\#\#pattern\}} & Si el patrón coincide con el comienzo del valor de la variable, elimine la parte más larga que coincida y devuelva el resto. \\\hline
\texttt{\$\{variable\%pattern\}} & Si el patrón coincide con el final del valor de la variable, elimine la parte más corta que coincida y devuelva el resto. \\\hline
\texttt{\$\{variable\%\%pattern\}} & Si el patrón coincide con el final del valor de la variable, elimine la parte más larga que coincida y devuelva el resto. \\\hline
\end{tabular}
\end{table}
Pueden ser difíciles de recordar, así que aquí tienes un práctico recurso mnemotécnico: \# corresponde a la parte delantera porque los signos numéricos \emph{preceden} a los números; \% corresponde a la parte trasera porque los signos de porcentaje \emph{siguen} a los números. Otro recurso mnemotécnico proviene de la ubicación típica (al menos en EE.UU.) de las teclas \# y \% en el teclado. La \# está a la izquierda y el \% a la derecha.
El uso clásico de los operadores de concordancia de patrones es la eliminación de componentes de los nombres de ruta, como prefijos de directorio y sufijos de nombres de archivo. Con esto en mente, aquí hay un ejemplo que muestra cómo funcionan todos los operadores. Supongamos que la variable \emph{path} tiene el valor \texttt{/home/billr/mem/long.file.name}; entonces:
\begin{longtable}[h]{|p{3cm}|p{9cm}|}
\hline
\textbf{Expresión} & \textbf{Resultado} \\\hline
\endfirsthead
\hline
\textbf{Expresión} & \textbf{Resultado} \\\hline
\endhead
\texttt{\$\{path\#\#/*/\}} & \hspace{4cm} \texttt{long.file.name} \\
\texttt{\$\{path\#/*/\}} & \hspace{2cm} \texttt{billr/mem/long.file.name} \\
\texttt{\$path} & \texttt{/home/billr/mem/long.file.name} \\
\texttt{\$\{path\%.*\}} & \texttt{/home/billr/mem/long.file} \\
\texttt{\$\{path\%\%.*\}} & \texttt{/home/billr/mem/loang} \\\hline
\end{longtable}
Los dos patrones utilizados aquí son \texttt{/*/}, que coincide con cualquier cosa entre dos barras, y \emph{.*}, que coincide con un punto seguido de cualquier cosa.
A partir de \emph{ksh93l}, estos operadores establecen automáticamente la variable de array \texttt{.sh.match.} Esto se discute en la \hyperref[sec:457]{Sección 4.5.7}, más adelante en este capítulo.
Incorporaremos uno de estos operadores en nuestra próxima tarea de programación, \hyperref[box:4-2]{Tarea 4-2}.
\begin{mybox}[Tarea 4-2]\label{box:4-2}
Estás escribiendo un compilador de C, y quieres usar el shell de Korn para tu front-end.\footnote{No te rías: antaño, muchos compiladores de Unix tenían shell scripts como interfaz.}
\end{mybox}
Imagina un compilador de C como una cadena de componentes de procesamiento de datos. El código fuente C se introduce al principio de la tubería, y el código objeto sale al final; hay varios pasos intermedios. La tarea del script de shell, entre otras muchas cosas, es controlar el flujo de datos a través de los componentes y designar los archivos de salida.
Tienes que escribir la parte del script que toma el nombre del fichero fuente C de entrada y crea a partir de él el nombre del fichero de código objeto de salida. Es decir, usted debe tomar un nombre de archivo que termina en \texttt{.c} y crear un nombre de archivo que es similar, excepto que termina en \texttt{.o}.
La tarea en cuestión es quitar el \texttt{.c} del nombre de archivo y añadir \texttt{.o}. Una única sentencia shell lo hace:
\begin{lstlisting}[language=bash]
objname=${filename%.c}.o
\end{lstlisting}
Esto le dice al shell que busque \texttt{.c} al final de \texttt{filename}. Si hay una coincidencia, devuelve \texttt{\$filename} con la coincidencia eliminada. Así que si \texttt{filename} tenía el valor \texttt{fred.c}, la expresión \texttt{\${filenmame\%.c}} devolvería \texttt{fred}. El \texttt{.o} se añade para obtener el \texttt{fred.o} deseado, que se almacena en la variable \texttt{objname}.
Si \texttt{filename} tuviera un valor inapropiado (sin \texttt{.c}) como \texttt{fred.a}, la expresión anterior se evaluaría como \texttt{fred.a.o}: como no hay coincidencia, no se elimina nada del valor de \texttt{filename}, y se añade \texttt{.o} de todos modos. Y, si \texttt{filename} contuviera más de un punto - por ejemplo, si fuera \emph{y.tab.c} que es tan infame entre los compiladores - la expresión aún produciría el deseado \emph{y.tab.o}. Observe que esto no sería cierto si usáramos \texttt{\%\%} en la expresión en lugar de \texttt{\%}. El primer operador utiliza la coincidencia más larga en lugar de la más corta, por lo que coincidiría con \texttt{.tab.o} y se evaluaría a \texttt{y.o} en lugar de \texttt{y.tab.o}. Así que el \texttt{\%} simple es correcto en este caso.
Sin embargo, para la \hyperref[box:4-3]{Tarea 4-3} sería preferible un borrado de coincidencia más larga.
\begin{mybox}[Tarea 4-3]\label{box:4-3}
Está implementando un filtro que prepara un archivo de texto para su impresión. Quiere poner el nombre del fichero - sin ningún prefijo de directorio - en la página <<banner>>. Suponga que, en su script, tiene la ruta del archivo a imprimir almacenada en la variable \texttt{pathname}.
\end{mybox}
Está claro que el objetivo es eliminar el prefijo del directorio de la ruta. La siguiente línea lo hace:
\begin{lstlisting}[language=bash]
bannername=${pathname##*/}
\end{lstlisting}
Esta solución es similar a la primera línea de los ejemplos mostrados anteriormente. Si \emph{pathname} fuera sólo un nombre de fichero, el patrón \texttt{*/} (cualquier cosa seguida de una barra) no coincidiría, y el valor de la expresión sería \texttt{\$pathname} sin modificar. Si \emph{pathname} fuera algo como \texttt{fred/bob}, el prefijo \texttt{fred/} coincidiría con el patrón y se eliminaría, dejando sólo \texttt{bob} como valor de la expresión. Lo mismo ocurriría si la ruta fuera algo como \texttt{/dave/pete/fred/bob}: como el \#\# borra la coincidencia más larga, borra todo \texttt{/dave/pete/fred/}.
Si usáramos \texttt{\#*/} en lugar de \texttt{\#\#*/}, la expresión tendría el valor incorrecto \texttt{dave/pete/fred/bob}, porque la instancia más corta de <<cualquier cosa seguida de una barra>> al principio de la cadena es sólo una barra (/).
La construcción \texttt{\$\{variable\#\#*/\}} es bastante similar a la utilidad de Unix \emph{basename(1)}. \emph{basename} es menos eficiente que \texttt{\$\{variable\#\#/*\}} porque puede ejecutarse en su propio proceso separado en lugar de dentro del shell.\footnote{basename puede estar incorporado en algunas versiones de \emph{ksh93}. Por lo tanto, no se garantiza que se ejecute en un proceso separado.} Otra utilidad, \emph{dirname(1)}, hace esencialmente lo contrario que \emph{basename}: devuelve sólo el prefijo del directorio. Es equivalente a la expresión del shell de Korn \texttt{\$\{variable\%/*\}} y es menos eficiente por la misma razón.
\subsection{Operadores de Sustitución de Patrones}
Además de los operadores de concordancia de patrones que eliminan fragmentos de los valores de las variables del shell, puedes hacer sustituciones en esos valores, como en un editor de texto. (De hecho, usando estas facilidades, casi podría escribir un editor de texto en modo línea como un script del shell). Estos operadores están listados en la Tabla \ref{Tab_sust}.
\begin{longtable}[h]{|p{6cm}|p{10cm}|}
\caption{Operadores de sustitución de patrones}
\label{Tab_sust}\\
\hline
\textbf{Operación} & \textbf{Significado} \\ \hline
\endfirsthead
\hline
\textbf{Operación} & \textbf{Significado} \\ \hline
\endhead
\multirow{3}{*}{\texttt{\$\{variable:start\}}} & Representan operaciones de subcadena. El resultado es el valor de la variable que empieza en la posición inicial y sigue por los caracteres de longitud. El primer carácter se sitúa en la posición 0 y, si no se indica ninguna longitud, se utiliza el resto de la cadena. \\
& Cuando se utiliza con \$* o \$@ o una matriz indexada por * o @ (véase el \hyperref[sec:Chapter6]{Capítulo 6}), \emph{start} es un índice inicial y \emph{length} es el recuento de elementos. En otras palabras, el resultado es una porción de los parámetros posicionales o de la matriz. Tanto \emph{start} como \emph{length} pueden ser expresiones aritméticas. \\
& A partir de \emph{ksh93m}, un inicio negativo se toma como relativo al final de la cadena. Por ejemplo, si una cadena tiene 10 caracteres, numerados del 0 al 9, un valor de inicio de -2 significa 7 (9 - 2 = 7). Del mismo modo, si la variable es una matriz indexada, un inicio negativo produce un índice trabajando hacia atrás desde el subíndice más alto de la matriz. \\\hline
\texttt{\$\{variable:start:length\}} & \\\hline
\texttt{\$\{variable/pattern/replace\}} & Si la variable contiene una coincidencia de patrón, la primera coincidencia se sustituye por el texto de reemplazar. \\\hline
\texttt{\$\{variable//pattern/replace\}} & Es la misma operación que la anterior, salvo que se sustituyen todas las coincidencias del patrón. \\\hline
\texttt{\$\{variable/pattern\}} & Si la variable contiene una coincidencia de patrón, elimina la primera coincidencia de patrón. \\\hline
\texttt{\$\{variable/\#pattern/replace\}} & Si la variable contiene una coincidencia de patrón, la primera coincidencia se sustituye por el texto de sustitución. La coincidencia debe producirse al principio del valor de la variable. Si no coincide allí, no se produce ninguna sustitución. \\\hline
\texttt{\$\{variable/\%pattern/replace\}} & Si la variable contiene una coincidencia de patrón, la primera coincidencia se sustituye por el texto de sustitución. La coincidencia debe producirse al final del valor de la variable. Si no coincide allí, no se produce ninguna sustitución. \\\hline
\end{longtable}
La sintaxis \texttt{\$\{variable/pattern\}} es diferente de los operadores \#, \#\#, \% y \%\% que vimos anteriormente. Esos operadores están limitados a coincidir al principio o al final del valor de la variable, mientras que la sintaxis que se muestra aquí no lo está. Por ejemplo:
\begin{lstlisting}[language=bash]
$ path=/home/fred/work/file
$ print ${path/work/play} # Reemplaza work por play
/home/fred/play/file
\end{lstlisting}
Volvamos a nuestro ejemplo del front-end del compilador y veamos cómo podríamos utilizar estos operadores. Al convertir un nombre de archivo fuente C en un nombre de archivo objeto, podríamos hacer la sustitución de esta manera:
\begin{lstlisting}[language=bash]
objname=${filename/%.c/.o} # Cambia .c por .o, pero solo al final
\end{lstlisting}
Si tuviéramos una lista de nombres de archivo C y quisiéramos cambiarlos todos por nombres de archivo objeto, podríamos utilizar el llamado operador de sustitución global:
\begin{lstlisting}[language=bash]
llfiles="fred.c dave.c pete.c"
$ allobs=${allfiles//.c/.o}
$ print $allobs
fred.o dave.o pete.o
\end{lstlisting}
Los patrones pueden ser cualquier expresión de patrón del shell de Korn, como se ha comentado anteriormente, y el texto de sustitución puede incluir la notación \textbackslash{} N para obtener el texto que coincida con un sub-patrón.
Finalmente, estas operaciones pueden aplicarse a los parámetros posicionales y a los arrays, en cuyo caso se realizan sobre todos los parámetros o elementos del array a la vez. (Las matrices se describen en el capítulo 6.)
\begin{lstlisting}[language=bash]
$ print "$@"
hi how are you over there
$ print ${@/h/H}
Hi How are you over tHere # Cambia h por H en todos los parametros
\end{lstlisting}
\subsubsection{Emparejamiento codicioso versus no codicioso}
Como prometí, aquí hay una breve demostración de las diferencias entre las expresiones regulares con y sin codicia:
\begin{lstlisting}[language=bash]
$ x='12345abc6789'
$ print ${x//+([[:digit:]])/X} # Sustitucion con la coincidencia mas larga
XabcX
$ print ${x//+-([[:digit:]])/X} # Sustitucion con coincidencia mas corta
XXXXXabcXXXX
$ print ${x##+([[:digit:]])} # Sustitucion con la coincidencia mas larga
abc6789
$ print ${x#+([[:digit:]])} # Eliminar la coincidencia mas corta
2345abc6789
\end{lstlisting}
La primera impresión sustituye la coincidencia más larga de <<uno o más dígitos>> por una sola X, en todas partes de la cadena. Como se trata de la coincidencia más larga, se sustituyen ambos grupos de dígitos. En el segundo caso, la coincidencia más corta para <<uno o más dígitos>> es un solo dígito, por lo que cada dígito se sustituye por una X.
Del mismo modo, los casos tercero y cuarto demuestran la eliminación de texto de la parte delantera del valor, utilizando la coincidencia más larga y la más corta. En el tercer caso, la coincidencia más larga elimina todos los dígitos; en el cuarto caso, la coincidencia más corta elimina un solo dígito.
\subsection{Operadores de nombre de variable}
Varios operadores se relacionan con nombres de variables del shell, como se ve en la Tabla \ref{tab:4.11}.
\begin{table}[h]
\center
\caption{Operadores relacionados con el nombre}
\label{tab:4.11}
\begin{tabular}{|m{4cm}|m{10cm}|} \hline
\textbf{Operador} & \textbf{Significado} \\ \hline
\texttt{\$\{!variable\}} & Devuelve el nombre de la variable real referenciada por la variable nameref. \\
\texttt{\$\{!base*\}} & \multirow{2}{9cm}{Lista de todas las variables cuyos nombres empiezan por base.} \\
\texttt{\$\{!base@\}} & \\\hline
\end{tabular}
\end{table}
Las referencias a nombres se trataron en la \hyperref[sec:4.4]{Sección 4.4}, anteriormente en este capítulo. Véase allí un ejemplo de \texttt{\$\{!name\}}.
Los dos últimos operadores de la Tabla \ref{tab:4.11} pueden ser útiles para depurar y/o rastrear el uso de variables en un script grande. Sólo para ver cómo funcionan:
\begin{lstlisting}[language=bash]
$ print ${!HIST*}
HISTFILE HISTCMD HISTSIZE
$ print ${!HIST@}
HISTFILE HISTCMD HISTSIZE
\end{lstlisting}
En el capítulo 6 se describen otros operadores relacionados con las variables de matriz.
\subsection{Operadores de longitud}
Hay tres operadores restantes sobre variables. Uno es \texttt{\$\{\#varname\}}, que devuelve el número de caracteres de la cadena.\footnote{Puede ser mayor que el número de bytes para juegos de caracteres multibyte.} (En el \hyperref[sec:Chapter6]{Capítulo 6} veremos cómo tratar éste y otros valores similares como números reales para poder usarlos en expresiones aritméticas). Por ejemplo, si \emph{filename} tiene el valor \emph{fred.c}, entonces \texttt{\$\{\#filename\}} tendría el valor 6. Los otros dos operadores (\texttt{\$\{\#array[*]\}} y \texttt{\$\{\#array[@]\}}) tienen que ver con las variables de array, que también se tratan en el \hyperref[sec:Chapter6]{Capítulo 6}.
\subsection{La variable \emph{.sh.match}}\label{sec:457}
La variable \texttt{.sh.match} se introdujo en \emph{ksh93l}. Es una matriz indexada (véase el \hyperref[sec:Chapter6]{Capítulo 6}), cuyos valores se establecen cada vez que se realiza una operación de coincidencia de patrones en una variable, como \texttt{\$\{filename\%\%*/\}}, con cualquiera de los operadores \#, \% (para la coincidencia más corta), o \#\#, \%\% (para la coincidencia más larga), o / y // (para sustituciones). \texttt{.sh.match[0]} contiene el texto que coincide con el patrón completo. \texttt{.sh.match[1]} contiene el texto que coincide con la primera subexpresión entre paréntesis, \texttt{.sh.match[2]} el texto que coincide con la segunda, y así sucesivamente. Los valores de \texttt{.sh.match} dejan de ser válidos (es decir, no intente utilizarlos) si cambia la variable sobre la que se realizó la comparación de patrones.
De nuevo, esta es una característica pensada para programación y procesamiento de texto más avanzados, análoga a características similares en otros lenguajes como \emph{perl}. Si estás empezando, no te preocupes.
\section{Sustitución de comandos}
Hasta ahora, hemos visto dos formas de introducir valores en las variables: mediante sentencias de asignación y mediante el suministro por parte del usuario de argumentos en la línea de comandos (parámetros posicionales). Existe otra forma: la sustitución de comandos, que permite utilizar la salida estándar de un comando como si fuera el valor de una variable. Pronto verá lo potente que es esta función.
La sintaxis de la sustitución de comandos es:
\begin{lstlisting}[language=bash]
$(Unix command)
\end{lstlisting}
El comando dentro del paréntesis se ejecuta, y cualquier cosa que el comando escriba en la salida estándar (y en el error estándar) se devuelve como valor de la expresión. Estas construcciones pueden anidarse, es decir, el comando Unix puede contener sustituciones de comandos.
He aquí algunos ejemplos sencillos:
\begin{itemize}
\item{El valor de \texttt{\$(pwd)} es el directorio actual (igual que la variable de entorno \texttt{\$PWD}).}
\item{El valor de \texttt{\$(ls)} son los nombres de todos los archivos del directorio actual, separados por nuevas líneas.}
\item{Para encontrar información detallada sobre un comando si no sabe dónde reside su archivo, escriba \texttt{ls -l \$(whence -p command} ). La opción -p fuerza a whence a hacer una búsqueda por ruta y no considerar palabras clave, built-ins, etc.}
\item{Para obtener el contenido de un archivo en una variable, puede utilizar \texttt{varname=\$(<filename)}. \texttt{\$(cat filename)} hará lo mismo, pero el intérprete de comandos utiliza el primero como una abreviatura incorporada y lo ejecuta de forma más eficiente.}
\item{Si quieres editar (con Emacs) todos los capítulos de tu libro en el shell de Korn que tengan la frase <<sustitución de comandos>>, suponiendo que todos tus archivos de capítulos empiecen por ch, podrías escribir:
\begin{lstlisting}[language=bash]
emacs $(grep -l 'command substitution' ch*.xml)
\end{lstlisting}
La opción \texttt{-l} de grep imprime sólo los nombres de los archivos que contienen coincidencias.}
\end{itemize}
La sustitución de órdenes, al igual que la expansión de variables, se realiza entre comillas dobles. (Las comillas dobles dentro de la sustitución de comandos no se ven afectadas por las comillas dobles que las encierran). Por lo tanto, nuestra regla en el \hyperref[sec:Chapter1]{Capítulo 1} y el \hyperref[sec:Chapter3]{Capítulo 3} sobre el uso de comillas simples para cadenas a menos que contengan variables se ampliará ahora: <<En caso de duda, use comillas simples, a menos que la cadena contenga variables o sustituciones de comandos, en cuyo caso use comillas dobles>>.
(Por compatibilidad con versiones anteriores, el shell de Korn soporta la notación original de sustitución de comandos del shell de Bourne (y del shell C) usando comillas inversas: \texttt{`...`}. Sin embargo, es considerablemente más difícil de usar que \texttt{\$(...)}, ya que las comillas y las sustituciones de órdenes anidadas requieren un cuidadoso escape. No utilizamos las comillas traseras en ninguno de los programas de este libro).
Sin duda se le ocurrirán muchas formas de usar la sustitución de comandos a medida que gane experiencia con el shell de Korn. Una que es un poco más compleja que las mencionadas anteriormente se relaciona con una tarea de personalización que vimos en el \hyperref[sec:Chapter3]{Capítulo 3}: personalizar su cadena del prompt.
Recuerde que puede personalizar su cadena del prompt asignando un valor a la variable \texttt{PS1}. Si estás en una red de ordenadores, y usas diferentes máquinas de vez en cuando, puede resultarte útil tener el nombre de la máquina en la que estás en tu cadena del prompt. La mayoría de las versiones modernas de Unix tienen el comando \emph{hostname(1)}, que imprime el nombre de red de la máquina en la que se encuentra en la salida estándar. (Si usted no tiene este comando, puede tener uno similar como \emph{uname}.) Este comando le permite obtener el nombre de la máquina en su cadena del prompt poniendo una línea como esta en su \emph{.profile} o archivo de entorno:
\begin{lstlisting}[language=bash]
PS1="$(hostname) $ "
\end{lstlisting}
(En este caso, no es necesario que el segundo signo de dólar vaya precedido de una barra invertida. Si el carácter después del \$ no es especial para el shell, el \$ se incluye literalmente en la cadena). Por ejemplo, si su máquina tuviera el nombre \texttt{coltrane}, entonces esta sentencia establecería su cadena prompt a \texttt{"coltrane \$ "}.
La sustitución de comandos nos ayuda con la solución de la siguiente tarea de programación, \hyperref[box:4-4]{Tarea 4-4}, relacionada con la base de datos de álbumes de la \hyperref[box:4-1]{Tarea 4-1}.
\begin{mybox}[Tarea 4-4]\label{box:4-4}
El fichero utilizado en la \hyperref[box:4-1]{Tarea 4-1} es en realidad un informe derivado de una tabla más grande de datos sobre álbumes. Esta tabla consta de varias columnas, o campos, a los que el usuario se refiere con nombres como <<artist>>, <<title>>, <<year>>, etc. Las columnas están separadas por barras verticales (|, igual que el carácter pipa de Unix). Para tratar columnas individuales en la tabla, los nombres de campo deben convertirse en números de campo.
Supongamos que existe una función del shell llamada \emph{getfield} que toma el nombre del campo como argumento y escribe el número de campo correspondiente en la salida estándar. Utilice esta rutina para extraer una columna de la tabla de datos.
\end{mybox}
La utilidad \emph{cut(1)} es ideal para esta tarea. \emph{cut} es un filtro de datos: extrae columnas de datos tabulares.\footnote{Algunos sistemas muy antiguos derivados de BSD no tienen \emph{cut}, pero puedes usar \emph{awk} en su lugar. Siempre que veas un comando de la forma texttt{cut -f N -d C nombrearchivo}, usa esto en su lugar: \texttt{awk -F C '\{print \$ N\}' filename}.} Si proporciona el número de columnas que desea extraer de la entrada, \emph{cut} imprime sólo esas columnas en la salida estándar. Las columnas pueden ser posiciones de caracteres o - relevante en este ejemplo - campos que están separados por caracteres TAB u otros delimitadores.
Supongamos que la tabla de datos de nuestra tarea es un fichero llamado \emph{albums} y que tiene el siguiente aspecto:
\begin{lstlisting}[language=bash]
Coltrane, John|Giant Steps|Atlantic|1960|Ja
Coltrane, John|Coltrane Jazz|Atlantic|1960|Ja
Coltrane, John|My Favorite Things|Atlantic|1961|Ja
Coltrane, John|Coltrane Plays the Blues|Atlantic|1961|Ja
...
\end{lstlisting}
Así es como utilizaríamos \emph{cut} para extraer la cuarta columna (año):
\begin{lstlisting}[language=bash]
cut -f4 -d\| albums
\end{lstlisting}
El argumento \emph{-d} se utiliza para especificar el carácter utilizado como delimitador de campo (TAB es el predeterminado). La barra vertical debe tener barra diagonal inversa para que el shell no intente interpretarla como una tubería.
A partir de esta línea de código y de la rutina \emph{getfield}, podemos deducir fácilmente la solución a la tarea. Supongamos que el primer argumento de \emph{getfield} es el nombre del campo que el usuario quiere extraer. Entonces la solución es:
\begin{lstlisting}[language=bash]
fieldname=$1
cut -f$(getfield $fieldname) -d\| albums
\end{lstlisting}
Si ejecutáramos este script con el argumento \texttt{year}, la salida sería:
\begin{lstlisting}[language=bash]
1960
1960
1961
1961
...
\end{lstlisting}
La \hyperref[box:4-5]{Tarea 4-5} es otra pequeña tarea que utiliza \texttt{cut}.
\begin{mybox}[Tarea 4-5]\label{box:4-5}
Suponga que ha iniciado sesión en un gran servidor o mainframe que admite muchos usuarios simultáneos. Envíe un mensaje de correo electrónico a todas las personas que estén conectadas en ese momento.
\end{mybox}
El comando \emph{who(1)} te dice quién está conectado (así como en qué terminal está y cuándo se conectó). Su salida tiene este aspecto:
\begin{lstlisting}[language=bash]
billr console May 22 07:57
fred tty02 May 22 08:31
bob tty04 May 22 08:12
\end{lstlisting}
Los campos están separados por espacios, no por TABs. Como necesitamos el primer campo, podemos utilizar un espacio como separador de campos en el comando de \emph{cut}. (De lo contrario, tendríamos que utilizar la opción de \emph{cut} que utiliza columnas de caracteres en lugar de campos). Para proporcionar un carácter de espacio como argumento en una línea de comandos, puede rodearlo de comillas:
\begin{lstlisting}[language=bash]
who | cut -d' ' -f1
\end{lstlisting}
Con la salida de who anterior, la salida de este comando se vería así:
\begin{lstlisting}[language=bash]
billr
fred
bob
\end{lstlisting}
Esto lleva directamente a la solución de la tarea. Sólo tienes que escribir:
\begin{lstlisting}[language=bash]
mail $(who | cut -d' ' -f1)
\end{lstlisting}
Se ejecutará el comando \texttt{mail billr fred bob} y entonces podrás escribir tu mensaje.
La \hyperref[box:4-6]{Tarea 4-6} es otra tarea que muestra lo útiles que pueden ser las canalizaciones de comandos en la sustitución de comandos.
\begin{mybox}[Tarea 4-6]\label{box:4-6}
El comando \emph{ls} permite buscar patrones con comodines, pero no permite seleccionar archivos por fecha de modificación. Diseña un mecanismo que te permita hacerlo.
\end{mybox}
Esta tarea se inspiró en la función del sistema operativo OpenVMS que permite especificar archivos por fecha con los parámetros \emph{BEFORE} y \emph{SINCE}.
Aquí tienes una función que te permite listar todos los ficheros que fueron modificados por última vez en la fecha que des como argumento. Una vez más, elegimos una función por razones de velocidad. El nombre de la función no pretende hacer ningún juego de palabras:
\begin{lstlisting}[language=bash]
function lsd {
date=$1
ls -l | grep -i "^.\{41\}$date" | cut -c55-
}
\end{lstlisting}
Esta función depende de la disposición de las columnas del comando \emph{ls -l}. En particular, depende de que las fechas empiecen en la columna 42 y los nombres de fichero empiecen en la columna 55. Si este no es el caso en tu versión de Unix, necesitarás ajustar los números de columna \footnote{Por ejemplo, \texttt{ls -l} en GNU/Linux tiene fechas que comienzan en la columna 43 y nombres de archivo que comienzan en la columna 57.}.
Usamos la utilidad de búsqueda \emph{grep} para hacer coincidir la fecha dada como argumento (en la forma \emph{Mon DD}, por ejemplo, \texttt{Jan 15} u \texttt{Oct 6}, este último con dos espacios) con la salida de \texttt{ls -l}. (El argumento de expresión regular para \emph{grep} se entrecomilla con comillas dobles, para realizar la sustitución de variables). Esto nos da un largo listado de sólo aquellos ficheros cuyas fechas coinciden con el argumento. La opción \emph{-i} de \emph{grep} le permite utilizar todas las letras minúsculas en el nombre del mes, mientras que el argumento bastante extravagante significa: <<Coincidir con cualquier línea que contenga 41 caracteres seguidos del argumento de la función>>. Por ejemplo, si se escribe \texttt{lsd 'jan 15'}, \emph{grep} buscará las líneas que contengan 41 caracteres seguidos de \texttt{jan 15} (o \texttt{Jan 15}).
La salida de \emph{grep} se canaliza a través de nuestro omnipresente amigo \emph{cut} para recuperar sólo los nombres de archivo. El argumento para \emph{cut} le dice que extraiga los caracteres de la columna 55 hasta el final de la línea.
Con la sustitución de comandos, puede utilizar esta función con cualquier comando que acepte argumentos de nombre de archivo. Por ejemplo, si desea imprimir todos los archivos de su directorio actual que fueron modificados por última vez hoy, y hoy es 15 de enero, podría escribir:
\begin{lstlisting}[language=bash]
lp $(lsd 'jan 15')
\end{lstlisting}
La salida de \emph{lsd} está en múltiples líneas (una por cada nombre de fichero), pero como la variable IFS (ver antes en este capítulo) contiene newline por defecto, el shell usa newline para separar palabras en la salida de /emph{lsd}, igual que hace normalmente con el espacio y TAB.
\section{Ejemplos avanzados: pushd y popd}
Concluimos este capítulo con un par de funciones que puedes encontrar útiles en tu uso diario de Unix. Resuelven el problema presentado por la \hyperref[box:4-7]{Tarea 4-7}.
\begin{mybox}[Tarea 4-7]\label{box:4-7}
En el shell C, los comandos \emph{pushd} y \emph{popd} implementan una pila de directorios que le permiten moverse a otro directorio temporalmente y que el shell recuerde dónde estaba. El comando \emph{dirs} imprime la pila. El shell de Korn no proporciona estos comandos. Impleméntelos como funciones del shell.
\end{mybox}
Comenzamos implementando un subconjunto significativo de sus capacidades y terminamos la implementación en el \hyperref[sec:Chapter6]{Capítulo 6}. (Para facilitar el desarrollo y la explicación, nuestra implementación ignora algunas cosas que una versión más a prueba de balas debería manejar. Por ejemplo, los espacios en los nombres de archivo harán que las cosas se rompan).
Si no sabe lo que es una pila, piense en un receptáculo de platos accionado por un muelle en una cafetería. Al colocar los platos en el recipiente, el muelle se comprime para que la parte superior se mantenga más o menos al mismo nivel. El plato colocado más recientemente en la pila es el primero que se coge cuando alguien quiere comida; por eso, la pila se conoce como una estructura de <<último en entrar, primero en salir>> o \emph{LIFO}. (Las víctimas de una recesión o de adquisiciones de empresas también reconocerán este mecanismo en el contexto de las políticas de despido de las empresas). Poner algo en una pila se conoce en informática como \emph{pushing}, y quitar algo de la parte superior se llama \emph{popping}.
Una pila es muy útil para recordar directorios, como veremos; puede <<mantener su lugar>> hasta un número arbitrario de veces. La forma \texttt{cd -} del comando \emph{cd} hace esto, pero sólo a un nivel. Por ejemplo: si estás en \emph{firstdir} y luego cambias a \emph{seconddir}, puedes escribir \texttt{cd -} para volver. Pero si empiezas en \emph{firstdir}, luego cambias a \emph{seconddir}, y luego vas a \emph{thirddir}, puedes usar \texttt{cd -} sólo para volver a \emph{seconddir}. Si escribes \texttt{cd -} de nuevo, volverás a \emph{thirddir}, porque es el directorio anterior\footnote{Piense en \texttt{cd -} como sinónimo de \texttt{cd \$OLDPWD}; véase el capítulo anterior.}.
Si quieres la funcionalidad <<anidada>> de recordar-y-cambiar que te llevará de vuelta a \emph{firstdir}, necesitas una pila de directorios junto con los comandos \emph{dirs, pushd} y \emph{popd}. Así es como funcionan:\footnote{Aquí lo hemos hecho de forma diferente al shell C. El shell C \emph{pushd} empuja primero el directorio inicial a la pila, seguido del argumento del comando. El shell C \emph{popd} elimina el directorio superior de la pila, revelando un nuevo directorio superior. A continuación, \emph{cds} al nuevo directorio superior. Creemos que este comportamiento es menos intuitivo que nuestro diseño aquí.}
\begin{itemize}
\item{\texttt{pushd dir} hace un \texttt{cd} a \texttt{dir} y luego empuja \emph{dir} a la pila.}
\item{\emph{popd} hace un \texttt{cd} al directorio superior, luego lo saca de la pila.}
\end{itemize}
Por ejemplo, considera la serie de eventos de la Tabla 4-12. Suponga que acaba de iniciar sesión y que se encuentra en su directorio personal (\emph{/home/you}).
Implementaremos una pila como una variable de entorno que contiene una lista de directorios separados por espacios.
\begin{table}[h]
\center
\caption{Ejemplo de pushd/popd}
\label{Tab:pushd}
\begin{tabular}{|m{4cm}|m{5cm}|m{4cm}|} \hline
\textbf{Comando} & \textbf{Contenido de la pila (arriba a la izquierda)} & \textbf{Directorio de resultados} \\ \hline
\texttt{pushd fred} & \texttt{/home/you/fred} & \texttt{/home/you/fred} \\\hline
\texttt{pushd /etc} & \texttt{/etc /home/you/fred} & \texttt{/etc} \\\hline
\texttt{cd /usr/tmp} & \texttt{/etc /home/you/fred} & \texttt{/usr/tmp} \\\hline
\texttt{popd} & \texttt{/home/you/fred} & \texttt{/etc} \\\hline
\texttt{popd} & \texttt{(empty)} & \texttt{/home/you/fred} \\\hline
\end{tabular}
\end{table}
Tu pila de directorios debería inicializarse en tu directorio personal cuando te conectes. Para ello, pon esto en tu \emph{.profile}:
\begin{lstlisting}[language=bash]
DIRSTACK="$PWD"
export DIRSTACK
\end{lstlisting}
No ponga esto en su fichero de entorno si tiene uno. La sentencia \emph{export} garantiza que \texttt{DIRSTACK} es conocido por todos los subprocesos; usted quiere inicializarlo sólo una vez. Si pone este código en un fichero de entorno, se reiniciará en cada subproceso del shell interactivo, lo que probablemente no quiera.
A continuación, necesitamos implementar \emph{dirs, pushd} y \emph{popd} como funciones. Aquí están nuestras versiones iniciales:
\begin{lstlisting}[language=bash]
function dirs { # imprimir pila de directorios (facil)
print $DIRSTACK
}
function pushd { # empujar el directorio actual a la pila
dirname=$1
cd ${dirname:?"missing directory name."}
DIRSTACK="$PWD $DIRSTACK"
print "$DIRSTACK"
}
function popd { # cd a top, sacarlo de la pila
top=${DIRSTACK%% *}
DIRSTACK=${DIRSTACK#* }
cd $top
print "$PWD"
}
\end{lstlisting}
Observa que no hay mucho código. Repasemos las funciones y veamos cómo funcionan. \emph{dirs} es fácil; sólo imprime la pila. La diversión comienza con \emph{pushd}. La primera línea simplemente guarda el primer argumento en la variable \emph{dirname} por razones de legibilidad.
El propósito principal de la segunda línea es cambiar al nuevo directorio. Usamos el operador \texttt{:?} para manejar el error cuando falta el argumento: si el argumento es dado, la expresión \texttt{\$\{dirname:? "missing directory name."\}} se evalúa a \texttt{\$dirname}, pero si no es dado, el shell imprime el mensaje \texttt{ksh: pushd: line 2: dirname: missing directory name}. y sale de la función.
La tercera línea de la función introduce el nuevo directorio en la pila. La expresión entre comillas dobles consiste en el nombre completo del directorio actual, seguido de un espacio, seguido del contenido de la pila de directorios (\texttt{\$DIRSTACK}). Las comillas dobles aseguran que todo esto se empaqueta en una sola cadena para asignar de nuevo a \texttt{DIRSTACK}.
La última línea simplemente imprime el contenido de la pila, con la implicación de que el directorio situado más a la izquierda es el directorio actual y el primero de la pila. (Esta es la razón por la que elegimos espacios para separar los directorios, en lugar de los más habituales dos puntos como en \texttt{PATH} y \texttt{MAILPATH}).
La función \emph{popd} hace otro uso de los operadores de coincidencia de patrones del shell. La primera línea utiliza el operador \%\%, que elimina la coincidencia más larga de << *>> (un espacio seguido de cualquier cosa). Esto elimina todo menos la parte superior de la pila. El resultado se guarda en la variable \texttt{top}, de nuevo por razones de legibilidad.
La segunda línea es similar, pero va en la otra dirección. Utiliza el operador \#, que intenta borrar la coincidencia más corta del patrón <<* >> (cualquier cosa seguida de un espacio) del valor de \texttt{DIRSTACK}. El resultado es que el directorio superior (y el espacio que le sigue) se borra de la pila.
La tercera línea en realidad cambia el directorio a la parte superior anterior de la pila. (Tenga en cuenta que a \emph{popd} no le importa dónde se encuentre cuando lo ejecute; si su directorio actual es el que está en la parte superior de la pila, no irá a ninguna parte). La línea final sólo imprime un mensaje de confirmación.
Este código es deficiente en los siguientes aspectos: en primer lugar, no prevé errores. Por ejemplo:
\begin{itemize}
\item{¿Qué ocurre si el usuario intenta introducir un directorio que no existe o no es válido?}
\item{¿Qué pasa si el usuario intenta hacer \emph{popd} y la pila está vacía?}
\end{itemize}
Pon a prueba tu comprensión del código averiguando cómo respondería a estas condiciones de error. La segunda deficiencia es que el código implementa sólo algunas de las funcionalidades de los comandos \emph{pushd} y \emph{popd} del shell C - aunque las partes más útiles. En el próximo capítulo, veremos cómo superar estas dos deficiencias.
El tercer problema con el código es que no funcionará si, por alguna razón, un nombre de directorio contiene un espacio. El código tratará el espacio como un carácter separador. Aceptaremos esta deficiencia por ahora. Sin embargo, cuando lea sobre arrays en el \hyperref[sec:Chapter6]{Capítulo 6}, piense en cómo podría usarlos para reescribir este código y eliminar el problema.