Contenido


Consejo de Linux

Parámetros de Bash y expansiones de parámetros

Pasar y analizar parámetros en scripts

Contenido de la serie:

Este contenido es la parte # de # de la serie: Consejo de Linux

Manténgase en contacto por contenidos adicionales de esta serie.

Este contenido es parte de la serie:Consejo de Linux

Manténgase en contacto por contenidos adicionales de esta serie.

Hoy en día, el bash shell está disponible en muchos sistemas Linux® y UNIX® , y es un shell habitual predeterminado en Linux. En este consejo aprenderá a manejar los parámetros y las opciones que hay en sus scripts Bash y a utilizar las expansiones de parámetros de shell para verificar o modificar parámetros. Este artículo se enfoca en bash, y los ejemplos se ejecutan todos en sistemas Linux en los que bash es el shell. Sin embargo, las mismas expansiones están disponibles en otros muchos shells, como ksh, ash o dash, y se pueden utilizar con esos shells o en otros UNIX o, incluso entornos, como Cygwin. Este consejo se desarrolla sobre las herramientas que se trataron en el consejo anteriorConsejo para Linux: Prueba de Bash y funciones de comparación. Parte del material de este artículo se ha extraído del tutorial de developerWorks Prep de examen 102 del LPI: Shells, creación de scripts, programación y compilación, que cubre muchas técnicas básicas de creación de scripts.

Parámetros pasados

Parte de la belleza de las funciones y scripts de shell es la capacidad que tienen de hacer que una única función o script se comporte de forma diferente al pasarle parámetros . En esta sección aprenderá a identificar y utilizar los parámetros que se pasan.

Dentro de las funciones o scripts, puede referirse a los parámetros utilizando las variables especiales de bash de la Tabla 1. Para referenciarlas como a las otras variables de shell, hay que añadirlas el símbolo $ como prefijo.

Tabla 1. Parámetros de shell para las funciones
ParámetroPropósito
0, 1, 2...Los parámetros de posición empezando desde el parámetro 0. El parámetro 0 se refiere al nombre del programa que inició bash, o el nombre del script de shell si la función se está ejecutando dentro de un script de shell. Vea las páginas del manual de bash para obtener información sobre otras posibilidades, como cuando bash se inicia con el -c . Se pasará una cadena de caracteres encapsulada en comillas dobles o simples, y se quitarán las comillas. En el caso de las comillas dobles, las variables de shell, como $HOME, se expandirán antes de llamar a la función. Usted tendrá que utilizar comillas simples o dobles para pasar los parámetros que contengan espacios en blanco incorporados u otros caracteres que pueden tener un significado especial para el shell.
*Los parámetros de posición a partir del parámetro 1. Si la expansión se realizase dentro de las comillas dobles, la expansión será una única palabra en la que el primer carácter de la variable especial de IFS separará los parámetros, o no aparecerá ningún espacio si IFS es nulo. El valor predeterminado de IFS es un espacio en blanco, una tabulación y una nueva línea. Si IFS no está configurado, el separador que se utiliza es un espacio en blanco, al igual que con el IFS predeterminado.
@Los parámetros de posición a partir del parámetro 1. Si la expansión se realizase dentro de las comillas dobles, cada parámetro se convertirá en una única palabra, así que, "$@" será equivalente a "$1" "$2". .. Si existe la probabilidad de que sus parámetros contengan espacios en blanco incorporados, es mejor que utilice esta forma.
#El número de parámetros, sin incluir el parámetro 0.

Nota: Cuando tenga más de 9 parámetros, no podrá utilizar $10 para referirse al décimo. Primero deberá procesar o guardar el primer parámetro ($1) y, después, utilizar el comando shift para soltar el parámetro 1 y bajar 1 todos los parámetros faltantes, para que el $10 se convierta en $9, y así consecutivamente. Se actualizará el valor de $# para reflejar el número de parámetros restantes. En la práctica, lo más normal es que usted quiera iterar sobre los parámetros para enviarlos a una función o script de shell, o a una lista creada sustituyendo comandos con una declaración para , así que, esta restricción es, en sí misma, un problema.

Ahora, puede definir una función sencilla para que sólo le diga cuantos parámetros hay y que los muestre como en el Listado 1.

Listado 1. Parámetros de la función
[ian@pinguino ~]$ testfunc () { echo "$# parameters"; echo "$@"; }
[ian@pinguino ~]$ testfunc
0 parameters

[ian@pinguino ~]$ testfunc a b c
3 parameters
a b c
[ian@pinguino ~]$ testfunc a "b c"
2 parameters
a b c

Los scripts de shell manejan los parámetros de la misma manera que las funciones. De hecho, con frecuencia se encontrará ensamblando scripts con partes de muchas funciones pequeñas. El Listado 2 muestra un script de shell, testfunc.sh, para la misma tarea sencilla y el resultado de ejecutarlo con una de las entradas anteriores. Acuérdese de marcar su script ejecutable con chmod +x.

Listado 2. Parámetros del script de shell
[ian@pinguino ~]$ cat testfunc.sh
#!/bin/bash
echo "$# parameters"
echo "$@";
[ian@pinguino ~]$ ./testfunc.sh a "b c"
2 parameters
a b c

En la Tabla 1, usted descubrió que el shell se puede referir a la lista de parámetros pasados como $* o $@, y que, si pone, o no, esas expresiones entre comillas afecta a cómo son interpretadas. Si utiliza $*, "$*", $@ o "$@", no verá mucha diferencia en el resultado de la función anterior, pero esté seguro de que las distinciones importarán mucho más cuando las cosas se vuelvan más complejas, ya que querrá analizar parámetros o pasar algunos a otras funciones o scripts. El Listado 3 muestra una función que imprime el número de parámetros y, después, imprime los parámetros según esas cuatro alternativas. El Listado 4 muestra la función en acción. La variable predeterminada de IFS utiliza un espacio como su primer carácter, así que el Listado 4 añade una barra vertical como el primer carácter de la variable IFS, para mostrar más claramente dónde se utiliza este carácter en la expansión de "$*".

Listado 3. Una función para explorar las diferencias en el manejo de parámetros
[ian@pinguino ~]$ type testfunc2
testfunc2 is a function
testfunc2 ()
{
    echo "$# parameters";
    echo Using '$*';
    for p in $*;
    do
        echo "[$p]";
    done;
    echo Using '"$*"';
    for p in "$*";
    do
        echo "[$p]";
    done;
    echo Using '$@';
    for p in $@;
    do
        echo "[$p]";
    done;
    echo Using '"$@"';
    for p in "$@";
    do
        echo "[$p]";
    done
}
Listado 4. Imprimir la información de los parámetros con testfunc2
[ian@pinguino ~]$ IFS="|${IFS}" testfunc2 abc "a bc" "1 2
> 3"
3 parameters
Using $*
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$*"
[abc|a bc|1 2
3]
Using $@
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$@"
[abc]
[a bc]
[1 2
3]

Estudie cuidadosamente las diferencias, especialmente para las formas entre comillas y los parámetros que incluyen espacios blancos, como espacios en blanco y caracteres de nueva línea. Observe del único par de caracteres [], que la expansión de "$*" es, en efecto, una única palabra.

Opciones y getopts

Los comandos tradicionales de UNIX y Linux consideran que algunos de los argumentos que se pasan son opciones. Históricamente, estos eran interruptores de caracteres únicos que se distinguían de otros caracteres por tener al principio un guion o un símbolo de menos. Se pueden combinar varias opciones por comodidad, como en el comando ls -lrt, que proporciona un largo listado del directorio (opción -l) en orden inverso (opción -r) ordenado por tiempo de modificación (opción -t).

Se puede utilizar la misma técnica con los scripts de shell; además, el comando incorporado getopts facilita su tarea. Para ver cómo funciona esto, evalúe el script de ejemplo, testopt.sh, que se muestra en el Listado 5.

Listado 5. El script testopt.sh
#!/bin/bash
echo "OPTIND inicia en $OPTIND"
while getopts ":pq:" optname
  do
    case "$optname" in
      "p")
        echo "Se ha especificado la opción $optname"
        ;;
      "q")
        echo "La opción $optname tiene el valor $OPTARG"
        ;;
      "?")
        echo "Opción desconocida $OPTARG"
        ;;
      ":")
        echo "Sin valor de argumentos para la opción $OPTARG"
        ;;
      *)
      # Should not occur
        echo "Error desconocido mientras se procesaban las opciones"
        ;;
    esac
    echo "OPTIND ahora es $OPTIND"
  done

El comando getopts utiliza un par devariables predeterminadas. La variable OPTIND inicialmente se establece en 1. Posteriormente, contiene el índice del siguiente parámetro que se debe procesar. El comando getopts devuelve true si encuentra una opción, así que un paradigma habitual del procesamiento de opciones utiliza un bucle while con una declaración case como en este ejemplo. El primer argumento para getopts es una lista de letras de opciones que se deben reconocer, en este caso, p y r. Dos puntos (:) después de una letra de opción indica que la opción requiere un valor; por ejemplo, una opción -f se puede utilizar para indicar el nombre de un archivo, como en el comando tar . Los dos puntos del principio de este ejemplo le dicen a getopts que sea silencioso y que suprima los mensajes de error habituales, ya que este script proporcionará su propio manejo de errores.

El segundo parámetro de este ejemplo optname es el nombre de una variable que recibirá el nombre de la opción encontrada. Si se espera que una opción tenga un valor, el valor, si está presente, se colocará en la variable OPTARG. En modo silencioso pueden ocurrir las dos siguientes condiciones de error.

  1. Si se encuentra una opción reconocible, el optname contendrá un ? y OPTARG contendrá la opción desconocida.
  2. Si se encuentra una opción que requiere un valor, pero no se encuentra el valor, optname contendrá un signo de : y OPTARG contendrá el nombre de la opción a la que le falta el argumento.

Si no está en modo silencioso, esos errores causarán un mensaje de error de diagnóstico y OPTARG se desconfigurará. El script puede utilizar los valores de ? o de : en optname para detectar y poder manejar el error.

El Listado 6 muestra dos ejemplos de la ejecución de este sencillo script.

Listado 6. Ejecución del script testopt.sh
[ian@pinguino ~]$ ./testopt.sh -p -q
OPTIND empieza en 1
Se ha especificado la opción p
OPTIND ahora es 2
No hay valor de argumento para la opción q
OPTIND ahora es 3
[ian@pinguino ~]$ ./testopt.sh -p -q -r -s tuv
OPTIND empieza en 1
Se ha especificado la opción p
OPTIND ahora es 2
La opción q tiene el valor -r
OPTIND ahora es 4
Opción s desconocida
OPTIND ahora es 5

Si tiene que hacerlo, puede pasar un conjunto de argumentos a getopts para que los evalúe. Usted mismo tendrá que restablecer OPTIND a 1 si quiere llamar a getopts para obtener un nuevo conjunto de argumentos en un script que ya haya utilizado con otros argumentos. Vea las páginas de información o del manual de bash para obtener más detalles.

Expansiones de parámetros

Ahora ya sabe cómo se pasan los parámetros a una función o script, y cómo identificar las opciones. Cómo empezar a procesar las opciones y parámetros. Estaría bien saber cuáles son los argumentos que quedan después de procesar las opciones. Es posible que quizás tenga que validar los valores de los parámetros, o asignar valores predeterminados a los parámetros que faltan. Esta sección presentará algunas de las expansiones de parámetros que están disponibles en bash. Por supuesto que usted también cuenta con todo el poder de los comandos de Linux o UNIX, como sed o awk para los trabajos más complejos, pero también debería saber cómo se utilizan las expansiones de shell.

Empecemos construyendo un script desde las funciones de análisis de opciones y de análisis de parámetros que vimos anteriormente. Nuestro script testargs.sh se muestra en el Listado 7.

Listado 7. El script testargs.sh
#!/bin/bash

showopts () {
  while getopts ":pq:" optname
    do
      case "$optname" in
        "p")
          echo "Se ha especificado la opción $optname"
          ;;
      "q")
        echo "La opción $optname tiene el valor $OPTARG"
        ;;
        "?")
          echo "Opción desconocida $OPTARG"
        ;;
        ":")
          echo "Sin valor de argumentos para la opción $OPTARG"
        ;;
        *)
        # Should not occur
        echo "Error desconocido mientras se procesaban las opciones"
        ;;
      esac
    done
  return $OPTIND
}

showargs () {
  for p in "$@"
    do
      echo "[$p]"
    done
}

optinfo=$(showopts "$@")
argstart=$?
arginfo=$(showargs "${@:$argstart}")
echo "Los argumentos son:"
echo "$arginfo"
echo "Las opciones son:"
echo "$optinfo"

Ejecute este script unas cuantas veces para ver cómo funciona, después lo examinaremos con más detalle. El Listado 8 muestra algo del resultado de muestra.

Listado 8. Ejecución del script testargs.sh
[ian@pinguino ~]$ ./testargs.sh -p -q qoptval abc "def ghi"
Los argumentos son:
[abc]
[def ghi]
Las opciones son:
Se ha especificado la opción p
La opción q tiene el valor qoptval
[ian@pinguino ~]$ ./testargs.sh -q qoptval -p -r abc "def ghi"
Los argumentos son:
[abc]
[def ghi]
Las opciones son:
La opción q tiene el valor qoptval
Se ha especificado la opción p
Opción desconocida r
[ian@pinguino ~]$ ./testargs.sh "def ghi"
Los argumentos son:
[def ghi]
Las opciones son:

Observe cómo se separan los argumentos de las opciones. La función showopts analiza las opciones como antes, pero utiliza la declaración "return" para devolver el valor de la variable OPTIND a la declaración de llamada. El procedimiento de llamada asigna este valor a una variable, argstart. Este valor luego se utiliza para seleccionar un subconjunto de los parámetros originales, que están formados por esos parámetros que no fueron procesados como opciones. Esto se realiza a través de la expansión de parámetro
${@:$argstart}
Acuérdese de envolver esta expresión en comillas, para mantener juntos los parámetros que tienen espacios incorporados, tal como vio en el Listado 2.

Si usted es un principiante con los scripts y las funciones, tenga en cuenta lo siguiente:

  1. La declaración return devuelve un valor de salida desde la función showopts, a la que se accede con $? a través del llamador. También se pueden utilizar valores de retorno de las funciones con comandos como test o while para controlar las ramificaciones y los bucles.
  2. Las funciones bash pueden incluir la palabra opcional "function", por ejemplo:
    function showopts ()
    Esto no forma parte del estándar POSIX y no es compatible en shells como dash, así que, si lo usa, no haga que su línea sea
    #!/bin/sh
    ya que esto le proporcionará el shell predeterminado del sistema, que es posible que no funcione como usted desee.
  3. La salida de la función, por ejemplo, la salida que produjo la declaración echo de estas dos funciones no se imprime, sino que se pone a disposición del llamador. Si no se asigna a una variable o no se utiliza como parte de la declaración de llamada, el shell intentará ejecutarla en vez de mostrarla.

Subconjuntos y subcadenas

La forma general de esta expansión es ${PARAMETER:OFFSET:LENGTH}, donde el argumento LENGTH es opcional. Así que, si usted sólo quiere seleccionar un subconjunto específico de los argumentos del script, puede utilizar la versión completa para decir cuántos elementos hay que seleccionar. Por ejemplo, ${@:4:3} se refiere a los tres argumentos que empiezan en el argumento 4, y que se llaman argumentos 4, 5 y 6. Esta expansión se puede utilizar para seleccionar parámetros individuales, aparte de los que se pueden acceder inmediatamente a través de los parámetros que van de $1 a $9. ${@:15:1} es una forma de acceder directamente al parámetro 15.

Esta expansión se puede utilizar con parámetros individuales y con el conjunto completo de parámetros que está representado por $* o $@. En este caso, el parámetro se trata como una cadena de caracteres y el número se refiere al intervalo y a la longitud. Por ejemplo, si la variable x tiene el valor "algún valor"", el
${x:3:5}
tendría el valor "e val", como se muestra en el Listado 9.

Listado 9. Subcadenas de caracteres de los valores del parámetro shell
[ian@pinguino ~]$ x="algún valor"
[ian@pinguino ~]$ echo "${x:3:5}"
e val

Longitudes

Ya ha visto que $# se refiere al número de parámetros y que la expansión ${PARAMETER:OFFSET:LENGTH} se aplica a parámetros individuales y a $* y $@, así que no le sorprenderá que la construcción análoga ${#PARAMETER} esté disponible para determinar la longitud de un parámetro individual. La sencilla función testlength que se muestra en el Listado 10 ilustra esto. Pruébelo por usted mismo.

Listado 10. Longitudes del parámetro
[ian@pinguino ~]$ testlength () { for p in "$@"; do echo ${#p};done }
[ian@pinguino ~]$ testlength 1 abc "def ghi"
1
3
7

Reconocimiento de patrones

La expansión de parámetro también incluye algunas capacidades de reconocimiento de patrones que tienen las mismas capacidades para utilizar caracteres de comodines que las que se utilizan en la expansión de nombres de archivos o comodines. Observe que esta no es una expresión regular de reconocimiento, tal como se utiliza por grep.

Tabla 2. Reconocimiento de patrones de la expansión de Shell
ConceptoPropósito
${PARAMETER#WORD}El shell expande WORD como expansión del nombre del archivo y elimina el patrón de reconocimiento más corto, si lo hubiera, del inicio del valor expandido de PARAMETER. Cuando se utiliza '@' o '$', se eliminan los patrones de cada parámetro de la lista.
${PARAMETER##WORD}Elimina el patrón de reconocimiento más largo desde el principio, en vez del más corto.
${PARAMETER%WORD}El shell expande WORD como expansión del nombre del archivo y elimina el patrón de reconocimiento más corto, si lo hubiera, del final del valor expandido de PARAMETER. Cuando se utiliza '@' o '$', se eliminan los patrones de cada parámetro de la lista.
${PARAMETER%%WORD}Elimina el patrón de reconocimiento más largo desde el final, en vez del más corto.
${PARAMETER/PATTERN/STRING}El shell expande PATTERN como expansión del nombre del archivo y reemplaza el patrón de reconocimiento más largo, si lo hubiera, en el valor expandido de PARAMETER. Para reconocer patrones al inicio del valor expandido de PARAMETER, utilice # como prefijo de PATTERN, o % si el reconocimiento se debería realizar al final. Si STRING está vacio, se puede omitir la cola / y se eliminan los reconocimientos. Cuando se utiliza '@' o '$', se substituyen los patrones de cada parámetro de la lista.
${PARAMETER//PATTERN/STRING}Realiza la sustitución de todos los reconocimientos, en vez de sólo el primero.

El Listado 11 muestra algo de la utilización básica de las expansiones de reconocimiento de patrones.

Listado 11. Ejemplos de reconocimiento de patrones
[ian@pinguino ~]$ x="a1 b1 c2 d2"
[ian@pinguino ~]$ echo ${x#*1}
b1 c2 d2
[ian@pinguino ~]$ echo ${x##*1}
c2 d2
[ian@pinguino ~]$ echo ${x%1*}
a1 b
[ian@pinguino ~]$ echo ${x%%1*}
a
[ian@pinguino ~]$ echo ${x/1/3}
a3 b1 c2 d2
[ian@pinguino ~]$ echo ${x//1/3}
a3 b3 c2 d2
[ian@pinguino ~]$ echo ${x//?1/z3}
z3 z3 c2 d2

Cómo juntarlo

Antes de que cubramos algunos de los puntos que faltan, echemos un vistazo a un ejemplo del mundo real de manejo de parámetros. Construí el paquete de autoría de developerWorks en un sistema Linux utilizando un script Bash. Almacenamos los diferentes archivos que necesito en subdirectorios de una biblioteca que llamaremos developerworks/library. La versión más reciente fue la 5.7, así que los archivos del esquema están en developerworks/library/schema/5.7, los archivos XSL están en developerworks/library/xsl/5.7 y las plantillas de muestra están en developerworks/library/schema/5.7/templates. Obviamente, un único parámetro que brinde la versión, 5.7 en este caso, sería suficiente para que el script construya rutas para todos esos archivos. Así que, el script toma el parámetro -v que debe tener un valor. La validación de este parámetro se realiza posteriormente, construyendo la ruta y después verificando que existe utilizando [ -d "$pathname" ]

Esto funciona bien para las creaciones de producción, pero, durante el desarrollo los archivos se almacenan en directorios permanentes:

  • developerworks/library/schema/5.8/archive/test-5.8/merge-0430
  • ydeveloperworks/library/xsl/5.8/archive/test-5.8/merge-0430 y
  • developerworks/library/schema/5.8/archive/test-5.8/merge-0430/templates-0430

donde la versión ahora es 5.8, y 0430 representa el mes y el día de la versión de prueba más reciente.

Para manejar esto, añadí un parámetro, -p, que contiene la parte complementaria de la información de la ruta—archive/test-5.8/merge-0430. Ahora, otra persona o yo podríamos olvidar la barra oblicua inicial o final, y algún usuario de Windows podría utilizar barras invertidas en vez de las normales, así que, decidí encargarme de eso en el script. Usted también observará que la ruta hacia el directorio de la plantilla contiene la fecha dos veces, así que necesitaba seleccionar la fecha, -0430 en esta ejecución.

El Listado 12 muestra el código que utilizo para manejar los dos parámetros y limpiar la ruta parcial según esos requisitos. El valor de la opción -v se almacena en la variable ssversion, mientras que la versión limpia de la variable -p se almacena en pathsuffix, y la fecha completa con el guion inicial se almacena en datesuffix. Los comentarios explican qué es lo que ocurre en cada paso. Usted reconocerá varias expansiones de parámetros, lo que incluye la longitud, en la subcadena de caracteres, el reconocimiento de patrones y el reemplazo de patrones en esta pequeña parte de script.

Listado 12. Análisis del parámetro para la compilación del paquete de autoría de developerWorks
while getopts ":v:p:" optval "$@"
  do
    case $optval in
      "v")
        ssversion="$OPTARG"
      ;;
      "p")
        # Convertir las barras invertidas en barras oblicuas
        pathsuffix="${OPTARG//\\//}"
        # Asegurar que esta es una / inicial y no una final
        [ ${pathsuffix:0:1} != "/" ] && pathsuffix="/$pathsuffix"
        pathsuffix=${pathsuffix%/}
        # Quitar el último guion y lo que sigue         dateprefix=${pathsuffix%-*}
        # Utilizar la longitud de lo que queda para obtener el guion y lo que sigue         [ "$dateprefix" != "$pathsuffix" ] && datesuffix="${pathsuffix:${#dateprefix}}"
        ;;
      *)
        errormsg="Error del parámetro u opción desconocida con opción - $OPTARG"
        ;;
    esac
  done

Como con la mayoría de las cosas de Linux y, quizás, de la programación general, esta no es la única solución para este problema, pero ilustra un uso más práctico de las expansiones que usted ha aprendido.

Valores predeterminados

En la última sección, vimos cómo se asignan valores de opciones a variables como ssversion o pathsuffix. En este caso, después se detectará una versión vacía y para las construcciones de producción aparecerá un sufijo de una ruta vacía, pero eso es aceptable. ¿Y si usted tuviese que asignar valores predeterminados para parámetros que no se han especificado? La expansión shell que se muestra en la Tabla 3 le ayudará con esta tarea.

Tabla 3. Expansión de shell relacionada con valores predeterminados
ConceptoPropósito
${PARAMETER:-WORD}Si no se ha establecido el PARAMETER o si es nulo, el shell expandirá WORD y sustituirá el resultado. El valor de PARAMETER no se cambia.
`${PARAMETER:=WORD}Si no se ha establecido el PARAMETER o si es nulo, el shell expandirá WORD y asignará el resultado a PARAMETER. Después este valor es sustituido. De esta manera no se pueden asignar valores a parámetros posicionales o a parámetros especiales.
${PARAMETER:?WORD}Si no se ha establecido el PARAMETER o si es nulo, el shell expandirá WORD y escribirá el resultado en el error estándar. Si WORD no está presente, en vez de eso se escribirá un mensaje. Si el shell no es interactivo, saldrá.
${PARAMETER:+WORD} Si no se ha establecido el PARAMETER o si es nulo, nada se sustituirá. En caso contrario, el shell expandirá WORD y sustituirá el resultado.

El Listado 13 ilustra esas expansiones y las diferencias entre ellas.

Listado 13. Sustitución por variables nulas o no establecidas.
[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:-'XYZ'}/${y:-'XYZ'}/$x/$y/"
/'XYZ'/abc def//abc def/
[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:='XYZ'}/${y:='XYZ'}/$x/$y/"
/'XYZ'/abc def/'XYZ'/abc def/
[[ian@pinguino ~]$ ( unset x;y="abc def"; echo "/${x:?'XYZ'}/${y:?'XYZ'}/$x/$y/" )\
>  >so.txt 2>se.txt
[ian@pinguino ~]$ cat so.txt
[ian@pinguino ~]$ cat se.txt
-bash: x: XYZ
[[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:+'XYZ'}/${y:+'XYZ'}/$x/$y/"
//'XYZ'//abc def/

Pasar parámetros

Cuando se están pasando parámetros, hay algunas sutilezas que pueden hacerle tropezar si no tiene cuidado. Ya conoce la importancia de las comillas y cómo afectan al uso de $* y $@, pero, considere el siguiente caso. Suponga que quiere tener un script o función que funcione en todos los archivos o, quizá, directorios del directorio de trabajo actual. Para esta ilustración, considere los scripts ll-1.sh y ll-2.sh que se muestran en el Listado 14.

Listado 14. Dos scripts sencillos
#!/bin/bash
# ll-1.sh
for f in "$@"
  do
    ll-2.sh "$f"
  done

#!/bin/bash
ls -l "$@"

El script ll-1.sh simplemente pasa cada uno de sus parámetros al script ll-2.sh y ll-2.sh hace un largo listado de directorios de los parámetros pasados. Mi directorio de pruebas contiene un par de archivos vacíos, "file1" y "file 2". El Listado 15 muestra el resultado de los scripts.

Listado 15. Ejecución de los scripts - 1
[ian@pinguino test]$ ll-1.sh *
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file1
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file 2

Hasta ahora, todo bien. Pero, si usted se olvida de utilizar el parámetro *, el script no hará nada. No funciona automáticamente sobre el contenido del directorio de trabajo actual como, por ejemplo, hace el comando ls . Una forma de corregirlo fácilmente es añadir una verificación para esta condición en ll-1.sh y utilizar el resultado del comando ls para generar la entrada para el ll-2.sh cuando no se proporciona nada al l1-sh. En el Listado 16 se muestra una posible solución.

Listado 16. Versión revisada de ll-1.sh
#!/bin/bash
# ll-1.sh - revision 1
for f in "$@"
  do
    ll-2.sh "$f"
  done
[ $# -eq 0 ] && for f in "$(ls)"
  do
    ll-2.sh "$f"
  done

Observe que hemos puesto comillas en el resultado del comando ls para asegurarnos de que manejamos correctamente el "archivo 2". El Listado 17 muestra el resultado de la ejecución del nuevo ll-1.sh con y sin el *.

Listado 17. Ejecución de los scripts - 2
[ian@pinguino test]$ ll-1.sh *
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file1
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file 2
[ian@pinguino test]$ ll-1.sh
ls: file1
file 2: No existe ese archivo o directorio

¿Está sorprendido? Cuando se pasan los parámetros, especialmente si son el resultado de un comando, las cosas se pueden volver complicadas. La pista aquí está en el mensaje de error que muestra que los nombres de los archivos están separados por caracteres de nueva línea. Hay muchas formas de solucionar este problema; una sencilla es utilizar el read incorporado como se muestra en el Listado 18. Pruébelo por usted mismo.

Listado 17. Ejecución de los scripts - 2
#!/bin/bash
# ll-1.sh - revision 2
for f in "$@"
  do
    ll-2.sh "$f"
  done
[ $# -eq 0 ] && ls | while read f
  do
    ll-2.sh "$f"
  done

La moral de la historia es que, si presta atención y prueba con entradas raras, sus scripts serán más confiables. ¡Buena suerte!

Conozca más

Si quiere saber más acerca de la creación de script bash en Linux, lea el tutorial "Prep de examen 102 del LPI: Shells, creación de scripts, programación y compilación," del que se extrajo parte de este artículo.


Recursos para Descargar


Temas relacionados

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Linux
ArticleID=1062772
ArticleTitle=Consejo de Linux: Parámetros de Bash y expansiones de parámetros
publish-date=05162007