Пользователь вызывает мой сценарий с путем к файлу, который будет либо создан, либо перезаписан в какой-то момент сценария, например foo.sh file.txt
или foo.sh dir/file.txt
.
Поведение «создать или перезаписать» во многом похоже на требования для размещения файла справа от >
оператора перенаправления вывода или для передачи его в качестве аргумента tee
(фактически, передача его в качестве аргумента tee
является именно тем, что я делаю ).
Перед тем, как попасть в кишках сценария, я хочу , чтобы сделать разумный контроль , если файл может быть создан / перезаписаны, но на самом деле не создать. Эта проверка не обязательно должна быть идеальной, и да, я понимаю, что ситуация может измениться между проверкой и моментом, когда файл фактически записан, - но здесь у меня все в порядке с наилучшим решением для типов усилий, поэтому я могу выручить рано в случае, если путь к файлу неверен.
Примеры причин, по которым файл не может быть создан:
- файл содержит компонент каталога, например,
dir/file.txt
но каталогdir
не существует - пользователь не имеет разрешения на запись в указанный каталог (или CWD, если каталог не был указан
Да, я понимаю, что проверка разрешений "заранее" - это не UNIX Way ™ , скорее я должен просто попробовать выполнить операцию и попросить прощения позже. Однако в моем конкретном сценарии это приводит к плохому взаимодействию с пользователем, и я не могу изменить ответственный компонент.
источник
/foo/bar/file.txt
. В основном я прохожу путьtee
нравится ,tee $OUT_FILE
когдаOUT_FILE
передается в командной строке. Это должно "просто работать" как с абсолютными, так и с относительными путями, верно?tee -- "$OUT_FILE"
хотя бы. Что если файл уже существует или существует, но не является обычным файлом (каталог, символическая ссылка, fifo)?tee "${OUT_FILE}.tmp"
. Если файл уже существует,tee
перезаписывает, что является желаемым поведением в этом случае. Если это каталог, неtee
получится (я думаю). символическая ссылка Я не уверен на 100%?Ответы:
Очевидный тест будет:
Но он действительно создает файл, если его там еще нет. Мы могли бы убирать за собой:
Но это приведет к удалению уже существующего файла, который вам, вероятно, не нужен.
У нас, однако, есть способ обойти это:
Вы не можете иметь каталог с тем же именем, что и другой объект в этом каталоге. Я не могу вспомнить ситуацию, в которой вы могли бы создать каталог, но не создать файл. После этого теста ваш скрипт сможет свободно создавать обычные
/path/to/file
и делать с ними все, что угодно.источник
if mkdir /var/run/lockdir
в качестве теста блокировки. Это атомарно, хотяif ! [[ -f /var/run/lockfile ]]; then touch /var/run/lockfile
между тестом и тем,touch
где может начаться другой экземпляр рассматриваемого скрипта , есть небольшой промежуток времени .Из того, что я собираю, вы хотите проверить это при использовании
(обратите внимание, что
--
или это не будет работать для имен файлов, начинающихся с -),tee
удастся открыть файл для записи.Вот что:
На данный момент мы будем игнорировать файловые системы, такие как vfat, ntfs или hfsplus, которые имеют ограничения относительно того, какие байтовые значения могут содержать имена файлов, квоту диска, ограничение процесса, selinux, apparmor или другой механизм безопасности в пути, полную файловую систему, не осталось inode, устройство файлы, которые не могут быть открыты таким образом по той или иной причине, файлы, которые являются исполняемыми файлами, которые в настоящее время отображаются в некотором адресном пространстве процесса, что также может повлиять на возможность открытия или создания файла.
С
zsh
:В
bash
или любой подобной Борну оболочке, просто заменитес:
Здесь
zsh
-специальные функции$ERRNO
(которые предоставляют код ошибки последнего системного вызова) и$errnos[]
ассоциативный массив для перевода в соответствующие стандартные имена макросов C. И$var:h
(из csh) и$var:P
(требуется zsh 5.3 или выше).У bash еще нет аналогичных функций.
$file:h
может быть замененdir=$(dirname -- "$file"; echo .); dir=${dir%??}
, или с GNUdirname
:IFS= read -rd '' dir < <(dirname -z -- "$file")
.Например
$errnos[ERRNO] == ENOENT
, подход может заключаться в том, чтобы запуститьls -Ld
файл и проверить, соответствует ли сообщение об ошибке ошибке ENOENT. Делать это надежно и мобильно сложно.Одним из подходов может быть:
(при условии , что сообщение об ошибке заканчивается с
syserror()
переводом ,ENOENT
и что этот перевод не включать:
) , а затем, вместо того[ -e "$file" ]
, сделайте следующее :И проверьте наличие ENOENT с ошибкой
Это
$file:P
самая сложная задачаbash
, особенно во FreeBSD.У FreeBSD есть
realpath
команда иreadlink
команда, которая принимает-f
опцию, но их нельзя использовать в тех случаях, когда файл является символической ссылкой, которая не разрешается. То же самое и сperl
хCwd::realpath()
.python
«ыos.path.realpath()
действительно кажется, работают аналогичноzsh
$file:P
, поэтому при условии , что по крайней мере одна версияpython
установлена , и что естьpython
команда , которая относится к одному из них, вы могли бы сделать (что не дано на FreeBSD есть):Но тогда вы могли бы сделать все это в
python
.Или вы могли бы решить не обрабатывать все эти угловые случаи.
источник
tee
таким требованием, поэтому действительно нужно, чтобы файл был создан, если он не существует, или мог быть обрезан до нуля и перезаписан если он это делает (или, тем не менее,tee
обрабатывает это).perl
,awk
,sed
илиpython
(или дажеsh
,bash
...). Сценарии оболочки - это использование лучшей команды для этой задачи. Здесь дажеperl
нет эквивалентаzsh
's$var:P
(онCwd::realpath
ведет себя как BSDrealpath
или вreadlink -f
то время как вы хотите, чтобы он вел себя как GNUreadlink -f
илиpython
sos.path.realpath()
)Один из вариантов, который вы могли бы рассмотреть, - это создание файла на ранней стадии, но только позднее его заполнение в вашем скрипте. Вы можете использовать
exec
команду, чтобы открыть файл в дескрипторе файла (например, 3, 4 и т. Д.), А затем использовать перенаправление на дескриптор файла (>&3
и т. Д.) Для записи содержимого в этот файл.Что-то вроде:
Вы также можете использовать
trap
для очистки файла при ошибке, это обычная практика.Таким образом, вы получаете реальную проверку разрешений, что вы сможете создать файл, и в то же время вы можете выполнить его достаточно рано, чтобы в случае неудачи вы не тратили время на ожидание длинных проверок.
ОБНОВЛЕНО: во избежание путаницы файла в случае неудачной проверки используйте
fd<>file
перенаправление bash, которое не усекает файл сразу. (Мы не заботимся о чтении из файла, это всего лишь обходной путь, поэтому мы не усекаем его. Добавление с>>
, вероятно, тоже сработает, но я склонен находить это более элегантным, оставляя флаг O_APPEND вне картинки.)Когда мы будем готовы заменить содержимое, нам нужно сначала обрезать файл (в противном случае, если мы пишем меньше байтов, чем было в файле ранее, конечные байты остались бы там.) Мы можем использовать усечение (1) Для этого
/dev/fd/3
нужно выполнить команду из coreutils, и мы можем передать ей дескриптор открытого файла, который у нас есть (с использованием псевдофайла), поэтому нет необходимости повторно открывать файл. (Опять же, технически что-то более простое, как: >dir/file.txt
хотелось бы, возможно, сработает, но не нужно повторно открывать файл, это более элегантное решение.)источник
echo -n >>file
илиtrue >> file
. Конечно, у ext4 есть файлы только для добавления, но вы можете жить с этим ложным срабатыванием.my-file.txt.backup
перед созданием нового). Другое решение состоит в том, чтобы записать новый файл во временный файл в той же папке, а затем скопировать его поверх старого файла после успешного завершения остальной части сценария - если эта последняя операция завершилась неудачно, пользователь может вручную исправить проблему, не теряя его или ее прогресс.Я думаю, что решение DopeGhoti лучше, но это также должно работать:
Первая конструкция if проверяет, является ли аргумент полным путем (начинается с
/
), и задает дляdir
переменной путь к каталогу до последнего/
. В противном случае, если аргумент не начинается с,/
но содержит/
(указав подкаталог), он будет установленdir
на текущий рабочий каталог + путь подкаталога. В противном случае предполагается наличие текущего рабочего каталога. Затем он проверяет, доступен ли этот каталог для записи.источник
Как насчет использования обычной
test
команды, как описано ниже?источник
man test
, и это[ ]
эквивалентно тесту (больше не общеизвестно). Вот строчка, которую я собирался использовать в своем неполноценном ответе, чтобы покрыть точный случай для потомков:if [ -w $1] && [ ! -d $1 ] ; then echo "do stuff"; fi
Вы упомянули, что пользовательский опыт управлял вашим вопросом. Я отвечу с точки зрения UX, так как у вас есть хорошие ответы по технической части.
Вместо того, чтобы выполнять предварительную проверку, как насчет записи результатов во временный файл, а затем в самом конце, поместив результаты в нужный файл пользователя? Подобно:
Преимущество такого подхода: он производит желаемую операцию в нормальном сценарии «счастливого пути», обходит проблему атомарности test-and-set, сохраняет разрешения целевого файла при создании, если это необходимо, и очень прост в реализации.
Примечание: если бы мы использовали
mv
, мы бы сохранили права доступа к временному файлу - я думаю, мы этого не хотим: мы хотим сохранить права доступа, установленные для целевого файла.Теперь, плохо: требуется вдвое больше места (
cat .. >
конструкции), вынуждает пользователя выполнять некоторую ручную работу, если целевой файл не был доступен для записи в то время, когда это было необходимо, и оставляет временный файл лежащим вокруг (что может иметь безопасность или вопросы обслуживания).источник
TL; DR:
В отличие от
<> "${userfile}"
илиtouch "${userfile}"
, это не приведет к ложным изменениям временных меток файла, а также будет работать с файлами только для записи.Из ОП:
И из вашего комментария к моему ответу с точки зрения UX :
Единственный надежный тест -
open(2)
это файл, потому что только он решает все вопросы о возможности записи: путь, владение, файловая система, сеть, контекст безопасности и т. Д. Любой другой тест затрагивает некоторую часть возможности записи, но не другие. Если вы хотите подмножество тестов, вам в конечном итоге придется выбирать, что для вас важно.Но вот еще одна мысль. Из того, что я понимаю:
Вы хотите сделать эту предварительную проверку из-за # 1, и вы не хотите возиться с существующим файлом из-за # 2. Так почему бы вам просто не попросить оболочку открыть файл для добавления, но на самом деле ничего не добавить?
Под капотом это решает вопрос о возможности записи именно так, как хотелось:
но без изменения содержимого файла или метаданных. Теперь да, такой подход:
lsattr
и реагировать соответственно.rm
.Хотя я утверждаю (в моем другом ответе), что наиболее удобный для пользователя подход - это создать временный файл, который пользователь должен переместить, я думаю, что это наименее враждебный для пользователя подход, чтобы полностью проверить его ввод.
источник