bashでファイルを作成、切り捨て、または上書きできるかどうかを確認する方法は?

bashでファイルを作成、切り捨て、または上書きできるかどうかを確認する方法は?

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
}

bashBourneに似たシェルで交換するだけです。

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:hdir=$(dirname -- "$file"; echo .); dir=${dir%??}、またはGNU dirname:に置き換えることができますIFS= read -rd '' dir < <(dirname -z -- "$file")

$errnos[ERRNO] == ENOENT1つのアプローチは、ファイル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

realpathFreeBSDには、コマンドとオプションをreadlink許可するコマンドがありますが、-fファイルが解決されていないシンボリックリンクである状況では使用できません。これはperlsと同じですCwd::realpath()

pythonos.path.realpath()と同様に動作するようですzsh $file:P。したがって、少なくともpython1つのバージョンがインストールされており、そのうちの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

関連情報