Usando punto y coma (;) vs plus (+) con exec en find

159

¿Por qué hay una diferencia en la producción entre usar

find . -exec ls '{}' \+

y

find . -exec ls '{}' \;

Tengo:

$ find . -exec ls  \{\} \+
./file1  ./file2

.:
file1  file2  testdir1

./testdir1:
testdir2

./testdir1/testdir2:


$ find . -exec ls  \{\} \;
file1  file2  testdir1
testdir2
./file2
./file1
Ankur Agarwal
fuente

Respuestas:

249

Esto podría ilustrarse mejor con un ejemplo. Digamos que findaparece estos archivos:

file1
file2
file3

Usando -execcon un punto y coma ( find . -exec ls '{}' \;), se ejecutará

ls file1
ls file2
ls file3

Pero si usa un signo más en su lugar ( find . -exec ls '{}' \+), se pasan tantos nombres de archivo como argumentos como argumentos a un solo comando:

ls file1 file2 file3

El número de nombres de archivo solo está limitado por la longitud máxima de la línea de comando del sistema. Si el comando excede esta longitud, se llamará al comando varias veces.

Martín
fuente
1
Gracias. Esto es muy útil para querer ordenar los archivos resultantes: find -maxdepth 1 -type f -mtime -1 -exec ls -ltr {} \ +
fbas
1
Tonto q: noto que +asociado con -execsiempre se escapa, pero +asociado con -mtimeno. ¿Sabes la razón? Supongo que es un hábito de escapar ;asociado con -exec.
kevinarpe
3
@kevinarpe de hecho, lo atribuiría a la costumbre ;. No puedo imaginar que alguna vez sea necesario escapar+
Martin
36

Todas las respuestas hasta ahora son correctas. Ofrezco esto como una demostración más clara (para mí) del comportamiento que se describe utilizando en echolugar dels :

Con un punto y coma, el comando echose llama una vez por archivo (u otro objeto del sistema de archivos) encontrado:

$ find . -name 'test*' -exec echo {} \;
./test.c
./test.cpp
./test.new
./test.php
./test.py
./test.sh

Con un plus, el comando echose llama una sola vez. Cada archivo encontrado se pasa como argumento.

$ find . -name 'test*' -exec echo {} \+
./test.c ./test.cpp ./test.new ./test.php ./test.py ./test.sh

Si findaparece un gran número de resultados, puede encontrar que el comando que se llama chokes en el número de argumentos.

Johnsyweb
fuente
1
¿No debería encontrar agregar los resultados solo a un número que hace que sea seguro pasarlo al shell? Al menos es lo que xargshacer ... en principio nunca se ahoga por demasiados argumentos.
Rmano
1
@Rmano: He visto find(y xargs) en Solaris emitir más argumentos de los que podrían consumirse. Los xargs(y find) en findutils de GNU parecen comportarse de manera más sensata, pero no todos usan GNU.
Johnsyweb
2
@Johnsyweb, todos los POSIX findintentarían evitar alcanzar el límite en la cantidad de argumentos. Y eso incluye Solaris '(al menos 10). Donde puede fallar es si haces algo como find ... -exec ksh -c 'cmd "$@" "$@"' sh {} +o find ... -exec ksh -c 'files="$*" cmd "$@"' sh {} +, pero findrealmente no se puede culpar por eso. Tenga en cuenta que GNU findfue una de las últimas findimplementaciones de soporte +(solía ser una molestia para portar script a sistemas GNU).
Stephane Chazelas
19

De man find:

-exec comando;

Ejecutar comando; verdadero si se devuelve el estado 0. Todos los siguientes argumentos para encontrar se consideran argumentos del comando hasta que un argumento que consista en '; se encuentra La cadena '{}' se reemplaza por el nombre del archivo actual que se procesa en todas partes donde aparece en los argumentos del comando, no solo en los argumentos donde está solo, como en algunas versiones de find. Es posible que sea necesario escapar de estas dos construcciones (con un '\') o citarlas para protegerlas de la expansión del shell. Consulte la sección de EJEMPLOS sec para ver ejemplos del uso de la opción '-exec'. El comando especificado se ejecuta una vez para cada archivo coincidente. El comando se ejecuta en el directorio de inicio. Hay problemas de seguridad inevitables en torno al uso de la opción -exec;

-exec comando {} +

Esta variante de la opción -exec ejecuta el comando especificado en los archivos seleccionados, pero la línea de comando se crea agregando cada nombre de archivo seleccionado al final ; El número total de invocaciones del comando será mucho menor que el número de archivos coincidentes. La línea de comando se construye de la misma manera que xargs construye sus líneas de comando. Solo se permite una instancia de '{}' dentro del comando. El comando se ejecuta en el directorio de inicio.

Entonces, según tengo entendido, \;ejecuta un comando separado para cada archivo encontrado por find, mientras que \+agrega los archivos y ejecuta un solo comando en todos ellos. El \es un personaje de escape, entonces es:

ls testdir1; ls testdir2 

vs

ls testdir1 testdir2

Hacer lo anterior en mi shell reflejó el resultado en su pregunta.

ejemplo de cuándo querrías usar \+

Supongamos dos archivos 1.tmpy 2.tmp:

1.tmp:

1
2
3

2.tmp:

0
2
3

Con \;:

 find *.tmp -exec diff {} \;
> diff: missing operand after `1.tmp'
> diff: Try `diff --help' for more information.
> diff: missing operand after `2.tmp'
> diff: Try `diff --help' for more information.

Mientras que si usa \+(para concatenar los resultados de find):

find *.tmp -exec diff {} \+
1c1,3
< 1
---
> 0
> 2
> 30

Entonces, en este caso, es la diferencia entre diff 1.tmp; diff 2.tmpydiff 1.tmp 2.tmp

Hay casos donde \;es apropiado y \+será necesario. El uso \+con rmes uno de esos casos, donde si está eliminando una gran cantidad de archivos, el rendimiento (velocidad) será superior a \;.

Matchew
fuente
También puedo leer la página del manual. Y lo hice, pero no creo entender la diferencia entre usar; vs +
Ankur Agarwal
No creo que el -1 fuera justo, le expliqué mi comprensión del hombre. No solo copié al hombre y me fui. pero he editado mi respuesta para incluir un mejor ejemplo.
matchew
10

findtiene sintaxis especial Usas los {}tal como están porque tienen significado para buscar como el nombre de ruta del archivo encontrado y (la mayoría) los shells no los interpretan de otra manera. Necesita la barra diagonal inversa \;porque el punto y coma tiene significado para el shell, que lo consume antes de findpoder obtenerlo. Entonces, lo que findquiere ver DESPUÉS de que se haga el shell, en la lista de argumentos pasada al programa C, es

"-exec", "rm", "{}", ";"

pero necesita \;en la línea de comando para obtener un punto y coma a través de la shell a los argumentos.

Puede salirse con la suya \{\}porque la interpretación citada de shell \{\}es justa {}. Del mismo modo, puede usar '{}'.

Lo que no puedes hacer es usar

 -exec 'rm {} ;'

porque el intérprete interpreta eso como un argumento,

"-exec", "rm {};"

y rm {} ;no es el nombre de un comando. (Al menos a menos que alguien esté realmente jodiendo).

Actualizar

la diferencia es entre

$ ls file1
$ ls file2

y

$ ls file1 file2

El +está cateando los nombres en una línea de comando.

Charlie Martin
fuente
1
Entiendo lo que dices. Estoy preguntando cuál es la diferencia entre usar; vs +
Ankur Agarwal
1
lo siento, pero ¿leíste mi pregunta o mi comentario cuidadosamente? Puede ser que necesite reformularlo. ¿Por qué hay una o / p diferente cuando uso punto y coma con exec en find versus cuando uso plus con exec en find?
Ankur Agarwal
2
Esta es una excelente explicación de POR QUÉ el comando es así, que la respuesta aceptada no cubre. ¡Gracias!
Sherwin Yu
1

La diferencia entre ;(punto y coma) o +(signo más) es cómo se pasan los argumentos al parámetro -exec/ encontrar -execdir. Por ejemplo:

  • utilizando ;ejecutará múltiples comandos (por separado para cada argumento),

    Ejemplo:

    $ find /etc/rc* -exec echo Arg: {} ';'
    Arg: /etc/rc.common
    Arg: /etc/rc.common~previous
    Arg: /etc/rc.local
    Arg: /etc/rc.netboot
    

    Todos los siguientes argumentos findse consideran argumentos del comando.

    La cadena {}se reemplaza por el nombre del archivo actual que se está procesando.

  • El uso +ejecutará los comandos menos posibles (ya que los argumentos se combinan juntos). Es muy similar a cómo xargsfunciona el comando, por lo que utilizará tantos argumentos por comando como sea posible para evitar exceder el límite máximo de argumentos por línea.

    Ejemplo:

    $ find /etc/rc* -exec echo Arg: {} '+'
    Arg: /etc/rc.common /etc/rc.common~previous /etc/rc.local /etc/rc.netboot
    

    La línea de comando se construye agregando cada nombre de archivo seleccionado al final.

    Solo {}se permite una instancia de dentro del comando.

Ver también:

kenorb
fuente
-5

Estábamos tratando de encontrar el archivo para la limpieza.

encontrar . -exec echo {} \; El comando se ejecutó durante la noche al final sin resultado.

encontrar . -exec echo {} \ + tiene resultados y solo tomó unas pocas horas.

Espero que esto ayude.

Ron
fuente
2
Esta respuesta no explica cómo funcionan esas dos formas y cómo difieren los resultados producidos por ellas.
misko321