Executando o teste -nt / -ot em um POSIX sh

11

Os utilitários teste [internos têm os testes -nt("mais recentes que") e -ot("mais antigos") na maioria dos shells, mesmo quando o shell está sendo executado no "modo POSIX" (também válido para os utilitários externos com os mesmos nomes no sistemas aos quais tenho acesso). Esses testes são para comparar registros de data e hora da modificação em dois arquivos. Sua semântica documentada varia ligeiramente nas implementações (com relação ao que acontece se um ou outro arquivo existir ou não), mas eles não estão incluídos na especificação POSIX. para o testutilitário .

Eles não foram transportados para o testutilitário quando o comando condicional foi removido do shell [KornShell] porque não foram incluídos no testutilitário incorporado nas implementações históricas do shutilitário.

Supondo que eu gostaria de comparar o carimbo de data / hora da modificação entre os arquivos em um /bin/shscript de shell e, em seguida, executar uma ação, dependendo se um arquivo é mais recente que o outro, como em

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

... que outro utilitário eu poderia usar, além de make(o que tornaria o restante do script pesado para dizer o mínimo)? Ou devo apenas assumir que ninguém jamais executará o script em uma "implementação histórica de sh" ou renunciará a escrever para um shell específico como bash?

Kusalananda
fonte
Como você pode ver na minha resposta, o bash não implementa o -ntrecurso de testuma maneira que é esperada desde aprox. 1995. Você deve usar a findexpressão basd, mesmo que com bashum comportamento correto.
schily

Respostas:

10

POSIXAMENTE:

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

O uso do caminho absoluto dos arquivos para impedir que um falso positivo com o nome do arquivo contenha apenas novas linhas.

No caso de usar o caminho relativo, altere o findcomando para:

find -L "$f1" -prune -newer "$f2" -exec echo . \;
cuonglm
fonte
tecnicamente, eu acho que não se $f1contém apenas novas linhas;)
ilkkachu
1
@ilkkachu poderia ser contornado -exec echo x \;ou similar?
Muru
@muru, sim. -printfSeria fácil se fosse norma
ilkkachu
3
findO status de saída do @Kusalananda AFAICT é independente do que os testes individuais de findretorno - exceto talvez se houver algum erro na execução desses testes. findsai 0 mesmo que nenhum arquivo seja encontrado.
Muru
1
Observe que o comportamento de [ / -nt /nofile ]varia com a implementação (mas nunca gera nenhum erro).
Stéphane Chazelas
7

Este pode ser um caso para usar um dos comandos mais antigos do Unix ls,.

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

O resultado é verdadeiro se a for mais novo que b.

meuh
fonte
3
Isso não funciona se os nomes dos arquivos começarem com -(ausentes --). Você precisaria -Lque fosse equivalente a -nt. Isso não funciona para comparar xe, $'x\nx'por exemplo.
Stéphane Chazelas
1
Eek! Mas também hein.
Kusalananda
4

Você levantou uma pergunta interessante e fez uma reivindicação que deveria ser verificada primeiro.

Eu verifiquei o comportamento de:

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

com várias conchas. Aqui estão os resultados:

  • bash Não funciona - imprime nada.

  • bosh funciona

  • traço Não funciona - imprime nada.

  • ksh88 Não funciona - imprime nada.

  • ksh93 works

  • mksh Não funciona - imprime nada.

  • posh prints: posh: [: -nt: operador / operando inesperado

  • obras de yash

  • O zsh funciona em versões mais recentes , versões anteriores não imprimem nada

Portanto, quatro dos nove shells suportam o recurso -nt e o implementam corretamente. Corretamente, neste caso, significa: é capaz de comparar registros de data e hora em plataformas recentes que suportam granularidade de registro de hora em segundos . Observe que os arquivos que eu selecionei diferem normalmente apenas alguns microssegundos em seus carimbos de hora.

Como é mais fácil encontrar uma findimplementação de trabalho , recomendo substituir

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

por uma findexpressão baseada.

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

funciona pelo menos desde $file1que não contenha apenas novas linhas.

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

é um pouco mais lento, mas funciona corretamente.

BTW: Em relação ao make, não posso falar em todas as implementações do make, mas SunPro Makesuporta a comparação de tempo com granularidade em nanossegundos desde aprox. 20 anos, enquanto smakee gmakeadicionou este recurso recentemente.

esperto
fonte
1
Os testes "não estão funcionando" não estão funcionando devido a uma falha na comparação dos carimbos de hora em menos de um segundo (definitivamente?) Ou algo mais? Quais são os registros de data e hora reais nos arquivos envolvidos no seu teste? +1 para o teste!
Kusalananda
2
Eu acho que o voto negativo é provavelmente sobre a redação do confronto. Aqui, seria mais justo chamá-lo de limitação (significativa em alguns contextos). Algumas findimplementações como busybox ou heirloom-toolchest terão a mesma limitação.
Stéphane Chazelas
3
Você precisaria -Lque a findversão fosse equivalente a -nt. Seria também falhar em nomes de arquivos que começam com -ou !, (...
Stéphane Chazelas
2
Por uma questão de fato, geralmente recebo votos negativos quando uso um texto de confronto. Aqui, ajudaria se você declarasse o contexto (arquivos modificados no mesmo timestamp quando truncados para a segunda resolução) no início da resposta.
Stéphane Chazelas
1
@ Schily, sim, os BSDs findtêm find -f "$file"isso, mas não é portátil.
Stéphane Chazelas