Зачем проверять наличие файла перед его поиском?

13

Когда вы пытаетесь найти файл, не хотите ли вы сказать, что файл не существует, и вы знаете, что исправить?

Например, nvm рекомендует добавить это в свой профиль / rc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

С вышеупомянутым, если nvm.shне существует, вы получите «тихую ошибку». Но если вы попробуете . "$NVM_DIR/nvm.sh", выход будет FILE_PATH: No such file or directory.

JBallin
источник
3
Ты не должен. Это круто. Попробуйте исходный код, затем обработайте ошибку, если таковая имеется
Микель
2
@ Микель, верно! тогда почему я вижу это везде?
JBallin
3
@FaheemMitha, хорошо, если то, что вы делаете, вообще чувствительно к безопасности (то есть ваша программа работает от чьего-либо имени), то вам действительно нужно заботиться об условиях гонки ( TOCTOU ). Однако, возможно, это не тот случай, поскольку у вас есть большие проблемы, если кто-то может изменить файлы в HOME.
ilkkachu
8
@FaheemMitha Никогда лучше не проверять существование в первую очередь. Ситуация может измениться между тестированием и использованием, приводя к ложным срабатываниям и ложным отрицаниям, и вам все равно придется обрабатывать сбой при использовании. И, как вы упомянули о производительности, тестирование перед использованием является в два раза медленнее, чем если вы не делаете этого и не позволяете системе делать то, что она собирается делать в любом случае. Этого не может быть.
user207421
1
@SimonRichter, в этом случае nvm не будет работать. Пользователь должен выяснить, что файл отсутствует самостоятельно.
JBallin

Ответы:

25

В оболочках POSIX .это специальная встроенная функция, поэтому ее сбой приводит к выходу оболочки (в некоторых оболочках, например bash, это происходит только в режиме POSIX).

То, что считается ошибкой, зависит от оболочки. Не все из них выходят из-за синтаксической ошибки при разборе файла, но большинство из них завершается, когда исходный файл не может быть найден или открыт. Я не знаю ничего, что могло бы завершиться, если последняя команда в исходном файле вернулась с ненулевым статусом выхода (если, errexitконечно, опция не включена).

Здесь делают:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Это тот случай, когда вы хотите получить исходный файл, если он там, и нет, если его нет (или здесь пусто -s).

То есть, это не должно считаться ошибкой (фатальная ошибка в оболочках POSIX), если файла нет, этот файл считается необязательным файлом.

Это все равно будет (фатальной) ошибкой, если файл не будет читаемым, или каталогом, или (в некоторых оболочках) при синтаксической ошибке при синтаксическом анализе, которая будет представлять собой реальные ошибки, о которых следует сообщать.

Некоторые утверждают, что есть состояние гонки. Но единственное, что это означает, это то, что оболочка завершит работу с ошибкой, если файл будет удален между [и ., но я бы сказал, что допустимо считать ошибкой, что этот файл с фиксированным путем внезапно исчезнет, ​​пока скрипт Бег.

С другой стороны,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

где command¹ удаляет специальный атрибут .команды (поэтому он не выходит из оболочки при ошибке) не будет работать так:

  • это скрыло бы .ошибки, но также и ошибки команд, выполняемых в исходном файле
  • это также скрыло бы реальные ошибки, такие как файл с неправильными разрешениями.

Другие распространенные синтаксисы (см., Например, grep -r /etc/default /etc/init*в системах Debian сценарии инициализации, в которые еще не были преобразованы systemd(где EnvironmentFile=-/etc/default/serviceвместо этого используется дополнительный файл среды)):

  • [ -e "$file" ] && . "$file"

    Проверьте файл, который там есть, все равно отправьте его, если он пуст. Все еще фатальная ошибка, если она не может быть открыта (даже если она там или была там). Вы можете увидеть больше вариантов, таких как [ -f "$file" ](существует и является обычным файлом), [ -r "$file" ](доступно для чтения) или их комбинаций.

  • [ ! -e "$file" ] || . "$file"

    Немного лучшая версия. Проясняет, что несуществующий файл является нормальным случаем. Это также означает, что $?будет отображать состояние выхода последней команды, запущенной в $file(в предыдущем случае, если вы получаете 1, вы не знаете, потому что $fileэто не было или эта команда не выполнена).

  • command . "$file"

    Ожидайте, что файл будет там, но не выходите, если он не может быть интерпретирован.

  • [ ! -e "$file" ] || command . "$file"

    Комбинация вышесказанного: все нормально, если файла нет, а для оболочек POSIX сообщается об ошибках открытия (или разбора) файла, но они не являются фатальными (что может быть более желательным для ~/.profile).


¹ Примечание: zshоднако, вы не можете использовать commandэто, если не в shэмуляции; обратите внимание, что в оболочке Korn, sourceна самом деле псевдоним для command ., не особый вариант.

Стефан Шазелас
источник
Интересный! Я не знал этого о POSIX sh. Но вопрос был о .bash_profile. Я думаю, что лучше, чем потом сожалеть, но есть ли когда-нибудь bash в режиме POSIX, когда .bash_profileон получен?
Микель
(Я понимаю, что вы могли бы интерпретировать этот вопрос как более широкое применение ко всем оболочкам POSIX, основываясь на прочтении ссылки на источник nvm в вопросе.)
Mikel
@Mikel, мой ответ остается в силе, bashкогда не в режиме POSIX. Вы захотите, [ -e /file ] && . /fileесли не считаете это ошибкой, когда файл не существует. Источник try затем обрабатывает ошибку, если она не может быть выполнена здесь.
Стефан Шазелас
1
@ Микель, это контрпродуктивно. 1) что не предотвращает выход при ошибке с оболочками POSIX (или bash в режиме POSIX) 2), который дублирует сообщение об ошибке (ваше на stdout), .уже сообщит об ошибке (на stderr). И если намерение состоит в том, чтобы не считать это ошибкой, когда файл не существует, это неверно (и это невозможно, из состояния выхода сказать, .что произошел сбой, потому что файл не существует или не был читаемым, или был не анализируется, или последняя команда не выполнена), что я и подчеркиваю в этом ответе.
Стефан Шазелас
1
Что касается состояния гонки - это гораздо меньше проблем ИМХО для входа на провал один раз ( в то время как что - то странное происходит в системе) , чем для входа в систему, чтобы потерпеть неудачу последовательно (даже если что-то не совсем верно в конфигурации пользователя -файлы). Таким образом, состояние гонки в этой проверке все еще улучшается по сравнению с отсутствием проверки.
Руах
5

Ответственный за nvmответ:

легко удалить nvm, просто удалив файл; форсирование дополнительной работы (чтобы отследить, где строки (строки) являются источником nvm) не кажется особенно ценным.

Моя интерпретация (в сочетании с превосходным объяснением Стефана и комментарием Кусалананды):

Это проще и безопаснее.

Он защищает от оболочек POSIX, выходящих при запуске из-за отсутствия файла (по разным причинам). Те, кто использует не POSIX (например, bash) оболочки, могут удалить условные, если они того пожелают.

JBallin
источник
1
Я против вашей интерпретации, что она «нацелена на начинающих». Это оборонительно. Вы не хотите, чтобы оболочка входа пользователя неожиданно завершала работу при запуске только потому, что этот файл пропал (по любой причине). Если эта строка кода находится в одном из файлов инициализации оболочки /etc, то это позволяет некоторым пользователям иметь файл, а другим - нет. ИМХО, nvmответ сопровождающего касается только одного аспекта.
Кусалананда
1

Как указали JBallin и Stéphane Chazelas , в оболочках POSIX поиск несуществующего файла может привести к сбою входа.

Но добавление теста, чтобы увидеть, существует ли файл, а затем попытка получить его, может вызвать состояние гонки. Если что-то изменится nvm.shмежду [ -s nvm.sh ]и . nvm.sh, это вызовет именно ту ошибку, которую они пытаются предотвратить, хотя и гораздо реже.

В общем, способ предотвратить состояние гонки - просто попробовать то, что вы хотите сделать, а затем обработать ошибку в случае сбоя, например

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Оказывается, это не работает в оболочках POSIX, потому что, как и выше, .сбой приведет к немедленному завершению работы оболочки до того, как может быть запущена любая обработка ошибок.

Мой ответ утверждает, что оболочки POSIX не имеют отношения к этому вопросу, потому что .bash_profileникогда не должны работать в режиме POSIX. Так что в любом случае мы можем просто выполнить приведенный выше код.

Чтобы быть максимально безопасным, мы могли бы убедиться, что режим POSIX не действует, или убедиться, что режим POSIX отключен, используя методику, описанную в /unix//a/383581/3169 .

Ответ Стефана содержит несколько полезных советов о том, как обращаться со всеми оболочками POSIX, что, как мне кажется, было намерением автора nvm, но несколько отличалось от того, о чем здесь задавался вопрос, поэтому у нас есть несколько возможных подходов, в зависимости от вашей цели. ,

Mikel
источник