Przeprowadzanie testu -nt / -ot w POSIX sh

11

Wbudowane testi [narzędzia mają testy -nt(„nowsze niż”) i -ot(„starsze niż”) w większości powłok, nawet gdy powłoka działa w „trybie POSIX” (dotyczy to również zewnętrznych narzędzi o tych samych nazwach w systemy, do których mam dostęp). Testy te służą do porównywania znaczników czasu modyfikacji dwóch plików. Ich udokumentowana semantyka różni się nieznacznie w zależności od implementacji (w odniesieniu do tego, co się stanie, jeśli jeden lub drugi plik nie istnieje), ale nie są one uwzględnione w specyfikacji POSIX. dla testnarzędzia .

Nie zostały przeniesione do testnarzędzia, gdy polecenie warunkowe zostało usunięte z powłoki [KornShell], ponieważ nie zostały uwzględnione w testnarzędziu wbudowanym w historyczne implementacje shnarzędzia.

Zakładając, że chciałbym porównać znacznik czasu modyfikacji między plikami w /bin/shskrypcie powłoki, a następnie podjąć działania w zależności od tego, czy jeden plik jest nowszy od drugiego, jak w

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

... jakiego innego narzędzia mógłbym użyć oprócz make(co sprawiłoby, że reszta skryptu byłaby co najmniej niewygodna)? A może powinienem po prostu założyć, że nikt nigdy nie uruchomi skryptu na „historycznej implementacji sh” lub nie zrezygnuje z pisania dla określonej powłoki bash?

Kusalananda
źródło
Jak widać w mojej odpowiedzi, bash nie implementuje -ntfunkcji testw sposób, który jest oczekiwany od ok. 1995. Powinieneś używać findwyrażenia basd, nawet bashjeśli lubisz prawidłowe zachowanie.
schily

Odpowiedzi:

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

Użycie bezwzględnej ścieżki do plików zapobiega fałszywie dodatniemu, gdy nazwa pliku zawiera tylko znaki nowej linii.

W przypadku użycia ścieżki względnej zmień findpolecenie na:

find -L "$f1" -prune -newer "$f2" -exec echo . \;
Cuonglm
źródło
technicznie myślę, że zawodzi, jeśli $f1zawiera tylko nowe wiersze;)
ilkkachu
1
@ilkkachu można obejść w -exec echo x \;podobny sposób?
muru
@muru, tak. -printfbyłoby łatwo, gdyby to był standard
ilkkachu
3
@Kusalananda AFAICT, findstatus wyjścia jest niezależny od tego, co poszczególne testy findpowrotu - z wyjątkiem być może błędu w uruchomieniu tych testów. findkończy 0, nawet jeśli nie znaleziono plików.
muru
1
Zauważ, że zachowanie [ / -nt /nofile ]różni się w zależności od implementacji (ale nigdy nie generuje żadnego błędu).
Stéphane Chazelas
7

Może to być przypadek stosując jedną z najstarszych polecenia Unix ls.

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

Wynik jest prawdziwy, jeśli a jest nowsze niż b.

meuh
źródło
3
To nie działa, jeśli nazwy plików zaczynają się od -(brak --). Trzeba by -Lto było równoważne -nt. Że nie działa, aby porównać xi $'x\nx'na przykład.
Stéphane Chazelas
1
Eek! Ale także huh.
Kusalananda
4

Zadałeś interesujące pytanie i zgłosiłeś roszczenie, które należy najpierw zweryfikować.

Sprawdziłem zachowanie:

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

z różnymi skorupkami. Oto wyniki:

  • bash Nie działa - nic nie drukuje.

  • bosh działa

  • myślnik nie działa - nic nie drukuje.

  • ksh88 Nie działa - nic nie drukuje.

  • działa ksh93

  • mksh Nie działa - nic nie drukuje.

  • posh print: posh: [: -nt: nieoczekiwany operator / operand

  • yash działa

  • zsh działa w nowszych wersjach , starsze wersje nic nie drukują

Tak więc cztery z dziewięciu powłok obsługują funkcję -nt i poprawnie ją implementują. Prawidłowo w tym przypadku oznacza: jest w stanie porównać znaczniki czasu na najnowszych platformach, które obsługują szczegółowość znaczników czasu w sekundach . Zauważ, że pliki, które wybrałem, różnią się zwykle tylko kilkoma mikrosekundami w swoich znacznikach czasu.

Ponieważ łatwiej jest znaleźć działającą findimplementację, zalecam wymianę

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

przez findwyrażenie oparte.

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

działa przynajmniej tak długo, jak długo $file1zawiera nie tylko nowe wiersze.

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

jest nieco wolniejszy, ale działa poprawnie.

BTW: Jeśli chodzi o markę, nie mogę mówić o wszystkich implementacjach make, ale SunPro Makeobsługuje porównanie czasu z ziarnistością nanosekundową od ok. 20 lat, podczas gdy smakei gmakedodaje tę funkcję niedawno.

schily
źródło
1
Czy testy „nie działają” nie działają z powodu braku porównania pod sekundowych znaczników czasu (zdecydowanie?) Czy coś innego? Jakie są rzeczywiste znaczniki czasowe plików zaangażowanych w test? +1 za testowanie!
Kusalananda
2
Myślę, że opinia negatywna prawdopodobnie dotyczy sformułowania konfrontacyjnego. W tym przypadku rozsądniej byłoby nazwać to ograniczeniem (znaczącym w niektórych kontekstach). Niektóre findimplementacje, takie jak busybox lub heirloom-toolchest, będą miały takie same ograniczenia.
Stéphane Chazelas
3
Należałoby -Ldla The findwersja za równoważne -nt. Byłoby to również nie na nazwy plików, które rozpoczynają się -lub !, (...
Stéphane Chazelas
2
W rzeczywistości często mam negatywne zdanie, gdy używam sformułowania konfrontacyjnego. Tutaj pomocne byłoby podanie kontekstu (pliki zmodyfikowane w tym samym znaczniku czasu po obcięciu do drugiej rozdzielczości) na początku odpowiedzi.
Stéphane Chazelas
1
@schily, tak, BSD findmają find -f "$file"to, ale nie jest przenośne.
Stéphane Chazelas