Realizar -nt / -ot prueba en un POSIX sh

11

El incorporado testy las [utilidades tienen las pruebas -nt("más nuevo que") y -ot("más antiguo que") en la mayoría de los shells, incluso cuando el shell se ejecuta en "modo POSIX" (también es cierto para las utilidades externas de los mismos nombres en el sistemas a los que tengo acceso). Estas pruebas son para comparar marcas de tiempo de modificación en dos archivos. Su semántica documentada varía ligeramente entre las implementaciones (con respecto a lo que sucede si uno u otro archivo existe o no), pero no están incluidos en la especificación POSIX. para la testutilidad .

No se testtransfirieron a la utilidad cuando el comando condicional se eliminó del shell [KornShell] porque no se han incluido en la testutilidad integrada en implementaciones históricas de la shutilidad.

Suponiendo que me gustaría comparar la marca de tiempo de modificación entre archivos en un /bin/shscript de shell y luego tomar medidas dependiendo de si un archivo es más nuevo que el otro, como en

if [ "$sigfile"     -nt "$timestamp" ] ||
   [ "$sigfile.tmp" -nt "$timestamp" ]
then
    return
fi

... ¿qué otra utilidad podría usar, aparte de make(lo que haría que el resto del script sea difícil de manejar por decir lo menos)? ¿O debería suponer que nadie ejecutará el script en una "implementación histórica de sh", o renunciar a escribir para un shell específico como bash?

Kusalananda
fuente
Como puede ver en mi respuesta, bash no implementa la -ntfunción de la testforma esperada ya que aprox. 1995. Debe usar la findexpresión basd incluso con bashsi le gusta el comportamiento correcto.
schily

Respuestas:

10

POSIXLY:

f1=/path/to/file_1
f2=/path/to/file_2

if [ -n "$(find -L "$f1" -prune -newer "$f2")" ]; then
    printf '%s is newer than %s\n' "$f1" "$f2"
fi

El uso de la ruta absoluta a los archivos evita que un falso positivo con nombre de archivo contenga solo nuevas líneas.

En caso de utilizar la ruta relativa, cambie el findcomando a:

find -L "$f1" -prune -newer "$f2" -exec echo . \;
Cuonglm
fuente
técnicamente, creo que falla si $f1solo contiene nuevas líneas;)
ilkkachu
1
@ilkkachu podría ser trabajado con -exec echo x \;o similar?
muru
@muru, sí. -printfsería fácil si fuera estándar
ilkkachu
3
findEl estado de salida de @Kusalananda AFAICT es independiente de las pruebas individuales de findretorno, excepto quizás si hay un error al ejecutar esas pruebas. findsale de 0 incluso si no se encuentran archivos.
muru
1
Tenga en cuenta que el comportamiento de [ / -nt /nofile ]varía con la implementación (pero nunca genera ningún error).
Stéphane Chazelas
7

Esto podría ser un caso para el uso de uno de los comandos más antigua Unix, ls.

x=$(ls -tdL -- "$a" "$b")
[ "$x" = "$a
$b" ]

El resultado es verdadero si a es más nuevo que b.

meuh
fuente
3
Eso no funciona si los nombres de archivo comienzan con -(falta --). Necesitarías -Lque sea equivalente a -nt. Eso no funciona para comparar xy, $'x\nx'por ejemplo.
Stéphane Chazelas
1
Eek! Pero también eh.
Kusalananda
4

Planteó una pregunta interesante e hizo un reclamo que primero debe verificarse.

Verifiqué el comportamiento de:

$shell -c '[ Makefile -nt SCCS/s.Makefile ] && echo newer'

Con varias conchas. Aquí están los resultados:

  • bash no funciona: no imprime nada.

  • Bosh trabaja

  • el guión no funciona: no imprime nada.

  • ksh88 no funciona, no imprime nada.

  • ksh93 funciona

  • mksh no funciona, no imprime nada.

  • impresiones elegantes : elegante: [: -nt: operador / operando inesperado

  • yash funciona

  • zsh funciona en versiones más nuevas , las versiones anteriores no imprimen nada

Entonces, cuatro de nueve shells admiten la característica -nt y la implementan correctamente. Correctamente en este caso significa: es capaz de comparar marcas de tiempo en plataformas recientes que admiten granularidad de marcas de tiempo por debajo del segundo . Tenga en cuenta que los archivos que seleccioné difieren típicamente solo unos pocos microsegundos en sus marcas de tiempo.

Como es más fácil encontrar una findimplementación que funcione , recomiendo reemplazar

if [ "$file1" -nt "$file2" ] ; then
    echo newer
fi

por una findexpresión basada.

if [ "$( find "$file1" -newer "$file2" )" ]; then
    echo newer
fi

funciona al menos mientras $file1no solo contenga nuevas líneas.

if [ "$( find -L "$file1" -newer "$file2" -exec echo newer \; )" ]; then
    echo newer
fi

es un poco más lento pero funciona correctamente.

Por cierto: con respecto a make, no puedo hablar de todas las implementaciones de make, pero SunPro Makeadmite la comparación de tiempo con la granularidad de nanosegundos desde aprox. 20 años, mientras que smakey gmakeagregó esta característica recientemente.

astuto
fuente
1
¿Las pruebas de "no funciona" no funcionan debido a una falla en la comparación de marcas de tiempo de menos de un segundo (¿definitivamente?) O algo más? ¿Cuáles son las marcas de tiempo reales en los archivos involucrados en su prueba? +1 para la prueba!
Kusalananda
2
Creo que el voto negativo probablemente sea sobre la redacción de confrontación. Aquí, sería más justo llamarlo una limitación (significativo en algunos contextos). Algunas findimplementaciones como busybox o heirloom-toolchest tendrán la misma limitación.
Stéphane Chazelas
3
Necesitaría que -Lla findversión sea equivalente a -nt. También fallaría en los nombres de archivo que comienzan con -o !, (...
Stéphane Chazelas
2
De hecho, a menudo recibo votos negativos cuando uso una redacción de confrontación. Aquí sería útil si estableciera el contexto (archivos modificados dentro de la misma marca de tiempo cuando se trunca a la segunda resolución) al comienzo de la respuesta.
Stéphane Chazelas
1
@schily, sí, los BSD findtienen find -f "$file"para eso, pero no es portátil.
Stéphane Chazelas