Выполнение -nt / -ot теста в POSIX ш

11

Встроенные утилиты testи [утилиты имеют тесты -nt(«новее чем») и -ot(«старше чем») в большинстве оболочек, даже когда оболочка работает в «режиме POSIX» (также верно для внешних утилит с такими же именами на системы, к которым у меня есть доступ). Эти тесты предназначены для сравнения меток времени изменения двух файлов. Их документированная семантика немного различается в разных реализациях (в отношении того, что происходит, если тот или иной файл существует в не), но они не включены в спецификацию POSIX. за testполезность .

Они не были перенесены в testутилиту, когда условная команда была удалена из оболочки [KornShell], потому что они не были включены в testутилиту, встроенную в исторические реализации shутилиты.

Предполагая, что я хотел бы сравнить метку времени изменения между файлами в /bin/shсценарии оболочки, а затем принять меры в зависимости от того, является ли один файл более новым, чем другой, как в

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

... какую другую утилиту я мог бы использовать, кроме make(что сделало бы остальную часть сценария громоздкой, если не сказать больше)? Или я должен просто предположить, что никто никогда не будет запускать сценарий для «исторической реализации sh», или смириться с написанием для конкретной оболочки вроде bash?

Кусалананда
источник
Как вы можете видеть в моем ответе, bash не реализует -ntфункцию, testкоторая ожидается с ок. 1995. Вы должны использовать findвыражение basd, даже bashесли вам нравится правильное поведение.
Шили

Ответы:

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

Использование абсолютного пути к файлам предотвращает ложное срабатывание, так как имя файла содержит только новые строки.

В случае использования относительного пути, измените findкоманду на:

find -L "$f1" -prune -newer "$f2" -exec echo . \;
cuonglm
источник
технически, я думаю, что он потерпит неудачу, если будет $f1содержать только
новые
1
@ilkkachu можно обойти -exec echo x \;или с аналогичным?
Муру
@ Муру, да. -printfбыло бы легко, если бы это было стандартно
ilkkachu
3
@Kusalananda AFAICT, findстатус выхода не зависит от того, какие отдельные тесты findвозвращаются, за исключением, возможно, ошибки в самом деле при запуске этих тестов. findвыходит 0, даже если файлы не найдены.
Муру
1
Обратите внимание, что поведение [ / -nt /nofile ]варьируется в зависимости от реализации (но никогда не выдает никакой ошибки).
Стефан Шазелас
7

Это может быть случай использования одной из самых старых команд Unix ls.

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

Результат верен, если a новее, чем b.

meuh
источник
3
Это не работает, если имена файлов начинаются с -(отсутствует --). Вам нужно, -Lчтобы это было эквивалентно -nt. Это не работает, чтобы сравнить xи, $'x\nx'например.
Стефан Шазелас
1
Ик! Но и да.
Кусалананда
4

Вы задали интересный вопрос и сделали заявление, которое сначала нужно проверить.

Я проверил поведение:

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

с различными снарядами. Вот результаты:

  • bash не работает - ничего не печатает.

  • чушь работает

  • Черта не работает - ничего не печатает.

  • ksh88 не работает - ничего не печатает.

  • кш93 работает

  • mksh не работает - ничего не печатает.

  • шикарная печать: шикарная: [: -nt: неожиданный оператор / операнд

  • Яш работает

  • zsh работает в новых версиях , старые версии ничего не печатают

Таким образом, четыре из девяти оболочек поддерживают функцию -nt и правильно ее реализуют. Правильно в этом случае означает: способен сравнивать метки времени на последних платформах, которые поддерживают детализацию меток времени в течение секунды . Обратите внимание, что выбранные мной файлы отличаются только несколькими микросекундами в своих отметках времени.

Поскольку легче найти работающую findреализацию, я рекомендую заменить

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

на findоснове выражения.

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

работает по крайней мере до тех пор, пока $file1не содержит только новые строки.

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

немного медленнее, но работает правильно.

Кстати, что касается make, я не могу говорить за все реализации make, но SunPro Makeподдерживает сравнение времени с наносекундной гранулярностью, начиная с прибл. 20 лет, пока smakeи gmakeдобавил эту функцию недавно.

Шили
источник
1
«Не работающие» тесты не работают из-за невозможности сравнить метки времени в секунду (определенно?) Или что-то еще? Каковы фактические временные метки файлов, участвующих в вашем тесте? +1 за тестирование!
Кусалананда
2
Я думаю, что отрицательный голос, вероятно, о конфронтационной формулировке. Здесь было бы справедливее назвать это ограничением (значительным в некоторых контекстах). Некоторые findреализации, такие как busybox или heirloom-toolchest, будут иметь такое же ограничение.
Стефан Шазелас
3
Вам нужно, -Lчтобы findверсия была эквивалентна -nt. Кроме того, произойдет сбой в именах файлов, начинающихся с -или !, (...
Стефан Шазелас
2
На самом деле, я часто получаю отрицательные отзывы, когда использую конфронтационную формулировку. Здесь было бы полезно, если бы вы указали контекст (файлы, измененные в пределах одной и той же временной метки при усечении до второго разрешения) в начале ответа.
Стефан Шазелас
1
@schily, да, BSD findесть find -f "$file"для этого, но это не портативно.
Стефан Шазелас