encontrar -exec con múltiples comandos

430

Estoy tratando de usar find -exec con múltiples comandos sin ningún éxito. ¿Alguien sabe si comandos como los siguientes son posibles?

find *.txt -exec echo "$(tail -1 '{}'),$(ls '{}')" \;

Básicamente, estoy tratando de imprimir la última línea de cada archivo txt en el directorio actual e imprimir al final de la línea, una coma seguida del nombre del archivo.

Andy
fuente
44
superuser.com/questions/236601/…
Ignacio Vazquez-Abrams
1
En cuanto a verificar la posibilidad del comando, ¿no lo probó en su sistema?
Sriram
1
Desde la findpágina del manual: There are unavoidable security problems surrounding use of the -exec option; you should use the -execdir option instead.unixhelp.ed.ac.uk/CGI/man-cgi?find
JVE999
1
Relacionado: unix.stackexchange.com/questions/156008/…
Kusalananda
El enlace @ JVE999 está roto, alternativa en ss64.com/bash/find.html
Keith M

Respuestas:

659

findacepta múltiples -execporciones al comando. Por ejemplo:

find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;

Tenga en cuenta que en este caso el segundo comando solo se ejecutará si el primero regresa con éxito, como lo menciona @Caleb. Si desea que ambos comandos se ejecuten independientemente de su éxito o fracaso, puede usar esta construcción:

find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;
Gitano
fuente
cómo grep dos veces? esto está fallando: find ./* -exec grep -v 'COLD,' {} \; -exec egrep -i "my_string" {} \;
rajeev
51
@rajeev El segundo ejecutivo solo se ejecutará si el código de retorno del primero devuelve correctamente, de lo contrario se omitirá. Probablemente esto debería notarse en esta respuesta.
Caleb
1
Esta sería la respuesta
pylover el
1
Tenga en cuenta el uso de -nen algunas de las otras respuestas para suprimir la nueva línea generada por echo, lo cual es útil si su segundo comando produce solo una línea de salida y desea que sean más fáciles de leer.
William Turrell
Gran solución Funciona de maravilla.
Philippe Delteil
98
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;
Avari
fuente
3
Si desea ejecutar Bash en lugar de Bourne, también puede usarlo en ... -exec bash -c ...lugar de ... -exec sh -c ....
Kenny Evitt
99
Nunca incrustar {}en el código de shell. Ver unix.stackexchange.com/questions/156008/…
Kusalananda
3
+1 @Kusalananda Inyectar nombres de archivo es frágil e inseguro. Utiliza parámetros. ver SC2156
pambda
52

Uno de los siguientes:

find *.txt -exec awk 'END {print $0 "," FILENAME}' {} \;

find *.txt -exec sh -c 'echo "$(tail -n 1 "$1"),$1"' _ {} \;

find *.txt -exec sh -c 'echo "$(sed -n "\$p" "$1"),$1"' _ {} \;
Pausado hasta nuevo aviso.
fuente
12
¿Para qué sirve el guión bajo antes de {}?
qed
2
@qed: es un valor desechable que ocupa el lugar de $0. Intente esto con "foobar" en lugar de "_": find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;- la salida: "[foobar] [/ usr / bin / find]".
Pausado hasta nuevo aviso.
1
@ XuWang: Sí, diría que ese es el caso. Como sabes, $0generalmente es el nombre del programa ( ARGV[0]).
Pausado hasta nuevo aviso.
3
Es crítico, para este método, que el script que se pasa sh -cesté entre comillas simples, no dobles. De $1lo contrario, está en el alcance incorrecto.
Nick
1
Las comillas @Nick no tienen nada que ver con eso: puede escribir '$1'con comillas dobles siempre que escape de la variable ( "\$1"). También puedes escapar de otros personajes ( "\"").
Camilo Martin
22

Otra forma es así:

multiple_cmd() { 
    tail -n1 $1; 
    ls $1 
}; 
export -f multiple_cmd; 
find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;

en una linea

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • " multiple_cmd()" - es una función
  • " export -f multiple_cmd": lo exportará para que cualquier otra subshell pueda verlo
  • " find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;" - encuentra que ejecutará la función en tu ejemplo

De esta forma, multiple_cmd puede ser tan largo y complejo como lo necesite.

Espero que esto ayude.

al3x2ndru
fuente
perfecto, justo lo que necesitaba!
Anentropic
no funciona para mí en OSX
Thomas
@Thomas lo hace, pero prueba este liner, probado en osx. Hice un directorio llamado 'aaa' con algunos archivos / directorios allí y CDd. Entonces, ~/aaa$ acmd() { echo x \"$1\" x; }; export -f acmd; find . -exec bash -c 'acmd {}' \;
barlop
@barlop, lo intentaré; ¡Gracias!
Thomas
14

Hay una manera más fácil:

find ... | while read -r file; do
    echo "look at my $file, my $file is amazing";
done

Alternativamente:

while read -r file; do
    echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"
Camilo Martin
fuente
1
los nombres de archivo pueden tener nuevas líneas, es por eso que find tiene el argumento -print0 y xargs tiene el argumento -0
abasterfield
@abasterfield Siempre espero nunca encontrarlos en la naturaleza jajaja
Camilo Martin
1
lo que quería hacer era "buscar ... -exec zcat {} | wc -l \;" que no funcionó Sin embargo, encuentre ... | mientras lee el archivo -r; hacer eco "$ archivo: zcat $file | wc -l"; hecho funciona, así que gracias!
Greg Dougherty
En el comentario anterior tengo "ticks atrás" alrededor de "zcat $ file | wc -l". Desafortunadamente, SO los convierte en formato, así que lo agregué como una respuesta real con el código correcto visible
Greg Dougherty
1
@GregDougherty Puede escapar de los backticks `para hacer que use barras invertidas de la siguiente manera: \​`(aún así, esa es otra buena razón para usar $()en su lugar).
Camilo Martin
8

No sé si puede hacer esto con find, pero una solución alternativa sería crear un script de shell y ejecutar esto con find.

lastline.sh:

echo $(tail -1 $1),$1

Hacer el script ejecutable

chmod +x lastline.sh

Uso find:

find . -name "*.txt" -exec ./lastline.sh {} \;
Andrea Spadaccini
fuente
77
los backticks están en desuso, fomente el uso de $ (...), que es mejor legible, independiente de la fuente y fácil de anidar. Gracias.
Usuario desconocido
4

La primera respuesta de Denis es la respuesta para resolver el problema. Pero, de hecho, ya no es un hallazgo con varios comandos en un solo ejecutivo, como sugiere el título. Para responder a un ejecutivo con varios comandos, tendremos que buscar otra cosa para resolver. Aquí hay un ejemplo:

Mantenga las últimas 10000 líneas de archivos .log que se han modificado en los últimos 7 días usando 1 comando exec usando varias referencias {}

1) vea qué hará el comando en qué archivos:

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "echo tail -10000 {} \> fictmp; echo cat fictmp \> {} " \;

2) Hazlo: (nota no más "\>" pero solo ">" esto es necesario)

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "tail -10000 {} > fictmp; cat fictmp > {} ; rm fictmp" \;

Eric Duruisseau
fuente
Pero esto se romperá si uno de los nombres de archivo tiene un espacio, creo.
Camilo Martin
4

Gracias a Camilo Martin, pude responder una pregunta relacionada:

Lo que quería hacer era

find ... -exec zcat {} | wc -l \;

que no funcionó Sin embargo,

find ... | while read -r file; do echo "$file: `zcat $file | wc -l`"; done

funciona, así que gracias!

Greg Dougherty
fuente
2

Extendiendo la respuesta de @Tinker,

En mi caso, necesitaba hacer un command | command | commandinterior -execpara imprimir tanto el nombre del archivo como el texto encontrado en los archivos que contienen un texto determinado.

Pude hacerlo con:

find . -name config -type f \( -exec  grep "bitbucket" {} \; -a -exec echo {} \;  \) 

el resultado es:

    url = git@bitbucket.org:a/a.git
./a/.git/config
    url = git@bitbucket.org:b/b.git
./b/.git/config
    url = git@bitbucket.org:c/c.git
./c/.git/config
usuario9869932
fuente
1
También puede imprimir el nombre de archivo y el contenido grep'd en una sola línea pasando /dev/nullcomo segundo argumento al grepcomando con un -execparámetro:find . -name config -type f -exec grep "bitbucket" {} /dev/null \;
Bill Feth
1

debería usar xargs :)

find *.txt -type f -exec tail -1 {} \; | xargs -ICONSTANT echo $(pwd),CONSTANT

otro (trabajando en osx)

find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'
smapira
fuente
3
Esto pasa por alto un caso de uso importante para findsituaciones en las que el número de archivos coincidentes es demasiado grande para una línea de comando. -execes una forma de sortear este límite. La canalización a una empresa de servicios públicos pierde ese beneficio.
Chris Johnson
xargs -nexiste para elegir el número de coincidencias por invocación. xargs -n 1 foocmdse ejecutará foocmd {}para cada partido.
AndrewF
0

Una find+xargsrespuesta

El siguiente ejemplo busca todos los .htmlarchivos y crea una copia con la .BAKextensión añadida (por ejemplo, 1.html> 1.html.BAK).

Comando único con múltiples marcadores de posición

find . -iname "*.html" -print0 | xargs -0 -I {} cp -- "{}" "{}.BAK"

Múltiples comandos con múltiples marcadores de posición

find . -iname "*.html" -print0 | xargs -0 -I {} echo "cp -- {} {}.BAK ; echo {} >> /tmp/log.txt" | sh

# if you need to do anything bash-specific then pipe to bash instead of sh

Este comando también funcionará con archivos que comiencen con un guión o contengan espacios como, por ejemplo, entre -my file.htmlcomillas de parámetros y --después de lo cpcual se indica cpel final de los parámetros y el comienzo de los nombres de archivo reales.

-print0 canaliza los resultados con terminadores de byte nulo.


para xargs, el -I {}parámetro define {}como el marcador de posición; puede usar el marcador de posición que desee; -0indica que los elementos de entrada están separados por nulos.

ccpizza
fuente