ユーザーは、foo.sh file.txt
やfoo.sh dir/file.txt
のように、スクリプトのある時点で作成または上書きされるファイルパスを使用してスクリプトを呼び出します。
作成または上書きの動作は、ファイルを>
出力リダイレクト演算子の右側に配置する、またはtee
に引数として渡す(実際には、 tee
の引数としては、まさに私がやっていることです)。
スクリプトの概要に入る前に、ファイルcanが作成/上書きされているかどうかを確認しますが、実際には作成しません。このチェックは完全である必要はありません、そしてはい、状況はチェックと実際にファイルが書き込まれるポイントとの間で変化する可能性があることを理解しています-しかし、ここではベストエフォートタイプで問題ありませんファイルパスが無効な場合に私が早期に救済できるようにソリューション。
ファイルを作成できなかった理由の例:
dir/file.txt
などのディレクトリコンポーネントが含まれていますが、ディレクトリdir
が存在しませんはい、許可を「前もって」チェックすることはNIX Way™ではないことに気づきました。むしろ、操作を試し、後で許しを求めるべきです。しかし、私の特定のスクリプトでは、これはユーザーエクスペリエンスの低下につながり、責任のあるコンポーネントを変更することはできません。
明らかなテストは次のとおりです。
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
を自由に作成し、それを使用して自由に実行できます。
私が集めているものから、使用するときにそれを確認したい
_tee -- "$OUT_FILE"
_
(_--
_であるか、-で始まるファイル名では機能しないことに注意してください)、tee
は書き込み用にファイルを開くことに成功します。
それはそれ:
今のところ、ファイル名に含めることができるバイト値、ディスククォータ、プロセス制限、selinux、apparmor、またはその他のセキュリティメカニズムに制限があるvfat、ntfs、hfsplusなどのファイルシステム、ファイルシステム全体、iノードが残っていない、デバイスは無視します何らかの理由でその方法で開くことができないファイル、現在一部のプロセスアドレス空間にマップされている実行可能ファイルは、ファイルを開いたり作成したりする機能にも影響を与える可能性があります。
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-like Shellで、単に
_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
_の場合、アプローチはファイルで_ls -Ld
_を実行し、エラーメッセージがENOENTエラーに対応するかどうかを確認することです。ただし、確実かつ移植性のある方法で行うのは難しい作業です。
1つのアプローチは次のとおりです。
_msg_for_ENOENT=$(LC_ALL=C ls -d -- '/no such file' 2>&1)
msg_for_ENOENT=${msg_for_ENOENT##*:}
_
(エラーメッセージendsがsyserror()
でENOENT
に変換され、その変換に_:
_が含まれていないと仮定します)、そして代わりに_[ -e "$file" ]
_の例:
_err=$(ls -Ld -- "$file" 2>&1)
_
そして、ENOENTエラーをチェックしてください
_case $err in
(*:"$msg_for_ENOENT") ...
esac
_
_$file:P
_の部分は、bash
で、特にFreeBSDで達成するのが最も難しいです。
FreeBSDにはrealpath
コマンドと、_-f
_オプションを受け入れるreadlink
コマンドがありますが、ファイルがシンボリックリンクでない場合は使用できません解決します。これはPerl
のCwd::realpath()
と同じです。
python
のos.path.realpath()
はzsh
_$file:P
_と同様に機能するように見えるため、少なくとも1つのバージョンのpython
がインストールされており、それらの1つを参照するpython
コマンドです(これはFreeBSDでは提供されていません)。
_dir=$(python -c '
import os, sys
print(os.path.realpath(sys.argv[1]) + ".")' "$dir") || return
dir=${dir%.}
_
しかし、その後、python
ですべてを行うこともできます。
または、これらすべてのコーナーケースを処理しないことを決定できます。
検討したいオプションの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
trap
を使用してエラー時にファイルをクリーンアップすることもできます。これは一般的な方法です。
このようにして、ファイルを作成できる権限のrealチェックを取得すると同時に、それが失敗した場合に十分早い段階でファイルを実行できます長いチェックを待つ時間を費やしていません。
UPDATED:チェックが失敗した場合にファイルを破壊しないようにするには、bashの fd<>file
redirection を使用しますファイルをすぐに切り捨てないでください。 (ファイルからの読み取りについては気にしません。これは単なる回避策であるため、切り捨てません。>>
を追加してもおそらく機能しますが、これはもう少しエレガントになりがちですが、 O_APPENDフラグを画像から除外します。)
内容を置き換える準備ができたら、最初にファイルを切り捨てる必要があります(そうしないと、書き込むファイルのバイト数が以前のファイルより少ない場合は、後続のバイトがそこに残ります)。 truncate(1) この目的のためにcoreutilsからのコマンド。これに、(/dev/fd/3
疑似ファイルを使用して)持っているオープンファイル記述子を渡すことができるため、ファイルを再度開く必要はありません。 (繰り返しになりますが、技術的には: >dir/file.txt
のような単純なものでもおそらく機能しますが、ファイルを再度開く必要がない方がよりエレガントなソリューションです。)
ユーザーエクスペリエンスが疑問を引き起こしているとのことですが、 UXの角度からお答えします。技術面では良い答えが得られるからです。
事前にチェックを実行するのではなく、結果を一時ファイルに書き込んで、最後に、結果をユーザーの希望するファイルに配置しませんか?お気に入り:
userfile=${1:?Where would you like the file written?}
tmpfile=$(mktemp)
# ... all the complicated stuff, writing into "${tmpfile}"
# fill user's file, keeping existing permissions or creating anew
# while respecting umask
cat "${tmpfile}" > "${userfile}"
if [ 0 -eq $? ]; then
rm "${tmpfile}"
else
echo "Couldn't write results into ${userfile}." >&2
echo "Results available in ${tmpfile}." >&2
exit 1
fi
このアプローチの良い点:通常のハッピーパスシナリオで目的の操作を生成し、テストと設定の原子性の問題を回避し、必要に応じて作成中にターゲットファイルの権限を保持し、実装が非常に簡単です。
注:mv
を使用した場合、一時ファイルのアクセス許可を保持します。それは望ましくありません。ターゲットファイルに設定されたアクセス許可を保持したいと思います。
今、悪い:それは2倍のスペースを必要とします(cat .. >
構文)、ターゲットファイルが必要なときに書き込み可能でなかった場合、ユーザーに手動での作業を強制し、一時ファイルをそのまま残します(セキュリティまたはメンテナンスの問題がある可能性があります)。
DopeGhotiのソリューションの方が良いと思いますが、これも機能するはずです。
file=$1
if [[ "${file:0:1}" == '/' ]]; then
dir=${file%/*}
Elif [[ "$file" =~ .*/.* ]]; then
dir="$(PWD)/${file%/*}"
else
dir=$(PWD)
fi
if [[ -w "$dir" ]]; then
echo "writable"
#do stuff with writable file
else
echo "not writable"
#do stuff without writable file
fi
最初のif構文は、引数がフルパス(/
で始まる)かどうかをチェックし、dir
変数を最後の/
までのディレクトリパスに設定します。それ以外の場合、引数が/
で始まらず、/
(サブディレクトリを指定)が含まれている場合、dir
を現在の作業ディレクトリ+サブディレクトリパスに設定します。それ以外の場合は、現在の作業ディレクトリを想定します。次に、そのディレクトリが書き込み可能かどうかを確認します。
以下に概説するような通常の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
TL; DR:
_: >> "${userfile}"
_
_<> "${userfile}"
_または_touch "${userfile}"
_とは異なり、これはファイルのタイムスタンプに誤った変更を加えず、書き込み専用ファイルでも機能します。
OPから:
ファイルを作成/上書きできるかどうかは妥当なチェックをしたいが、実際には作成したくない。
そしてあなたのコメントから Xの観点からの私の答え :
これが失敗する大部分の時間は、提供されたパスが無効であるためです。不正ユーザーがFSを同時に変更して操作の途中でジョブを中断することを合理的に防ぐことはできませんが、無効または書き込み不可の#1失敗の原因を確認することはできます。道。
唯一の信頼できるテストは open(2)
ファイルです。これだけが、書き込み可能性に関するすべての質問を解決します。パス、所有権、ファイルシステム、ネットワーク、セキュリティコンテキストなど。その他のテストでは書き込み可能性の一部に対処しますが、他の一部には対処しません。テストのサブセットが必要な場合は、最終的に何が重要かを選択する必要があります。
しかし、ここで別の考えがあります。私が理解していることから:
#1のためにこの事前チェックを行う必要があり、#2のために既存のファイルをいじりたくありません。それでは、シェルにファイルを開いて追加するように依頼するだけで、実際には何も追加しないのですか?
_$ tree -ps
.
├── [dr-x------ 4096] dir_r
├── [drwx------ 4096] dir_w
├── [-r-------- 0] file_r
└── [-rw------- 0] file_w
$ for p in file_r dir_r/foo file_w dir_w/foo; do : >> $p; done
-bash: file_r: Permission denied
-bash: dir_r/foo: Permission denied
$ tree -ps
.
├── [dr-x------ 4096] dir_r
├── [drwx------ 4096] dir_w
│ └── [-rw-rw-r-- 0] foo
├── [-r-------- 0] file_r
└── [-rw------- 0] file_w
_
内部的には、これにより、書き込み可能性の問題が正確に解決されます。
_open("dir_w/foo", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
_
ただし、ファイルの内容やメタデータは変更しません。今、はい、このアプローチ:
lsattr
である程度検出し、それに応じて対応できます。rm
でこれを軽減します。(他の回答では)最もユーザーフレンドリーなアプローチは、ユーザーが移動する必要のある一時ファイルを作成することだと主張していますが、これは最小限のユーザー敵意アプローチで完全に入力を精査します。