encontrar | xargs shasum crea la suma de comprobación del archivo de suma de comprobación en sí (prematuramente) y falla al verificar

10

Mi problema (en una secuencia de comandos con #!/bin/sh) es el siguiente: Intento sumar todos los archivos en un directorio con fines de archivo. El archivo de suma de comprobación (en mi caso sha1) con todos los nombres de archivo debe residir en el mismo directorio. Digamos que tenemos un directorio ~/testcon archivos f1y f2:.

mkdir ~/test
cd ~/test
echo "hello" > f1
echo "world" > f2

Ahora calculando las sumas de verificación con

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum

hace exactamente lo que quiero, enumera todos los archivos del directorio actual y calcula las sumas sha1 (la profundidad máxima se puede cambiar más adelante). La salida en STDOUT es:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

Desafortunadamente, al intentar guardar esto en un archivo con

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum > sums.sha1

el archivo resultante muestra la suma de comprobación por sí mismo:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

y, por lo tanto, falla más tarde shasum --check, debido al obvio problema de la modificación de archivos adicionales al guardar la última suma.

Miré a mi alrededor y al usar -pflag for xargs, descubrí que de alguna manera crea el archivo de salida antes de incluso ejecutar el comando find, por lo tanto, se encuentra el archivo adicional y se sumará a la suma de comprobación ...

Sé que, como solución alternativa, podría guardar la suma de comprobación en otra ubicación (directorio temporal a través de mktemp) o excluirla en find específicamente, pero me gustaría entender por qué se comporta de la manera en que lo hace, lo que a mis ojos no es tan útil, por ejemplo, si el primer comando verificaría si el archivo de salida ya está en el disco, nunca obtendría la respuesta correcta ...

usuario121391
fuente
8
No es xargs, es el propio shell el que crea este archivo, porque antes de que se ejecute cualquier comando, el shell redirige todas las entradas, salidas y canalizaciones, de modo que cuando findcomienza el archivo de salida ya existe. Utilice en su -execlugar:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij
@jimmij, tampoco se garantiza que funcione si shson necesarias varias invocaciones. Tenga en cuenta que necesita un argumento para $0antes {}.
Stéphane Chazelas
@jimmij ¿Tu otra respuesta que sugirió teeha desaparecido? Lo probé y funciona bien, también suprimí STDOUT con la adición de 1>/dev/null. ¿Hubo algún problema con la respuesta o fue un error?
user121391
@ user121391 Stephane señaló que a veces puede haber un problema de condición de carrera, lo que parece cierto. Lo eliminé por un tiempo para que pueda mirar, pero si tiene muchos archivos en la lista, ese comando podría salir mal.
jimmij
@jimmij ah, ya veo. Puede ser útil si lo prefijas con una advertencia sobre los problemas, porque creo que no se sabe muy bien que esto puede suceder. De lo contrario, habría aceptado su respuesta para los casos si las ejecuciones recurrentes incluyen el archivo antiguo y Anthon's para los casos en los que debería sobrescribirse.
user121391

Respuestas:

12

Puede evitar que el archivo llegue xargsusando:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\n' |
  xargs -r shasum -- > sums.sha1

Sin embargo, para evitar problemas con el nombre de archivo que tiene espacios en blanco o líneas nuevas o comillas o barras invertidas, usaría:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\0' |
  xargs -r0 shasum -- > sums.sha1

en lugar.

El --objetivo es evitar problemas con los nombres de archivo que comienzan con -. Sin embargo, no ayudará para un archivo llamado -. Si lo hubiera usado en -print0lugar de -printf '%P\0', no habría necesitado --y no habría tenido un problema con el -archivo.

Anthon
fuente
Su solución es lo que terminé usando. Me gusta especialmente que las ejecuciones posteriores no vuelvan a mostrar el archivo de suma de comprobación e inflen el directorio. Además, en mi script solía basenameobtener el nombre de archivo sums.sha1 de la ruta completa dada (esto no se incluyó en la pregunta, pero podría ayudar a otros).
user121391
7

Como estás usando -maxdepth 1, supongo que no quieres recurrencia. Si es así, simplemente hazlo en el shell:

for f in ~/test/*; do
    shasum -- "$f"
done > sums.sha1

Para omitir directorios, puede hacer:

for f in ~/test/*; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Si necesita recurrencia y está usando bash, haga lo siguiente:

shopt -s globstar
for f in ~/test/**; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Tenga en cuenta que todos estos enfoques tienen la ventaja de trabajar en nombres de archivos arbitrarios, incluidos aquellos con espacios, líneas nuevas o cualquier otra cosa.

terdon
fuente
Creo que mencionaría que esto resuelve cualquier problema que el OP tenga con los nombres de archivo con nuevas líneas también. Por otro lado, si sums.sha1ya está allí (de una ejecución anterior) su solución lo incorporará.
Anthon
Lo siento, no lo aclaré antes: la profundidad máxima solo se usó en este ejemplo, uso una función en la que el usuario / script puede proporcionar cualquier valor, aunque actualmente solo necesito profundidad 1.
user121391
@ user121391 ver respuesta actualizada para un enfoque recursivo.
terdon
Tenga en cuenta que también intentará sumar otros tipos de archivos no regulares como tuberías, dispositivos ... (y enlaces simbólicos a ellos).
Stéphane Chazelas
Gracias, personalmente estoy usando sh, pero su respuesta podría ayudar a otros.
user121391
4

con zsh:

shasum -- *(D.) > sums.sha1

El globo se expandirá antes de que se realice la redirección, por lo sums.sha1que no se incluirá si no estaba allí en primer lugar.

Des incluir archivos de puntos (archivos ocultos) como lo findharía. .es seleccionar solo archivos regulares (como el suyo -type f).

Para excluir el de sums.sha1todos modos en caso de que estuviera allí en primer lugar:

setopt extendedglob # best in ~/.zshrc
shasum -- ^sums.sha1(D.) > sums.sha1

Tenga en cuenta que esos ejecutan un comando shasum, por lo que puede terminar viendo un error "Lista de Arg demasiado larga" si la lista es enorme. Para evitar eso:

autoload zargs
zargs -e/ -- *(D.) / shasum > sums.sha1

Recomendaría usar en ./*lugar de *evitar posibles problemas con un archivo llamado -.

Stéphane Chazelas
fuente
Edité la pregunta con tipo de shell, pero su respuesta me recuerda que quería cambiar a zsh hace algún tiempo ...;)
user121391
1

Como las otras respuestas ya indicaron, el problema es que el shell se abre y crea el sums.sha1archivo, antes de ejecutar su canalización. Puede usar el programa spongeque es parte del moreutilspaquete de muchas distribuciones. A diferencia de la redirección de shell sponge, esperará hasta que reciba todo, antes de abrir el archivo. Generalmente se usa cuando desea escribir un archivo que lee en la misma tubería.

En su caso se usa así:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A
TimWolla
fuente
0

Como alternativa a find / xargs, etc., es posible que desee sha1deep. Sin embargo, probablemente esté en un paquete diferente: en mi caja viene en el paquete md5deep.

Como otros han dicho, el shell crea sums.sha1 incluso antes de que se inicie find. Un truco con ! -name sums.sha1to findfuncionará, al igual que

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
Torinthiel
fuente