Compare duas datas de modificação de arquivo

5

Estou criando um sistema genérico de compilação / transpilação. Uma maneira de saber se um arquivo já foi compilado / transpilado seria comparar as datas de modificação do arquivo de origem e de destino.

Eu preciso escrever um script bash que possa fazer isso:

source_file=foo;
target_file=bar;

stat_source=$(stat source_file);
stat_target=$(stat target_file);

mas como posso extrair as datas da saída de estatísticas e compará-las? Existe uma maneira melhor do que stat para comparar o tempo de modificação mais recente de um arquivo?

Se eu chamar stat em um arquivo de log, recebo isso:

16777220 12391188 -rw-r--r-- 1 alexamil staff 0 321 "Jun 22 17:45:53 2017" "Jun 22 17:20:51 2017" "Jun 22 17:20:51 2017" "Jun 22 15:40:19 2017" 4096 8 0 test.log

AFAICT, a granularidade de tempo não é mais fina que os segundos. Eu preciso conseguir algo mais granular do que isso, se possível.

    
por Alexander Mills 23.06.2017 / 02:45

5 respostas

10

Considerando que você está usando o stat (funcionalidade semelhante, mas formato de saída diferente em BSDs e GNU), você também pode usar o utilitário test , que faz essa comparação diretamente:

   FILE1 -nt FILE2
          FILE1 is newer (modification date) than FILE2

   FILE1 -ot FILE2
          FILE1 is older than FILE2

No seu exemplo,

if [ "$source_file" -nt "$target_file" ]
then
    printf '%s\n' "$source_file is newer than $target_file"
fi

O recurso não está disponível no POSIX (consulte a documentação para test ), que fornece como uma razão:

Some additional primaries newly invented or from the KornShell appeared in an early proposal as part of the conditional command ([[]]): s1 > s2, s1 < s2, str = pattern, str != pattern, f1 -nt f2, f1 -ot f2, and f1 -ef f2. They were not carried forward into the test utility when the conditional command was removed from the shell because they have not been included in the test utility built into historical implementations of the sh utility.

Isso pode mudar no futuro, embora , como o recurso é amplamente suportado.

Note que quando os operandos são links simbólicos, é a hora da modificação do destino do symlink que é considerado (que é geralmente o que você quer, use find -newer se não). Quando links simbólicos não podem ser resolvidos, o comportamento entre implementações (alguns consideram um arquivo existente como sempre mais novo que um que não pode resolver, alguns sempre reportarão falso se algum dos operandos não puder ser resolvido) .

Observe também que nem todas as implementações suportam granularidade abaixo de segundo ( bash ' test / [ incorporado na versão 4.4 ainda não inclui, por exemplo, GNU test e test incorporado zsh ou ksh93 do, pelo menos no GNU / Linux).

Para referência:

  • Para a implementação do utilitário GNU test (mas observe que seu shell, se fish ou Bourne-like, também terá um test / [ integrado que normalmente faz sombras, use env test em vez de test para ignorá-lo), get_mtime em teste. c lê struct timespec e
  • a opção -nt usa esses dados
por 23.06.2017 / 02:50
1

Em testes neste sistema linux. A maneira usual de testar tempos de arquivo é o shell:

[ file1 -nt file2 ] && echo "yes"

Parece funcionar com segundos. Isso, que tocará os arquivos com uma diferença de tempo menor que um segundo, não detecta essa diferença:

$ touch file2; sleep 0.1; touch file1; [ file1 -nt file2 ] && echo "yes"

Para confirmar o problema (tempo após o ponto ser nanossegundos):

$ ls --time-style=full-iso -l file?
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.707387495 -0400 file1
-rw-r--r-- 1 user user 0 2017-06-23 01:37:01.599392538 -0400 file2

O file1 é (um pouco) mais recente que file2 .

O problema agora será processar corretamente o valor do tempo.

Uma solução é usar uma saída formatada de ls:

$ ls --time-style=+%s.%N -l file?
-rw-r--r-- 1 user user 0 1498196221.707387495 file1
-rw-r--r-- 1 user user 0 1498196221.599392538 file2

Extraindo o tempo para duas variáveis (sem o ponto):

$ file1time=$(ls --time-style=+%s%N -l file1 | awk "{print(\)}")
$ file2time=$(ls --time-style=+%s%N -l file2 | awk "{print(\)}")

E compare os tempos (tempos com nanossegundos mal cabem em um valor de 64 bits. Se o seu sistema não usa 64 bits, essa comparação falhará):

$ [ $file1time -gt $file2time ] && echo "yes"
yes

Isso mostra que file1 é mais recente que file2

Se ls não tiver o formato necessário, você poderá tentar stat.

$ stat file1
  File: file1
  Size: 0               Blocks: 0          IO Block: 4096   regular file
Device: 805h/2053d      Inode: 9180838     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/    user)   Gid: ( 1000/    user)
Access: 2017-06-23 01:37:01.707387495 -0400
Modify: 2017-06-23 01:37:01.707387495 -0400
Change: 2017-06-23 01:37:01.707387495 -0400
 Birth: -

Se a saída mostrar nanossegundos, precisaremos da data para analisar (e formatar) a hora.

$ stat --printf='%y\n' file1
2017-06-23 01:37:01.707387495 -0400

$ date +'%s%N' -d "$(stat --printf='%y\n' file1)" 
1498196221707387495

O restante é o mesmo, atribua os resultados de file1 e file2 a duas variáveis e os compare numericamente.

    
por 23.06.2017 / 09:45
1

Se você está disposto a assumir o Linux não embarcado, então você pode usar o comando test external, que faz parte do GNU coreutils. ( test é outro nome para [ e é embutido na maioria dos shells). Ele tem granularidade de nanossegundos (até a precisão relatada pelo sistema de arquivos).

/usr/bin/test "$target" -nt "$source"

O operador -nt não é definido pelo POSIX, mas está presente em muitas implementações, incluindo traço, bash, pdksh, mksh, ksh ATT, zsh, GNU coreutils test e BusyBox. No entanto, muitas implementações (dash, bash, pdksh, mksh, BusyBox - testadas no Debian jessie) suportam apenas uma granularidade de 1 segundo.

Mas seria melhor usar ferramentas dedicadas a esse trabalho, como make . Executar um comando somente se um determinado arquivo é mais novo que algum outro arquivo é o ponto principal da criação. Com o seguinte conteúdo em um arquivo chamado Makefile (observe que você precisa de um caractere de tabulação antes da linha de comando de cada um).

target: source
    echo This command is only executed if target is newer than source
    do_stuff <source >$@

Execute make target para executar os comandos que o geram. Se target existir e for mais recente que source , os comandos não serão executados. Leia alguma documentação de make para mais informações.

    
por 26.06.2017 / 01:14
0

POSIXly, você usaria find :

if find "$source_file" -prune -newer "$target_file" | grep -q '^'; then
  printf '%s\n' "$source_file is newer than $target_file"
else
  echo "It's not newer or one of the files is not accessible"
fi

Para links simbólicos, que compara o tempo dos próprios links simbólicos. Para comparar os destinos dos links simbólicos, adicione a opção -H ou -L .

Isso pressupõe que $source_file não comece com - e não corresponda a um dos predicados find . Se você precisar lidar com nomes de arquivos arbitrários, precisará fazer coisas assim primeiro:

case $source_file in
  (["-+()!"]*) source_file=./$source_file;;
esac
As implementações do

GNU e FreeBSD find , pelo menos, suportam granularidade abaixo de segundo. AFAICT, macos parece nem mesmo armazenar informações de tempo sub-segundo nos atributos de arquivo no sistema de arquivos HFS +, pelo menos.

    
por 26.06.2017 / 12:40
0

A data do GNU pode formatar o mtime de um arquivo para ser nanossegundo desde a época:

date +%s%N --reference file

Esses números são comparáveis com teste ou '['. Ele ainda funciona no bash v3.00 em um sistema i386 (32 bits) (por volta de 2008).

xxx=$(date +%s%N --reference file1)
yyy=$(date +%s%N --reference file2)
[ $xxx -lt $yyy ] && echo file1 is older than file2
    
por 21.07.2018 / 01:29