Чтобы избежать условий гонки:
name=some-file
n=
set -o noclobber
until
file=$name${n:+-$n}.ext
{ command exec 3> "$file"; } 2> /dev/null
do
((n++))
done
printf 'File is "%s"\n' "$file"
echo some text in it >&3
И вдобавок у вас файл открыт для записи на fd 3.
С помощью bash-4.4+
вы можете сделать это функцией, например:
create() { # fd base [suffix [max]]]
local fd="$1" base="$2" suffix="${3-}" max="${4-}"
local n= file
local - # ash-style local scoping of options in 4.4+
set -o noclobber
REPLY=
until
file=$base${n:+-$n}$suffix
eval 'command exec '"$fd"'> "$file"' 2> /dev/null
do
((n++))
((max > 0 && n > max)) && return 1
done
REPLY=$file
}
Для использования, например, как:
create 3 somefile .ext || exit
printf 'File: "%s"\n' "$REPLY"
echo something >&3
exec 3>&- # close the file
Значение max
можно использовать для защиты от бесконечных циклов, когда файлы не могут быть созданы по другой причине, кроме noclobber
.
Обратите внимание, что noclobber
применяется только к оператору >
, а не к >>
или <>
.
Оставшееся состояние гонки
На самом деле noclobber
не во всех случаях устраняет состояние гонки. Он только предотвращает затирание обычных файлов (но не других типов файлов, так что, например, cmd > /dev/null
не дает сбоев) и сам имеет состояние гонки в большинстве оболочек.
Оболочка сначала делает stat(2)
в файле, чтобы проверить, является ли он обычным файлом или нет (fifo, каталог, устройство...). Только если файл не существует (пока) или является обычным файлом, 3> "$file"
использует флаг O_EXCL, чтобы гарантировать отсутствие затирания файла.
Таким образом, если есть fifo или файл устройства с таким именем, он будет использоваться (при условии, что он может быть открыт только для записи), и обычный файл может быть стерт, если он будет создан в качестве замены для fifo/device/directory. .. между этими stat(2)
и open(2)
без O_EXCL!
Изменение
{ command exec 3> "$file"; } 2> /dev/null
to
[ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null
Избегает использования уже существующего нестандартного файла, но не устраняет состояние гонки.
Теперь это действительно проблема только перед лицом злонамеренного противника, который захочет заставить вас перезаписать произвольный файл в файловой системе. Это устраняет состояние гонки в обычном случае, когда два экземпляра одного и того же скрипта выполняются одновременно. Таким образом, это лучше, чем подходы, которые предварительно проверяют наличие файла только с помощью [ -e "$file" ]
.
Для рабочей версии вообще без состояния гонки вы можете использовать оболочку zsh
вместо bash
, которая имеет необработанный интерфейс к open()
как встроенная sysopen
в модуле zsh/system
:
zmodload zsh/system
name=some-file
n=
until
file=$name${n:+-$n}.ext
sysopen -w -o excl -u 3 -- "$file" 2> /dev/null
do
((n++))
done
printf 'File is "%s"\n' "$file"
echo some text in it >&3
person
Stephane Chazelas
schedule
30.08.2012