Навіщо перевіряти наявність файлу, перш ніж його знайти?


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.


3
Ви не повинні. Це раси. Спробуйте джерело, тоді виправте помилку, якщо така є
Mikel

2
@Mikel право! то чому я бачу це скрізь?
JBallin

3
@FaheemMitha, ну, якщо те, що ти робиш, взагалі чутливе до безпеки (тобто твоя програма працює від чужого імені), то вам справді потрібно дбати про умови перегонів ( TOCTOU ). Це, мабуть, не так, оскільки у вас виникають більші проблеми, якщо хтось може змінити файли в HOME.
ilkkachu

8
@FaheemMitha Ніколи не краще спочатку перевірити існування. Ситуація може змінюватися між тестом та використанням, приводячи як до помилкових позитивних, так і до помилкових негативів. І як ви вже згадували про ефективність, тестування перед використанням ендемічно вдвічі повільніше, ніж цього не робити і дозволяти системі робити це, що вона все одно робити. Не може бути.
користувач207421

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 для скриптів init, які ще не були перетворені 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емуляції; зауважте, що в оболонці Корна sourceнасправді є псевдонімом command .неспеціального варіанту.


Цікаво! Я не знав цього про POSIX sh. Але питання було про те .bash_profile. Я вважаю, що краще безпечно, ніж вибачте, але чи колись башти в режимі POSIX, коли він .bash_profileпрацює?
Мікель

(Я усвідомлюю, що ви можете трактувати це питання як більш широке застосування до всіх оболонок POSIX на основі читання посилання на джерело nvm у запитанні.)
Mikel

@Mikel, моя відповідь все ще стосується тих, хто bashне знаходиться в режимі POSIX. Ви хочете, [ -e /file ] && . /fileякщо ви не вважаєте це помилкою, коли файл не існує. Джерело спробуйте потім виправити помилку, якщо тут не може бути зроблено жодної .
Стефан Шазелас

1
@Mikel, це контрпродуктивно. 1) що не перешкоджає виходу при помилці з оболонками POSIX (або баш в режимі POSIX) 2), що дублює повідомлення про помилку (ваше в stdout), .вже повідомить про помилку (на stderr). І якщо намір полягає в тому, щоб не вважати його помилкою, коли файл не існує, це неправильно (і неможливо, зі статусу виходу сказати, чи .не вдалось, оскільки файл не існував чи не читався, або був не піддається аналізу або остання команда не вдалася), які я вказую на цю відповідь.
Стефан Шазелас

1
Що стосується умови перегонів - набагато менше проблем IMHO, коли вхід не вдасться один раз (хоча щось дивно відбувається в системі), ніж для того, щоб лагін послідовно вийшов з ладу (навіть якщо в налаштуваннях користувача є щось не зовсім правильне) -файли). Тож умова перегонів у цій перевірці все ще є поліпшенням, ніж відсутність чеку.
ruakh

5

Супровід nvmвідповіді:

видалити nvm легко, просто видаливши файл; примушування додаткової роботи (щоб знайти, де рядки є джерелом nvm) не здається особливо цінним.

Моя інтерпретація (поєднана з відмінним поясненням Стефана та коментарем Кусалананда):

Це простіше і безпечніше.

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


1
Я проти вашого тлумачення, що воно "спрямоване на новачків". Це оборонно. Ви не хочете, щоб оболонка входу користувача несподівано закінчувалася під час запуску лише тому, що цей файл зник (з будь-якої причини). Якщо цей рядок коду є в одному з файлів /etc, що знаходиться в командній оболонці під , тоді він дозволяє деяким користувачам мати файл, а іншим - файлу. ІМХО, відповідь nvmобслуговуючого персоналу стосується лише одного аспекту.
Kusalananda

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 .

Відповідь Stéphane містить кілька корисних пропозицій щодо того, як поводитися з усіма оболонками POSIX, що, на мою думку, було задумом автора nvm, але було дещо іншим, ніж те, що тут задавали питання, тому ми маємо безліч можливих підходів, залежно від того, яка ваша мета .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.