bash - paso - script linux ejemplos
Cómo iterar sobre argumentos en un script Bash (6)
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=($@)
for (( j=0; j<argc; j++ )); do
echo ${argv[j]}
done
Tenga en cuenta que la respuesta de Robert es correcta y que también funciona en sh
. Puedes (de manera portátil) simplificarlo aún más:
for i in "$@"
es equivalente a:
for i
Es decir, no necesitas nada!
Pruebas ( $
es indicador de comando):
$ 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
Lo primero que leí sobre esto en Unix Programming Environment de Kernighan y Pike.
En bash
, help for
documentos esto:
for NAME [in WORDS ... ;] do COMMANDS; done
Si
''in WORDS ...;''
no está presente, entonces se supone''in "$@"''
.
Use "$@"
para representar todos los argumentos:
for var in "$@"
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: "$@"
es correcta y $*
(sin comillas) casi siempre es incorrecto. Esto se debe a que "$@"
funciona bien cuando los argumentos contienen espacios y funciona igual que $*
cuando no lo hacen. En algunas circunstancias, "$*"
está bien, pero "$@"
generalmente (pero no siempre) funciona en los mismos lugares. Sin cotizar, $@
y $*
son equivalentes (y casi siempre están equivocados).
Entonces, ¿cuál es la diferencia entre $*
, $@
, "$*"
y "$@"
? Todos están relacionados con ''todos los argumentos de la shell'', pero hacen cosas diferentes. Cuando no están entre comillas, $*
y $@
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 "$@"
trata los argumentos casi exactamente como estaban cuando se especificaron en la línea de comandos. "$@"
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 "$@"
.)
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 "$@"
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 "$@"
, aquí hay 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
$
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" "$@"
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.
aparse() {
while [[ $# > 0 ]] ; do
case "$1" in
--arg1)
varg1=${2}
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"