Esecuzione del test -nt / -ot in un sh POSIX


11

Il built-in teste le [utility hanno i test -nt("più recenti di") e -ot("più vecchi di") nella maggior parte delle shell, anche quando la shell è in esecuzione in "modalità POSIX" (vero anche per le utility esterne con gli stessi nomi sul sistemi a cui ho accesso). Questi test servono per confrontare i timestamp di modifica su due file. La loro semantica documentata varia leggermente tra le implementazioni (per quanto riguarda cosa succede se l'uno o l'altro file esiste su non), ma non sono inclusi nelle specifiche POSIX. per l' testutilità .

Non sono stati portati avanti testnell'utilità quando il comando condizionale è stato rimosso dalla shell [KornShell] perché non sono stati inclusi nell'utilità testintegrata nelle implementazioni storiche shdell'utilità.

Supponendo che vorrei confrontare il timestamp di modifica tra i file in uno /bin/shscript di shell e quindi intervenire a seconda che un file sia più recente dell'altro, come in

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

... quale altra utility potrei usare, a parte make(che renderebbe il resto dello script ingombrante per non dire altro)? O dovrei solo supporre che nessuno eseguirà mai la sceneggiatura su una "implementazione storica di sh", o dimettersi dalla scrittura per una shell specifica come bash?


Come puoi vedere nella mia risposta, bash non implementa la -ntfunzione di testcome ci si aspetta da ca. 1995. Dovresti usare l' findespressione basd anche bashse ti piace il comportamento corretto.
schily

Risposte:


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

L'uso del percorso assoluto ai file impedisce un falso positivo con il nome file contiene solo nuove righe.

In caso di utilizzo del percorso relativo, quindi modificare il findcomando in:

find -L "$f1" -prune -newer "$f2" -exec echo . \;

tecnicamente, penso che fallisca se $f1contiene solo nuove righe;)
ilkkachu il

1
@ilkkachu potrebbe essere aggirato -exec echo x \;o simile?
Muru,

@muru, sì. -printfsarebbe facile se fosse standard
ilkkachu il

3
@Kusalananda AFAICT, lo findstato di uscita è indipendente da ciò che i singoli test di findritorno - tranne forse se c'è un errore nell'esecuzione di quei test. findesce 0 anche se non viene trovato alcun file.
Muru,

1
Si noti che il comportamento di [ / -nt /nofile ]varia con l'implementazione (ma non genera mai alcun errore).
Stéphane Chazelas,

7

Questo potrebbe essere un caso per l'utilizzo di uno dei più antichi del comando Unix, ls.

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

Il risultato è vero se a è più recente di b.


3
Ciò non funziona se i nomi dei file iniziano con -(mancante --). Avresti bisogno -Lche sia equivalente a -nt. Questo non funziona per confrontare xe $'x\nx'per esempio.
Stéphane Chazelas,

1
Eek! Ma anche eh.
Kusalananda

4

Hai sollevato una domanda interessante e avanzato un reclamo che dovrebbe essere prima verificato.

Ho verificato il comportamento di:

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

con varie conchiglie. Ecco i risultati:

  • bash Non funziona: non stampa nulla.

  • bosh funziona

  • trattino non funziona: non stampa nulla.

  • ksh88 non funziona - non stampa nulla.

  • ksh93 funziona

  • mksh non funziona: non stampa nulla.

  • posh stampe: posh: [: -nt: operatore / operando imprevisto

  • lo yash funziona

  • zsh funziona nelle versioni più recenti , le versioni precedenti non stampano nulla

Quindi quattro su nove shell supportano la funzione -nt e la implementano correttamente. Correttamente in questo caso significa: è in grado di confrontare i timestamp su piattaforme recenti che supportano la granularità di timestamp sub-secondo . Si noti che i file che ho selezionato differiscono in genere solo pochi microsecondi nei loro timestamp.

Poiché è più facile trovare findun'implementazione funzionante , raccomando di sostituirla

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

da findun'espressione basata.

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

funziona almeno fino a quando $file1non contiene solo nuove righe.

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

è un po 'più lento ma funziona correttamente.

A proposito: per quanto riguarda make non posso parlare per tutte le implementazioni make, ma SunPro Makesupporta il confronto temporale con granularità di nanosecondi da circa. 20 anni, mentre smakee gmakeha aggiunto questa caratteristica di recente.


1
I test "non funzionanti" non funzionano a causa di un errore nel confrontare i timestamp sub-second (sicuramente?) O qualcos'altro? Quali sono i timestamp effettivi sui file coinvolti nel test? +1 per il test!
Kusalananda

2
Penso che il downvote riguardi probabilmente la formulazione conflittuale. Qui, sarebbe più giusto chiamarlo un limite (significativo in alcuni contesti). Alcune findimplementazioni come busybox o heirloom-toolchest avranno la stessa limitazione.
Stéphane Chazelas,

3
Avresti bisogno -Lche la findversione sia equivalente a -nt. Avrebbe anche esito negativo sui nomi dei file che iniziano con -o !, (...
Stéphane Chazelas,

2
È un dato di fatto che ricevo spesso voti negativi quando uso una formulazione conflittuale. Qui sarebbe d'aiuto se hai indicato il contesto (file modificati nello stesso timestamp quando troncati alla seconda risoluzione) all'inizio della risposta.
Stéphane Chazelas,

1
@schily, sì, i BSD findce l'hanno find -f "$file"ma non è portatile.
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.