競合状態を回避するには :
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
へ
[ ! -e "$file" ] && { command exec 3> "$file"; } 2> /dev/null
既存の非通常ファイルの使用は避けますが、競合状態には対処しません。
これは、ファイル システム上の任意のファイルを上書きさせようとする悪意のある攻撃者に直面した場合にのみ、実際に懸念されることです。同じスクリプトの 2 つのインスタンスが同時に実行される通常のケースでは、競合状態が解消されます。そのため、 [ -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
簡単:
touch file`ls file* | wc -l`.ext
以下が得られます:
$ ls file*
file0.ext file1.ext file2.ext file3.ext file4.ext file5.ext file6.ext
次のスクリプトが役に立ちます。競合状態を避けるために、スクリプトの複数のコピーを同時に実行しないでください。
name=somefile
if [[ -e $name.ext || -L $name.ext ]] ; then
i=0
while [[ -e $name-$i.ext || -L $name-$i.ext ]] ; do
let i++
done
name=$name-$i
fi
touch -- "$name".ext