410 votos

Cómo iterar a través de argumentos en un script de bash

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

foo $1 args -o $1.ext

Quiero ser capaz de pasar varios nombres de entradas en la secuencia de comandos - ¿cuál es la forma correcta de hacerlo? Por supuesto que quiero para manejar nombres de archivo con espacios.

653voto

Robert Gamble Puntos 41984

Uso "$@" a representar a todos los argumentos:

for var in "$@"
do
    echo "$var"
done

Esto va a iterar cada argumento y a imprimir en una línea separada. $@ se comporta como $* excepto que cuando citó los argumentos se dividen correctamente si hay espacios en ellos:

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

140voto

Jonathan Leffler Puntos 299946

Reescritura de un ya eliminado respuesta por VonC

Robert Jugar la lapidaria respuesta trata directamente con la pregunta. Este amplifica en algunos temas con nombres que contengan espacios.

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

Tesis de fondo: "$@" es correcto, y $* (sin comillas) es casi siempre mal. Esto es debido a que "$@" funciona bien cuando los argumentos contener espacios), y funciona de la misma como $* cuando no es así. En algunas circunstancias, "$*" está bien también, pero "$@" generalmente (pero no siempre a) obras en los mismos lugares. No cotizadas en bolsa, $@ y $* son equivalentes (y casi siempre mal).

[Añadido: Entonces, ¿cuál es la diferencia entre los $*, $@, "$*"y "$@"? Todos ellos están relacionados con " todos los argumentos del shell', pero que hagan cosas diferentes. Cuando no cotizan en bolsa, $* y $@ hacer la misma cosa. Tratan a cada 'palabra' (secuencia de no espacios en blanco) como un argumento separado. El citado formas son muy diferentes, a pesar de que: "$*" trata el argumento de la lista como una sola cadena separada por espacios, mientras que "$@" trata los argumentos de la casi exactamente igual a como estaban cuando se especifique en la línea de comandos. Ver más información abajo, después de la introducción de la (no estándar) de comando al.
]

Secundario de la tesis: si usted necesita procesar los argumentos con los espacios y, a continuación, que les pase a otros comandos, que a veces se necesita no estándar herramientas para ayudar.

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 le funciona? No funciona debido a que la cáscara de los procesos de citas antes de que se expande las variables. Así, para obtener el shell para prestar atención a las comillas incrustado en $var, usted tiene que 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 pone muy difícil cuando usted tiene los nombres de archivo, tales como "He said, "Don't do this!"" (con comillas dobles y comillas y los 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
    $ 

Las conchas (todos ellos) no hacen especialmente fácil de manejar cosas, así que (curiosamente) muchos programas Unix no hacer un buen trabajo de el manejo de ellos. En Unix, el nombre de un archivo (solo componente) puede contener cualquier carácter excepto slash y NUL '\0'. Sin embargo, las conchas recomendamos sin espacios o saltos de línea o fichas en cualquier lugar de nombres de ruta de acceso. Es también la razón por la estándar de Unix los nombres de archivo no puede contener espacios, etc.

Cuando se trata con los nombres de archivo que puede contener espacios y otros caracteres problemáticos, tienes que ser extremadamente cuidadoso, y me encontré con hace tiempo que necesitaba un programa que no es un estándar en Unix. Me llaman escape (versión 1.1 fecha de 1989-08-23T16:01:45Z).

Aquí es un ejemplo de escape - con el CCSC sistema de control. Es una cubierta de script que hace tanto delta (piense en el check-in) y un get (creo check-out). Diversos argumentos, especialmente -y (la razón por la que hizo el cambio) podría contener espacios en blanco y saltos de línea. Tenga en cuenta que la secuencia de comandos data del año 1992, por lo que se utiliza de nuevo-las garrapatas en lugar de $(cmd ...) notación y no uso #!/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 "$@"
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 lo haría uso escapar tan a fondo en estos días - es no se necesitan con el -e argumento, por ejemplo - pero, en general, este es uno de mis más sencillos scripts usando escape.)

La escape programa simplemente salidas de sus argumentos, en lugar de como echo no, pero se asegura de que los argumentos están protegidos para uso con eval (un nivel de eval; tengo un programa que hizo shell remoto de ejecución, y que necesitábamos para 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 que se llama al que las listas de sus argumentos uno por línea (y es aún más antigua: la versión 1.1 fecha 1987-01-27T14:35:49). Es más útil cuando la depuración de secuencias de comandos, como puede ser conectado a una línea de comandos para ver qué argumentos se pasan al comando.

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

[Añadido: Y ahora, para mostrar la diferencia entre los "$@" notaciones, aquí es un ejemplo más:

$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ 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
$

Aviso de que nada se conserva el original de los espacios en blanco entre los * y */* en la línea de comandos. También, tenga en cuenta que puede cambiar el 'argumentos de línea de comandos " en la shell mediante el uso de:

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

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

Hmm, eso es una larga respuesta - tal vez la exégesis es el mejor término. Código fuente para escape disponible sobre pedido (por correo electrónico a nombre de punto apellido at gmail dot com). El código fuente para al es increíblemente sencillo:

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

Eso es todo. Es equivalente al test.sh secuencia de comandos que Robert Gamble mostró, y puede ser escrita como una función de shell (pero shell funciones no existen en la versión local de Bourne shell cuando por primera vez me escribió al).

67voto

Alok Singhal Puntos 33073

Tenga en cuenta que Robert la respuesta es correcta y funciona en sh como bueno. Usted puede (portable) simplificar aún más:

for i in "$@"

es equivalente a:

for i

Es decir, usted no necesita nada!

Pruebas ($ es símbolo del sistema):

$ set a b "spaces here" d
$ for i; do echo $i; done
a
b
spaces here
d
$ for i in "$@"; do echo $i; done
a
b
spaces here
d

La primera vez que leí sobre esto en Unix Entorno de Programación por Kernighan y Pike.

En bash, help for documentos de este:

for NAME [in WORDS ... ;] do COMMANDS; done

Si 'in WORDS ...;' no está presente, 'in "$@"' es asumido.

26voto

nuoritoveri Puntos 488

Para los casos más sencillos, también puede utilizar shift. Se trata lista de argumentos como una cola, cada shift lanza el primer argumento, el número de cada uno de los argumentos que está a la izquierda disminuye.

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

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X