空のディレクトリの移植性チェック

空のディレクトリの移植性チェック

BashとDashを使用すると、シェルを使用して空のディレクトリを確認できます(単純化のためにドットファイルは無視されます)。

set *
if [ -e "$1" ]
then
  echo 'not empty'
else
  echo 'empty'
fi

しかし、私は最近この場合Zshが失敗することを知りました。

% set *
zsh: no matches found: *

% echo "$? $#"
1 0

したがって、コマンドがset失敗するだけでなく設定もできません$@$#isかどうかをテストできると思いましたが、0Zshが実行を中断したようです。

% { set *; echo 2; }
zsh: no matches found: *

BashとDashとの比較:

$ { set *; echo 2; }
2

bash、dash、zshで動作するようにこれを実行できますか?

ベストアンサー1

これにはいくつかの問題があります

set *
if [ -e "$1" ]
then
  echo 'not empty'
else
  echo 'empty'
fi

パスワード:

  • このnullglobオプションを有効にすると(以前は他のほとんどのシェルでサポートされていた)、すべてのシェル変数(および一部のシェルの関数)が代わりに一覧表示されます。zshset *set
  • 非表示の最初のファイル名がまたは-で始まる場合は、+オプションとして扱われますset。 zshとkshのいくつかの実装/バージョンでは+A[$(reboot)]set -- *
  • *非表示のファイルのみが拡張されるため、ディレクトリが空であることをテストするのではなく、ディレクトリに非表示のファイルが含まれているかどうかをテストします。一部のシェルでは、dotglobまたはオプションを使用するか、特殊変数(シェルに応じて)をglobdot使用して問題を解決できます。FIGNORE
  • [ -e "$1" ]stat()システムコールが成功したかどうかをテストします。最初のファイルがアクセスできない場所へのシンボリックリンクの場合、falseが返されます。ディレクトリが空であることを確認するためにファイルは必要ありませんstat()(または必要ありませんlstat())。内容があることを確認するだけです。
  • *拡張機能は現在ディレクトリを開き、すべての項目を検索し、非表示になっていないすべての項目を保存して並べ替える必要があります。これは非常に非効率的です。

ディレクトリが空でないこと(およびを除くすべてのエントリがあるかどうか)を確認する最も効率的な方法は、glob修飾子を使用する.ことです( for..zshFFいっぱい、ここでは空ではないことを意味します):

if [ .(NF) ]; then
  print . is not empty
else
  print "it's empty or I can't read it"
fi

Nnullglobグローバル予選です。したがって、いっぱいになると.(NF)拡張され、そうでなければ何もありません。..

lstat()ディレクトリ内のzshリンク数が2より大きいと判断された場合は、1つ以上のサブディレクトリがあるため空ではないため、ディレクトリを開く必要さえありません。この場合、読み取り権限がなくてもディレクトリが空ではないことがわかります。それ以外の場合、zshはディレクトリを開き、その内容を読み取った後、すべての内容を読み取ったり、保存したり、並べ替えたりせずに、どちらも最初の.項目で停止します。..

POSIXシェル(zshエミュレーションでPOSIXとしてのみ動作sh)を使用すると、globを使用してディレクトリが空でないことを確認するのは非常に厄介です。

1つの方法は次のとおりです。

set .[!.]* '.[!.]'[*] .[.]?* [*] *
if [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]; then
  echo "empty or can't read"
else
  echo not empty
fi

(Glob関連のオプションがデフォルト値から変更されず(POSIXのみ指定)、(for)、(for)変数がnoglob設定されていない(for)有効な文字を形成しないバイトシーケンスを含むファイル名がないとします。 。GLOBIGNOREbashFIGNOREkshyash

POSIXシェルでglobが一致しないと拡張されないというアイデアです(これは1970年代後半のBourneシェルで導入されたバグ機能でした)。したがって、==をset -- *取得すると、一致するものがないかどうか、名前が存在するファイルがあるかどうかはわかりません。$1**

問題を解決する(欠陥のある)方法はを使用することです[ -e "$1" ]set -- [*] *ファイルが存在しない場合は、上記のファイルが残り、隠されたファイル[*] *に対して同様の操作を実行する*ため、両方のケースを明確にします。これは、Bourneシェルのもう1つの欠陥(Forsythシェル、pdksh、およびpdkshによって修正された)* *のためにやや恥ずかしいです。拡張には特別な(医師)項目が含まれており、報告されたとき。zshfish.*...readdir()

したがって、すべてのシェルで機能させるには、次のようにします。

cwd_empty()
  if [ -n "$ZSH_VERSION" ]; then
    eval '! [ .(NF) ]'
  else
    set .[!.]* '.[!.]'[*] .[.]?* [*] *
    [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]
  fi

それにもかかわらず、zsh基本構文はPOSIX sh構文と互換性がありません。これは、Bourneシェル(POSIX.2が最初にリリースされる前に)のほとんどの主な問題を以前のバージョンと互換性のない方法で修正するためです*。 Bourneシェル以前のシェルにはこの問題はありcshませtcshfishでした。.*含まれていますが、.分割+globなどの他のいくつかの項目は引数または算術拡張に対して実行されるため、エミュレーションを開かない限り、..POSIX shで書かれたコードは期待できません。常に動作します。zshsh

シミュレーションshは特に存在するので、 で行うことができますzsh

POSIXでファイルに書き込むには、source次のようにします。shzsh

emulate sh -c 'source ./that-file'

その後、ファイルはシミュレーションで評価され、ファイルで宣言されたすべてのsh機能は対応するシミュレーションモードのままです。

おすすめ記事