[更新:元の質問では bash と zsh が指定されていましたが、この質問に戻って、プラットフォーム (OS X は Unix です!) と $SHELL 間で私の応答をより便利で移植性の高いものにしようと思いました。他の回答は、リストがどのように作成されたか、または選択がどのように行われたかを質問が示していなかったため、「ランダムな」ファイル名の一時ファイル リストを想定していました。応答で、一時ファイルを使用してリストを作成する 1 つの方法を示します。どうすればランダム化できるかわかりませんfind
操作は「インライン」であり、他の誰かがこれがどのように(移植可能に)行われるかを示してくれることを願っています。また、これがいくつかのコメントや批評を引き付けることを願っています。私は perl のリファレンスを削除しましたが、これを perl で再度実行し、perl は移植性が高いため、Windows 上で実行できるようにすることに挑戦します。コメントを待ってから、この回答を短くしてクリーンアップします。ありがとう。]
ファイル リストの作成
GNU find(1) で多くのことができます。以下は、ファイル名と、必要なデータの 3 つのタブ区切り列 (ファイルの名前、場所、キロバイト単位のサイズ) を含む単一のファイルを作成します。
find / -type f -fprintf tmp.txt '%f\t%h/%f\t%k \n'
ファイル システム全体からエントリを取得するために、すべてのファイル名をランダムにする(つまり、リンクを作成しない) ことを想定しています。私のワークステーションには 800000 個のファイルがありますが、大量の RAM があるため、これを行うのにそれほど時間はかかりません。私のラップトップには約 300K のファイルがあり、メモリはあまりありませんが、完全なリストを作成するのに数分しかかかりませんでした。検索から特定のディレクトリを除外または削除して調整する必要があります。
フラグの良いところは-fprintf
、ファイル名のスペースを処理しているように見えることです。vim
andを使用してファイルを調べsed
(つまり、スペースを含む行を探す)、 and の出力を比較するwc -l
ことuniq
で、出力の感覚をつかみ、結果のリストが正常かどうかを確認できます。cut
次に、これを、grep
またはsed
、および友人にパイプして、必要awk
な方法でファイルを作成できます。たとえば、シェルプロンプトから:
~/# touch `cat tmp.txt |cut -f1`
~/# for i in `cat tmp.txt|cut -f1`; do cat tmp.txt | grep $i > $i.dat ; done
ここでは、作成したファイルに.dat
拡張子を付けて、参照先のファイルと区別し、簡単に移動したり削除したりできるようにしています。拡張子はそのままにしておく必要はありません$i > $i
。
このフラグの悪い点-fprintf
は、GNU find でのみ使用でき、POSIX 標準フラグではないため、OS X や BSD では使用できないことです(ただし、GNU find は Unix にまたはfind(1)
としてインストールされる場合があります)。これを行うためのより移植性の高い方法は、ファイルのまっすぐなリストを作成することです(これには、ZFS プールに 800k ファイルと多くの低速ドライブがある私のシステムでは約 15 秒かかります。より効率的なものを考え出すことは、人々にとって簡単なはずです。コメントでやってください!)。そこから、標準ユーティリティを使用して必要なデータ値を作成し、Florin Stingaciu が上に示したようにファイル リストを処理できます。gfind
gnufind
find / -type f > tmp.txt
#!/bin/sh
# portably get a random number (OS X, BSD, Linux and $SHELLs w/o $RANDOM)
randnum=`od -An -N 4 -D < /dev/urandom` ; echo $randnum
for file in `cat tmp.txt`
do
name=`basename $file`
size=`wc -c $file |awk '{print $1}'`
# Uncomment the next line to see the values on STDOUT
# printf "Location: $name \nSize: $size \n"
# Uncomment the next line to put data into the respective .dat files
# printf "Location: $file \nSize: $size \n" > $name.dat
done
# vim: ft=sh
ここまでたどり着いたら、これが大量のファイルを作成することに気付くでしょう。私のワークステーションでは、800kの.dat
ファイルが作成されます。では、処理のために 800k のリストから 1000 個のファイルをランダムに選択するにはどうすればよいでしょうか? それにはいくつかの方法があります。
ファイルリストからランダムに選択する
システム上のすべてのファイルのリストがあります (!)。1000 個のファイルを選択するには、リスト ファイル ( ) からランダムに 1000 行を選択する必要がありますtmp.txt
。上で見たクールな手法を使用して乱数を生成することにより、選択する行番号の上限を設定できますod
。非常にクールでクロスプラットフォームであるため、シェルでこれにエイリアスを設定しています ;-) - 次に、モジュロ除算を実行します( %
)ファイル内の行数を除数として使用します。次に、その番号を取得して、それが awk または sed に対応するファイル内の行を選択し (例: sed -n <$RANDOMNUMBER>p filelist
)、1000 回反復してさっさと! 1000 個のランダム ファイルの新しいリストがあります。かどうか...本当に遅いです!スピードアップする方法を探しているawk
とsed
、(行ではなく) バイト単位でファイルを検索し、結果をordd
を使用して行に変換する Alex Lines を使用した優れたトリックです。詳しくはアレックスのブログをご覧ください。彼のテクニックに関する私の唯一の問題は、スイッチを十分に高い数値に設定することでした。不思議な理由で (誰かが説明してくれることを願っています)、おそらく私のせいで、実際の最大行長よりもはるかに大きな数に設定しない限り、不完全な行が 吐き出され ます。おそらく文字とバイトを混同していたと思います。説明はありますか?sed
awk
count=
locale
LC_ALL=en_US.UTF-8
dd
randlist.txt
count=
したがって、上記の警告の後、2 つ以上のプラットフォームで動作することを期待して、問題を解決するための私の試みを次に示します。
#!/bin/sh
IFS='
'
# We create tmp.txt with
# find / -type f > tmp.txt # tweak as needed.
#
files="tmp.txt"
# Get the number of lines and maximum line length for later
bytesize=`wc -c < $files`
# wc -L is not POSIX and we need to multiply so:
linelenx10=`awk '{if(length > x) {x=length; y = $0} }END{print x*10}' $files`
# A function to generate a random number modulo the
# number of bytes in the file. We'll use this to find a
# random location in our file where we can grab a line
# using dd and sed.
genrand () {
echo `od -An -N 4 -D < /dev/urandom` ' % ' $bytesize | bc
}
rm -f randlist.txt
i=1
while [ $i -le 1000 ]
do
# This probably works but is way too slow: sed -n `genrand`p $files
# Instead, use Alex Lines' dd seek method:
dd if=$files skip=`genrand` ibs=1 count=$linelenx10 2>/dev/null |awk 'NR==2 {print;exit}'>> randlist.txt
true $((i=i+1)) # Bourne shell equivalent of $i++ iteration
done
for file in `cat randlist.txt`
do
name=`basename $file`
size=`wc -c <"$file"`
echo -e "Location: $file \n\n Size: $size" > $name.dat
done
# vim: ft=sh