bash - paso - shell script linux español




Cómo iterar sobre argumentos en un script Bash (4)

Para casos simples también puedes usar shift . Trata la lista de argumentos como una cola, cada shift arroja el primer argumento, el número de cada argumento que queda se disminuye.

#this prints all arguments
while test $# -gt 0
do
    echo $1
    shift
done

Tengo un comando complejo del que me gustaría hacer un script de shell / bash. Puedo escribirlo en términos de $1 fácilmente:

foo $1 args -o $1.ext

Quiero poder pasar varios nombres de entrada al script. ¿Cuál es la forma correcta de hacerlo?

Y, por supuesto, quiero manejar nombres de archivos con espacios en ellos.


También puede acceder a ellos como elementos de una matriz, por ejemplo, si no desea iterar a través de todos ellos

argc=$#
argv=([email protected])

for (( j=0; j<argc; j++ )); do
    echo ${argv[j]}
done

Use "[email protected]" para representar todos los argumentos:

for var in "[email protected]"
do
    echo "$var"
done

Esto iterará sobre cada argumento y lo imprimirá en una línea separada. $ @ se comporta como $ * excepto que cuando se citan, los argumentos se dividen correctamente si hay espacios en ellos:

sh test.sh 1 2 '3 4'
1
2
3 4

Reescritura de una answer ahora eliminada por VonC .

La respuesta sucinta de Robert Gamble trata directamente con la pregunta. Este amplifica algunos problemas con los nombres de archivos que contienen espacios.

Vea también: $ {1: + "$ @"} en / bin / sh

Tesis básica: "[email protected]" es correcta y $* (sin comillas) casi siempre es incorrecto. Esto se debe a que "[email protected]" funciona bien cuando los argumentos contienen espacios y funciona igual que $* cuando no lo hacen. En algunas circunstancias, "$*" está bien, pero "[email protected]" generalmente (pero no siempre) funciona en los mismos lugares. Sin cotizar, [email protected] y $* son equivalentes (y casi siempre están equivocados).

Entonces, ¿cuál es la diferencia entre $* , [email protected] , "$*" y "[email protected]" ? Todos están relacionados con 'todos los argumentos de la shell', pero hacen cosas diferentes. Cuando no están entre comillas, $* y [email protected] hacen lo mismo. Tratan cada 'palabra' (secuencia de no espacios en blanco) como un argumento separado. Sin embargo, las formas citadas son bastante diferentes: "$*" trata la lista de argumentos como una sola cadena separada por espacios, mientras que "[email protected]" trata los argumentos casi exactamente como estaban cuando se especificaron en la línea de comandos. "[email protected]" expande a nada cuando no hay argumentos posicionales; "$*" expande a una cadena vacía, y sí, hay una diferencia, aunque puede ser difícil percibirla. Vea más información a continuación, después de la introducción del comando (no estándar) al .

Tesis secundaria: si necesita procesar argumentos con espacios y luego pasarlos a otros comandos, a veces necesita herramientas no estándar para ayudarlo. (O debe usar matrices, cuidadosamente: "${array[@]}" comporta de manera análoga a "[email protected]" .)

Ejemplo:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

¿Por qué no funciona eso? No funciona porque el shell procesa las cotizaciones antes de expandir las variables. Por lo tanto, para que el shell preste atención a las citas incrustadas en $var , debe usar eval :

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

Esto se vuelve realmente complicado cuando tienes nombres de archivo como " He said, "Don't do this!" " (Con comillas, comillas dobles y espacios).

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

Los shells (todos ellos) no hacen que sea tan fácil de manejar tales cosas, por lo que (curiosamente) muchos programas de Unix no hacen un buen trabajo al manejarlos. En Unix, un nombre de archivo (componente único) puede contener cualquier carácter excepto barra diagonal y NUL '\0' . Sin embargo, los shells no recomiendan espacios, nuevas líneas o pestañas en ningún lugar de los nombres de las rutas. También es la razón por la que los nombres de archivos Unix estándar no contienen espacios, etc.

Cuando se trata de nombres de archivos que pueden contener espacios y otros caracteres molestos, debe tener mucho cuidado y hace mucho tiempo descubrí que necesitaba un programa que no sea estándar en Unix. Lo llamo escape (la versión 1.1 fue fechada 1989-08-23T16: 01: 45Z).

Este es un ejemplo de escape en uso, con el sistema de control SCCS. Es un script de portada que hace tanto un delta (pensar en check-in ) como un get (pensar en check-out ). Varios argumentos, especialmente -y (la razón por la que realizó el cambio) contendrían espacios en blanco y líneas nuevas. Tenga en cuenta que la secuencia de comandos data de 1992, por lo que usa marcas de retroceso en lugar de la notación de $(cmd ...) y no usa #!/bin/sh en la primera línea.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "[email protected]"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(Probablemente no usaría escape tan a fondo en estos días, no es necesario con el argumento -e , por ejemplo), pero en general, este es uno de mis scripts más simples que usan escape .

El programa de escape simplemente genera sus argumentos, al igual que echo , pero garantiza que los argumentos estén protegidos para su uso con eval (un nivel de eval ; tengo un programa que hizo la ejecución remota de la shell y que necesitaba escapar de la salida de escape ).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

Tengo otro programa llamado al que enumera sus argumentos uno por línea (y es aún más antiguo: versión 1.1 con fecha 1987-01-27T14: 35: 49). Es más útil al depurar scripts, ya que se puede conectar a una línea de comandos para ver qué argumentos se pasan realmente al comando.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ Agregado: Y ahora para mostrar la diferencia entre las distintas notaciones "[email protected]" , aquí hay un ejemplo más:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

Observe que nada conserva los espacios en blanco originales entre * y */* en la línea de comandos. Además, tenga en cuenta que puede cambiar los 'argumentos de la línea de comando' en el shell utilizando:

set -- -new -opt and "arg with space"

Esto establece 4 opciones, ' -new ', ' -opt ', ' and ', y ' arg with space '.
]

Hmm, esa es una respuesta bastante larga, tal vez exégesis sea ​​el mejor término. El código fuente para el escape disponible a pedido (correo electrónico para el primer nombre punto apellido en gmail punto com). El código fuente de al es increíblemente simple:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

Eso es todo. Es equivalente al script test.sh que mostró Robert Gamble, y podría escribirse como una función de shell (pero las funciones de shell no existían en la versión local de la shell Bourne cuando escribí por primera vez).

También tenga en cuenta que puede escribir al como un simple script de shell:

[ $# != 0 ] && printf "%s\n" "[email protected]"

El condicional es necesario para que no produzca resultados cuando no se pasa ningún argumento. El comando printf producirá una línea en blanco con solo el argumento de cadena de formato, pero el programa C no produce nada.







command-line