ディレクトリごとの特定のファイル形式のディスク使用量を測定します(「du --include」のデモンストレーションで繰り返し)。

ディレクトリごとの特定のファイル形式のディスク使用量を測定します(「du --include」のデモンストレーションで繰り返し)。

これは私の作業コードですが、最適化されていないと思います。これよりも早く作業を完了する方法が必要です。

find . -type f -iname '*.py' -printf '%h\0' |
  sort -z -u |
  xargs -r -0 -I{} sh -c '
    find "{}" -maxdepth 1 -type f -iname "*.py" -print0 |
      xargs -r -0 du -sch |
      tail -1 |
      cut -f1 |
      tr "\n" " "
    echo -e "{}"' |
  sort -k1 -hr |
  head -50

目的は、含まれているすべてのディレクトリを再帰的に検索し、*.py各ディレクトリの名前ですべてのファイルの合計サイズを印刷し、*.pyサイズの逆順に並べ替えてから最初の50を表示することです。

このコードをパフォーマンスの面で改善しながらも同じ出力を維持する方法についてのアイデアはありますか?

編集する:

次の例では、提案をテストしました。47GB total: 5805 files 残念ながら、すべての提案が同じ指針に従うわけではないので、最初から最後まで比較することはできません。合計サイズはディスク使用量で、区切り文字は空白でなければなりません。形式は次のようにする必要があります。numfmt --to=iec-i --suffix=B

次の4つはソートされた出力ですが、Davidは実際のディスク使用量ではなくファイルの累積サイズを示しています。しかし、彼の改善は顕著に改善されました。 9.5倍以上速くなりました。 StéphaneとIsaacのコードは参照コードより約32倍速いので、非常に近い勝者です。

$ time madjoe.sh
real    0m2,752s
user    0m3,022s
sys     0m0,785s

$ time david.sh 
real    0m0,289s
user    0m0,206s
sys     0m0,131s

$ time isaac.sh 
real    0m0,087s
user    0m0,032s
sys     0m0,032s

$ time stephane.sh 
real    0m0,086s
user    0m0,013s
sys     0m0,047s

残念ながら、次のコードは最大50個の結果をソートまたは表示しません。さらに、以前にIsaacのコードと比較すると、次のコードはIsaacの改善より約6倍遅くなります。

$ time hauke.sh 
real    0m0,567s
user    0m0,609s
sys     0m0,122s

答え1

配列のすべてのディレクトリ合計を収集し、最後にすべてを印刷して@HaukeLagingのソリューションを簡素化しました(GNU awkを使用)。また、numfmt(最終的に)1回の呼び出しのみが必要です。

#!/bin/sh

find . -type f -iname '*.py' -printf '%s %h\0' |
    awk 'BEGIN { RS="\0"; };

         { gsub(/\\/,"&&"); gsub(/\n/,"\\n");
           size=$1; sub("[^ ]* ",""); dirsize[$0]+=size }

         END {   PROCINFO["sorted_in"] = "@val_num_desc";
                 i=0;
                 for ( dir in dirsize ) { if(++i<=50) 
                     { print dirsize[dir], dir; }else{ exit } 
                 }
             }        ' | numfmt --to=iec-i --suffix=B

これは、ディスク使用量ではなくpyファイルの累積見かけのサイズを生成し、ディレクトリのサブディレクトリにあるファイルを合計することを防ぎます。

答え2

%b見かけのサイズの合計ではないディスク使用量を計算するには、代わりに¹を使用し、%s各ファイルを一度だけ計算する必要があります。つまり、次のようになります。

LC_ALL=C find . -iname '*.py' -type f -printf '%D:%i\0%b\0%h\0' |
  gawk -v 'RS=\0' -v OFS='\t' -v max=50 '
    {
      inum = $0
      getline du
      getline dir
    }
    ! seen[inum]++ {
      gsub(/\\/, "&&", dir)
      gsub(/\n/, "\\n", dir)
      sum[dir] += du
    }
    END {
      n = 0
      PROCINFO["sorted_in"] = "@val_num_desc"
      for (dir in sum) {
        print sum[dir] * 512, dir
        if (++n >= max) break
      }
    }' | numfmt --to=iec-i --suffix=B --delimiter=$'\t'

ディレクトリ名の改行はとしてレンダリングされ、\nバックスラッシュ(最小の現在のロケールでデコードされている)はとしてレンダリングされます\\

ファイルが複数のディレクトリに見つかると、ファイルは最初に見つかったディレクトリに基づいて(順序付けられていない)計算されます。

POSIXLY_CORRECT環境に変数がないとします(変数がある場合、設定はPROCINFO["sorted_in"]適用されないため、gawkリストはソートされません)。これを保証できない場合は、いつでも起動できますgawkenv -u POSIXLY_CORRECT gawk ...GNUenvまたは互換性または(unset -v POSIXLT_CORRECT; gawk ...))。

あなたのアプローチにはいくつかの他の問題があります。

  • それ以外の場合、LC_ALL=CGNUはfind名前がロケールで有効な文字を構成しないファイルを報告しないため、一部のファイルが欠落する可能性があります。
  • 埋め込み{}コードは、sh任意のコード注入の脆弱性を構成します。たとえば、というファイルを考えてみましょう$(reboot).py。決してこれをしてはいけません。ファイルパスは追加のパラメータとして渡され、場所パラメータを使用してコードで参照する必要があります。
  • echo-e任意のデータ(特にここでは意味のないデータ)を表示するために使用することはできません。代わりに使用してくださいprintf
  • ファイルのリストが大きい場合は複数回呼び出すことができます。この場合、最後の行には最後の実行の合計のみが含まれますxargs -r0 du -schdu

1ディスク%b使用量は512バイト単位で報告されます。 512バイトは既存のセクタのサイズなので、ディスク割り当ての最小単位です。%kということもありますが、int(%b / 2)これは512バイトブロックを持つファイルシステムで誤った結果を提供します(ファイルシステムブロックは通常2の累乗であり、サイズは少なくとも512バイトです)。

²GawkLC_ALL=Cでも使用すると、より効率的ですが、バックスラッシュもエンコードされるため、BIG5またはGB18030文字セットを使用するロケールで出力が中断される可能性があります(ファイル名もその文字セットにエンコードされています)。そこに他の文字もあります。コーディングから。

³スクリプトでshisがに設定されている、またはで始まる場合は、環境にエクスポートされるため、その変数も意図せずにこっそり入る可能性があります。bashPOSIXLY_CORRECTyshsh-a-o allexport

答え3

私はあなた自身のduを書く必要があると思います。

現在、2つのfindと1つのduを使用して階層を3回繰り返します。

PerlFile::Findパッケージから始めることをお勧めします。

または、最初の照会で次の内容を出力できます-printf '%k %h\n'。その後、ディレクトリごとに並べ替え、Perlまたはawk(またはbash)を使用してディレクトリを合計し、「人間が」読める形式に変換し、最後に並べ替えとヘッダーを作成できます。

いずれにせよ、A)ディレクトリツリーを一度だけ探索し、B)できるだけ少ないプロセスを作成する必要があります。

編集:実装例

#!/bin/bash

find . -type f -iname '*.py' -printf '%k %h\n' | sort -k2 | (
    at=
    bt=
    output() {
        if [[ -n "$at" ]]
        then
            printf '%s\t%s\n' "$at" "$bt"
        fi
    }
    while read a b
    do
        if [[ "$b" != "$bt" ]]
        then
            output
            bt="$b"
            at=0
        fi
        at=$(( $at + $a ))
    done
    output
) | sort -hr | head -50 | numfmt -d'   ' --field=1 --from-unit=Ki --to=iec-i

注:%kが重要です。 %sは見かけのサイズを報告し、%k(およびdu)はディスクサイズを報告します。スパースファイルと大容量ファイルの場合は異なります。 (必要にdu --apparent-size応じてそうします。)

注:numfmtは最後に配置する必要がある限り実行されます。 「%k」を使用する場合は、開始単位を指定する必要があります。

注:numfmtの-dパラメーターには単一のタブを含める必要があります。ここに入力することはできず、numfmtはそれを許可しません-d'\t'。区切り文字がタブではない場合、ギャップはめちゃくちゃになります。だから私は本文にechoの代わりにprintfを使用しました。 (別の方法は、最後にechoとsedを使用して最初のスペースをタブに変更することです。

注:最初に最初の並べ替えを見逃して再テストすると、一部のディレクトリに重複するエントリが発生しました。

注:numfmtは最近追加されました。

答え4

これははるかに速いかもしれませんが、あなたの方法とまったく同じではありませんか?サブディレクトリファイルは2回カウントされません。

find . -type f -iname '*.py' -printf '%s %h\0' |
    awk 'BEGIN { RS="\0"; }; '\
'{ pos=index($0," "); size=substr($0,1,(pos-1)); dir=substr($0,pos+1); gsub("\n","\\n",dir); '\
'if(dir!=lastdir) { if(NR>1) { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; } '\
'sizesum=size; lastdir=dir; } '\
'else sizesum=sizesum+size; }; '\
'END { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; }'

3,2KiB ./dir1
1,1MiB ./dir2

より速いものに加えて、改行文字をリテラルに置き換えます\n。改行を含むディレクトリ名が必要な場合は、パイプの終わりまでこれを処理する必要があります。これはコードで何もしないことです。

関連情報