2010-04-14 9 views
11

A menudo tengo un comando que procesa un archivo, y quiero ejecutarlo en cada archivo en un directorio. ¿Hay alguna forma incorporada de hacer esto?bash "map" equivalente: ejecutar comando en cada archivo

Por ejemplo, decir que tengo un programa data que da salida a un número importante de un archivo:

./data foo 
137 
./data bar 
42 

quiero ejecutarlo en todos los archivos en el directorio de alguna manera como esto:

map data `ls *` 
ls * | map data 

para producir una salida como ésta:

foo: 137 
bar: 42 

Respuesta

15

Si usted está tratando de ejecutar su programa de data en un montón de archivos, la manera más fácil/menos complicada es utilizar -exec en find.

Digamos que quería ejecutar data en todos los archivos txt en el directorio actual (y subdirectorios). Esto es todo lo que se necesita:

find . -name "*.txt" -exec data {} \; 

Si quiere restringir el al directorio actual, usted puede hacer esto:

find . -maxdepth 1 -name "*.txt" -exec data {} \; 

Hay un montón de opciones con find.

+1

oh sí, ¡esto es realmente lo que quiero! ty – Claudiu

7

Si lo que desea es ru comando na en cada archivo que se puede hacer esto:

for i in *; do data "$i"; done 

Si también desea mostrar el nombre del archivo que se está trabajando actualmente en la entonces usted podría utilizar esto:

for i in *; do echo -n "$i: "; data "$i"; done 
+1

Con la advertencia de citar '$ I' por lo que los archivos con espacios en sus nombres no reciben tratamiento como múltiples argumentos a lo que se conoce programa –

+0

Daniel: fijo, gracias. –

+1

Usted puede conseguir lejos con un simple bucle en este caso porque el 'ls' se puede convertir en expansión pegote. Si realmente se desea utilizar la salida de un comando, el bucle se repartirán en todos los espacios en blanco incrustado, por lo que es probable que desee establecer '$ IFS' a sólo saltos de línea - véase mi respuesta si es necesario. – Cascabel

7

Parece que desea xargs :

find . --maxdepth 1 | xargs -d'\n' data 

Para imprimir cada comando en primer lugar, se pone un poco más complejo:

find . --maxdepth 1 | xargs -d'\n' -I {} bash -c "echo {}; data {}" 
+0

ah agradable, el más conciso hasta el momento.¿Hay alguna manera fácil de imprimir el archivo en el que está trabajando actualmente? – Claudiu

+0

'data' ciertamente es bienvenido para imprimir el nombre de archivo actual si le gusta ... – Cascabel

+0

@Claudiu: editado para mostrar el archivo. – Stephen

1

Prueba esto:

for i in *; do echo ${i}: `data $i`; done 
2

Los métodos comunes son:

ls * | while read file; do data "$file"; done 

for file in *; do data "$file"; done 

El segundo puede tener problemas si tiene espacios en blanco en los nombres de archivo; en ese caso usted probablemente querrá asegurarse de que funciona en un subnivel, y establecer IFS:

(IFS=$'\n'; for file in *; do data "$file"; done) 

se puede envolver fácilmente el primero en una secuencia de comandos:

#!/bin/bash 
# map.bash 

while read file; do 
    "$1" "$file" 
done 

el cual puede ser ejecutado como lo solicitaste, solo ten cuidado de no ejecutar accidentalmente nada tonto con él. La ventaja de utilizar una construcción de bucle es que puede colocar fácilmente varios comandos dentro de ella como parte de un proyecto de una línea, a diferencia de xargs, donde tendrá que colocarlos en un script ejecutable para que se ejecute.

Por supuesto, también puede simplemente utilizar la utilidad xargs:

find -maxdepth 0 * | xargs -n 1 data 

Tenga en cuenta que usted debe asegurarse de que los indicadores están apagados (ls --indicator-style=none) si normalmente se utilicen, o la @ anexa a los enlaces simbólicos se convertirán en nombres de archivo inexistentes.

+0

use 'para el archivo en *' en lugar de 'para el archivo en $ (ls *)' –

+0

@glenn jackman: Me doy cuenta de eso, y estaba cubierto en otra respuesta. Intenté proporcionar la respuesta general aquí, porque no siempre es simple globbing que obtiene su lista de archivos. Puede ser 'grep -l',' find ... ', quién sabe. – Cascabel

+0

Tiene la palabra correcta en 'grep -l' y' find'; le daré +1 si la reemplaza en su respuesta. Puede analizar con seguridad su salida bien definida. 'ls' tiene un formato de salida no documentado y, por lo tanto, es una historia diferente. – ignis

0

Se puede crear un script de shell, así:

#!/bin/bash 
cd /path/to/your/dir 
for file in `dir -d *` ; do 
    ./data "$file" 
done 

que recorra todos los archivos en/ruta/a/su/dir y se ejecuta la secuencia de comandos "datos" en él. Asegúrese de modificar el script anterior para que sea ejecutable.

5

Debe evitar parsing ls:

find . -maxdepth 1 | while read -r file; do do_something_with "$file"; done 

o

while read -r file; do do_something_with "$file"; done < <(find . -maxdepth 1) 

Este último no crea un subnivel fuera del bucle while.

+3

+1 Para no analizar ls. – Juliano

0

ls no maneja espacios en blanco, avances de línea y otras cosas funky en nombres de archivo y debe evitarse siempre que sea posible.

find solo es útil si desea sumergirse en subdirectorios, o si desea utilizar otras de las otras opciones (mtime, tamaño, nombre).

Pero muchos comandos de manejar múltiples archivos de sí mismos, por lo que no necesitan un bucle for:

for d in * ; do du -s $d; done 

pero

du -s * 
md5sum e* 
identify *jpg 
grep bash ../*.sh 
1

Dado que pidió específicamente acerca de esto en términos de "mapa", yo pensé en compartir esta función que tengo en mi biblioteca personal shell:

# map_lines: evaluate a command for each line of input 
map_lines() 
{ 
     while read line ; do 
       $1 $line 
     done 
} 

yo uso esto en el ma nner que para una solución:

$ ls | map_lines ./data 

lo he denominado map_lines en lugar de un mapa ya que asumí algún día puede implementar una map_args donde se usaría así:

$ map_args ./data * 

Esa función se vería de esta manera:

map_args() 
{ 
    cmd="$1" ; shift 
    for arg ; do 
     $cmd "$arg" 
    done 
} 
2

paralelo GNU se especializa en la fabricación de este tipo de asignaciones:

parallel data ::: * 

Se ejecutará un trabajo en cada núcleo de CPU en paralelo.

GNU Parallel es un paralelizador general y hace que sea fácil ejecutar trabajos en paralelo en la misma máquina o en varias máquinas a las que tiene acceso ssh.

Si tiene 32 trabajos diferentes que desee ejecutar en 4 CPUs, una manera directa para paralelizar es correr 8 puestos de trabajo en cada CPU:

Simple scheduling

paralelo GNU vez genera un nuevo proceso cuando uno termina - mantener la CPU activa y por lo tanto el ahorro de tiempo:

GNU Parallel scheduling

instalación

Si GNU Parallel no está empaquetado para su distribución, puede hacer una instalación personal, que no requiere acceso raíz. Se puede hacer en 10 segundos al hacer esto:

(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash 

Para otras opciones de instalación están en http://git.savannah.gnu.org/cgit/parallel.git/tree/README

Más información

Ver más ejemplos: http://www.gnu.org/software/parallel/man.html

Vea los videos de introducción: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Walk through t que tutorial: http://www.gnu.org/software/parallel/parallel_tutorial.html

Inscríbete a la lista de correo electrónico para obtener ayuda: https://lists.gnu.org/mailman/listinfo/parallel

0

que acabo de escribir este guiónespecíficamente para hacer frente a la misma necesidad.

http://gist.github.com/kindaro/4ba601d19f09331750bd

Se utiliza find para construir un conjunto de archivos adaptación, que permite la selección más fina de los archivos de mapa Desde sino que permite una ventana de errores más duros también.

Diseñé dos modos de funcionamiento: el primer modo ejecuta un comando con el "archivo de origen" y "archivo de destino" argumentos, mientras que el segundo modo suministra el contenido del archivo fuente a un comando como la entrada estándar y escribe su stdout en un archivo de destino.

Podemos considerar más añadiendo apoyo a la ejecución en paralelo y tal vez limitar el conjunto de la costumbre encontrar argumentos a unos pocos las más necesarias. No estoy muy seguro de si eso es lo correcto.

Cuestiones relacionadas