foo.sh file.txt
ユーザーは、スクリプト内の特定のポイントで作成または上書きされたファイルパスを使用して自分のスクリプトを呼び出しますfoo.sh dir/file.txt
。
生成または上書きの動作は、ファイルを出力リダイレクト演算子の右側に配置するか、引数>
として渡す要件と非常によく似ていますtee
(実際の引数として渡すことは、tee
私が行うことです)。
スクリプトを開始する前に、ファイルを正しく確認したいと思います。できる実際に作成せずに生成/上書きされます。この検査は完璧である必要はありません。はい、スキャンと実際にファイルを作成する時点の間で状況が変わることがあることを知っています。しかし、ここでは大丈夫です。最大の努力ファイルパスが間違っている場合は、早期に終了できるようにソリューションを入力してください。
ファイルを生成できない理由の例:
- ファイルにディレクトリコンポーネントが含まれていますが、
dir/file.txt
ディレクトリはdir
存在しません。 - ユーザーに指定されたディレクトリ(またはディレクトリが指定されていない場合はCWD)に対する書き込み権限がありません。
はい、権限を「事前」として確認するのではないことを知っています。Unix Way™、むしろまず手術をしてみて許しを求めなければならなかったのに。しかし、私の特定のスクリプトでは、これはユーザーエクスペリエンスが悪く、担当コンポーネントを変更できませんでした。
答え1
明らかなテストは次のとおりです。
if touch /path/to/file; then
: it can be created
fi
しかし、まだファイルがない場合は、実際にファイルを生成します。私たちはそれを自分でまとめることができます:
if touch /path/to/file; then
rm /path/to/file
fi
ただし、これにより不要な既存のファイルが削除されます。
しかし、我々はこの問題に対する解決策を持っています:
if mkdir /path/to/file; then
rmdir /path/to/file
fi
ディレクトリは、ディレクトリ内の他のオブジェクトと同じ名前を持つことはできません。ディレクトリを作成できますが、ファイルを作成できない状況は想像できません。このテストが完了すると、スクリプトはルーチンを自由に生成し、/path/to/file
必要な操作を実行できます。
答え2
集めたものから使用時に確認したいこと
tee -- "$OUT_FILE"
(--
そうでなければ - で始まるファイル名には機能しません。)tee
ファイルは書き込みのために正常に開きます。
それだけです:
- ファイルパスの長さがPATH_MAX制限を超えていません。
- ファイルが存在し(symlink確認後)、タイプではありません。目次そして、あなたはそれに対する書き込み権限を持っています。
- ファイルが存在しない場合、ファイルのディレクトリ名はディレクトリとして存在し(シンボリックリンクの確認後)、ユーザーにそれに対する書き込みおよび検索権限があり、ファイル名はディレクトリを含むファイルシステムのNAME_MAX制限を超えません。
- あるいは、ファイルが存在せず、シンボリックリンク循環でもないが、上記の条件を満たすファイルを指すシンボリックリンクである。
ファイル名に含めることができるバイト値、ディスククォータ、プロセス制限、selinux、apparmor、またはその他のセキュリティメカニズム、ファイルシステム全体、残りのinodeなしの制限付きvfat、ntfs、またはhfsplusなどのファイルシステムは今や無視しましょう。デバイス何らかの理由でこの方法で開くことができないファイル、現在一部のプロセスアドレス空間にマップされている実行可能ファイルなどは、すべてファイルを開いたり生成したりする機能に影響を与える可能性があります。
そしてzsh
:
zmodload zsh/system
tee_would_likely_succeed() {
local file=$1 ERRNO=0 LC_ALL=C
if [ -d "$file" ]; then
return 1 # directory
elif [ -w "$file" ]; then
return 0 # writable non-directory
elif [ -e "$file" ]; then
return 1 # exists, non-writable
elif [ "$errnos[ERRNO]" != ENOENT ]; then
return 1 # only ENOENT error can be recovered
else
local dir=$file:P:h base=$file:t
[ -d "$dir" ] && # directory
[ -w "$dir" ] && # writable
[ -x "$dir" ] && # and searchable
(($#base <= $(getconf -- NAME_MAX "$dir")))
return
fi
}
bash
Bourneに似たシェルで交換するだけです。
zmodload zsh/system
tee_would_likely_succeed() {
<zsh-code>
}
そして:
tee_would_likely_succeed() {
zsh -s -- "$@" << 'EOF'
zmodload zsh/system
<zsh-code>
EOF
}
ここでの具体的な機能zsh
は$ERRNO
、(最後のシステムコールのエラーコード公開)$errnos[]
と対応する標準Cマクロ名に変換する連想配列です。および$var:h
(cshから)および$var:P
(zsh 5.3以降が必要です)。
Bashにはまだ同等のものはありません。
$file:h
dir=$(dirname -- "$file"; echo .); dir=${dir%??}
、またはGNU dirname
:に置き換えることができますIFS= read -rd '' dir < <(dirname -z -- "$file")
。
$errnos[ERRNO] == ENOENT
1つのアプローチは、ファイルls -Ld
を実行してエラーメッセージがENOENTエラーに対応することを確認することです。しかし、これを安定して移植可能にするのは難しいです。
1つのアプローチは次のとおりです。
msg_for_ENOENT=$(LC_ALL=C ls -d -- '/no such file' 2>&1)
msg_for_ENOENT=${msg_for_ENOENT##*:}
(エラーメッセージを想定終わるsyserror()
)次に、次のようにENOENT
します。:
[ -e "$file" ]
err=$(ls -Ld -- "$file" 2>&1)
ENOENTエラーを確認してください。
case $err in
(*:"$msg_for_ENOENT") ...
esac
これは.NET、特にFreeBSDで$file:P
実装するのが最も難しい部分です。bash
realpath
FreeBSDには、コマンドとオプションをreadlink
許可するコマンドがありますが、-f
ファイルが解決されていないシンボリックリンクである状況では使用できません。これはperl
sと同じですCwd::realpath()
。
python
os.path.realpath()
と同様に動作するようですzsh
$file:P
。したがって、少なくともpython
1つのバージョンがインストールされており、そのうちの1つを参照するコマンド(FreeBSDでは提供されていません)があると仮定するpython
と、次のことができます。
dir=$(python -c '
import os, sys
print(os.path.realpath(sys.argv[1]) + ".")' "$dir") || return
dir=${dir%.}
ただし、ですべての操作を実行することもできますpython
。
あるいは、これらの極端なケースをすべて処理しないことを決定することもできます。
答え3
考慮すべき1つのオプションは次のとおりです。作る以前にファイルを作成し、後でスクリプトで入力します。コマンドを使用してexec
ファイルディスクリプタ(3、4など)からファイルを開き、ファイルディスクリプタ(など>&3
)にリダイレクトを使用してファイルに内容を書き込むことができます。
それは次のとおりです。
#!/bin/bash
# Open the file for read/write, so it doesn't get
# truncated just yet (to preserve the contents in
# case the initial checks fail.)
exec 3<>dir/file.txt || {
echo "Error creating dir/file.txt" >&2
exit 1
}
# Long checks here...
check_ok || {
echo "Failed checks" >&2
# cleanup file before bailing out
rm -f dir/file.txt
exit 1
}
# We're ready to write, first truncate the file.
# Use "truncate(1)" from coreutils, and pass it
# /dev/fd/3 so the file doesn't need to be reopened.
truncate -s 0 /dev/fd/3
# Now populate the file, use a redirection to write
# to the previously opened file descriptor.
populate_contents >&3
また、aを使用して間違ったtrap
ファイルをクリーンアップすることもできます。これは一般的な慣行です。
これにより本物権限を確認すると、ファイルを生成すると同時にファイルを早期に実行できるため、失敗しても長い確認のために待つために時間を無駄にする必要はありません。
修正する:スキャンに失敗した場合にファイルの破損を防ぐためにbashを使用してください。fd<>file
リダイレクトファイルはすぐに切り捨てられません。 (私たちはファイルから読み取るのに気にしません。これは解決策なので切り捨てられません>>
。
コンテンツを置き換える準備ができたら、まずファイルを切り取る必要があります(そうしないと、ファイルに以前より少ないバイトを書き込むと、後続のバイトが残ります)。切り取り(1)/dev/fd/3
ファイルを再度開く必要がないように、ダミーファイルを使用してcoreutilsコマンドに開いているファイル記述子を渡します。 (技術的には、同様で簡単なアプローチはうまくいく: >dir/file.txt
かもしれませんが、ファイルを再度開く必要がないのがよりエレガントなソリューションです。)
答え4
test
以下に説明する一般的なコマンドを使用するのはどうですか?
FILE=$1
DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'
if [ -d $DIR ] ; then
echo "base directory $DIR for file exists"
if [ -e $FILE ] ; then
if [ -w $FILE ] ; then
echo "file exists, is writeable"
else
echo "file exists, NOT writeable"
fi
elif [ -w $DIR ] ; then
echo "directory is writeable"
else
echo "directory is NOT writeable"
fi
else
echo "can NOT create file in non-existent directory $DIR "
fi