dotglobの現在の値を使用します。

dotglobの現在の値を使用します。

私はbashプロンプトPS1で作業しており、現在のディレクトリのファイル数を印刷したいと思います。

動作するコードを書いたが、
この(冗長)スクリプトを単純化する方法はありますか?

$(ls -l | grep ^- | wc -l) $(if [ $(ls -l | grep ^- | wc -l) -eq 1 ]; then echo "file"; else echo "files"; fi)

私の目的は、フォルダ内のファイルの数を印刷しますもし宣言すべき扱う複数形は2つの状況を管理します。

  • ファイル1個
  • 2ファイル

たとえば、my〜フォルダには3つのファイルが含まれているため、スクリプトは「files」という単語を印刷する必要があります。 my ~/Desktopにはファイルが1つしかないため、スクリプトは「files」を印刷する必要があります。

上記のコード行を書いて作業は完了しましたが、よりきれいでスマートな方法があると思いました。

答え1

まず、より高速なコマンドを使用して正しい番号を見つけることができます。私のテストでは、これはfind . -maxdepth 1 -type f -not -name '.*' | wc -l(約4:3)よりも速いことがわかりました。ls -l | grep '^-' | wc -lファイルも非表示にするには、検索部分を使用または省略するls -a必要があります。-not -name '.*'

次に、ファイルを2回計算する代わりに、結果を変数に保存して再利用します。

$(count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
  if [[ "$count" -eq 1 ]]; then
    echo "1 file"
  else
    echo "$count files"
  fi)

ご覧のとおり、サブシェルを使用する必要があります。それ以外の場合は、2番目のコマンドで変数を使用できません。私もここではなく[[ ... ]]bash固有のものを使用しました[ ... ]。一般的に言えば、これはより良い解決策です。

最後に、Bash変数を使用して$PROMPT_COMMANDプロンプトを印刷する前にいくつかのコードを実行することもできます。$PS1これにより、すべてのコードを変数に保存する必要がなくなります$PS1。変数$countは次のとおりです。グローバルしかし。次のように見えます。

function count_files () {
  __count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
  if [[ "$__count" -eq 1 ]]; then
    __plural=
  else
    __plural=s
  fi
}
PROMPT_COMMAND=count_files
PS1='other stuff $__count file$__plural more stuff'

しかし、これらのステップのどれを単純化すると考えるかは不明です。 :)

編集する

ちょうど見つけたこのスレッド。次のようにファイル数を計算するために使用できます。

function count_files () {
  local name
  __count=0
  for name in *; do
    [[ -f "$name" ]] && ((__count++))
  done
  # like above ...
}

検索バージョンより約1:13ほど高速です。これは少数のプロセスを開始するため、男性的です(findバージョンがlsバージョンよりも速い理由と同じです)。トレッドには別のextglobソリューションがあります。それはすべてあなたがファイルを望むか、ファイルへのリンクを望むかによって異なります。繰り返しますが、コードは速くなりましたが、今はもっと複雑に見えます。では、どのような「シンプルさ」を目指していますか?

編集2

私がコメントで言ったことにもかかわらず、プロセスを開始するオーバーヘッドは、ソートする必要がある場合、ソートするオーバーヘッドに比べておそらく小さいでしょう。たくさん/usr/bin/ファイルの場合、約2500個のファイルを含むコードを実行すると表示が開始されます。

答え2

スクリプトを単純化する最初の方法は、ファイル数を変数に保存して再計算する必要がないようにすることです。

現在のディレクトリ内のファイル数を見つける他の方法:

n=$(ls -l | grep -c ^-)

ここでの単純化は、grepの-cオプションを使用して一致を計算することです。一致を誤って計算するリスクがあります。lsの出力を解析します。というファイルがあれば、$'some\n-file'行の先頭にハイフンを入れるようです。

n=$(stat -c %F -- * | grep -c 'regular .*file')

grep は、.*「一般ファイル」と「一般空ファイル」が一致すると見なされます。これ統計資料*このコマンドは、シェルglobが回避する各ファイルの種類を出力しますls

bashに慣れているが聞いたことがあるならzshと強力なファイル名のグロービング構文、次のことができます。

n=$(zsh -c 'a=( *(.) ); echo ${#a}')

aその中に、拡張子が「純粋なファイル」であるファイルのリストでのみフィルタ処理されたファイルで埋められる配列を作成します。*.

複素数を正しく印刷するには、ケースステートメントを検討してください。

case $n in
(1) printf "1 file";;
(*) printf "$n files";;
esac

Case ステートメントは、ファイルの数に応じて異なるメッセージを印刷したい場合、より大きな柔軟性を提供します(例:「ファイルなし!」)。

より簡単には、次の条件を検討してください。

[[ $n == 1 ]] && printf "1 file" || printf "$n files"

最後に、Steeldriverの推奨事項について説明します。

n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
printf "%d file%s" "$n" "$(test "$n" -ne 1 && echo s)"

nこれは(最終的に)コマンド置換を有効にして値を割り当てます。一時コマンド置換では、filesすべてのエントリとdirsディレクトリに固有の2つの配列を作成しました。コマンド置換の最後の作業は、2つの違いを報告することです。次に、printf適切な複数形のサフィックスでファイル数を印刷します。

これはあなたが持っているすべての設定を使いますdotglob。ドットファイルを強制的に計算(または省略)するには、dotglobコマンドオーバーライドで設定または設定を解除する必要があります。

dotglobの現在の値を使用します。

n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

現在のドットグローブに関係なく、ドットファイルを強制的に計算します。

n=$(shopt -s dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

実装するいいえ現在のdotglobに関係なく、ドットファイルの数:

n=$(shopt -u dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

答え3

find . -maxdepth 1 -type f | awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'

説明する:

  1. find . -maxdepth 1 -type f- 現在のディレクトリ内のすべてのファイルを検索します。
  2. awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
    • NR= プログラムから受信したライン数find、ファイル数と同じです。
    • (NR > 1) ? "s" : "")- 行(ファイル)の数が1つを超える場合は、単語のs末尾に末尾を追加しますfile

答え4

私はこれを作りました。lsファイル名に奇妙な文字(改行文字など)が含まれると、出力が破損する可能性があるため、パス名拡張を使用しませんでした。

#!/usr/bin/env bash

((n=0))

for i in *; do
  [[ -f "${i}" ]] && ((n++))
done

((n==1)) && { echo "${n} file"; exit; }
echo "${n} files"

関連情報