約2000ファイルのディレクトリがあります。N
bashスクリプトまたはパイプされたコマンドのリストを使用して、ファイルのランダムなサンプルを選択するにはどうすればよいですか?
ls | shuf -n 5
Unix Stackexchangeからのソース
約2000ファイルのディレクトリがあります。N
bashスクリプトまたはパイプされたコマンドのリストを使用して、ファイルのランダムなサンプルを選択するにはどうすればよいですか?
ls | shuf -n 5
Unix Stackexchangeからのソース
回答:
以下は、GNUソートのランダムオプションを使用するスクリプトです。
ls |sort -R |tail -$N |while read file; do
# Something involving $file, or you can leave
# off the while to just get the filenames
done
"$file"
スペースの影響を受けやすいのは、表示されていないの使用だけです。
これにはshuf
(GNU coreutilsパッケージの)を使用できます。ファイル名のリストをフィードし、ランダムな順列から最初の行を返すように要求します。
ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..
-n, --head-count=COUNT
値を調整して、必要な行の数を返します。たとえば、5つのランダムなファイル名を返すには、次のようにします。
find dirname -type f | shuf -n 5
N
ランダムなファイルを選択したかったので、使用1
は少し誤解を招きます。
find dirname -type f -print0 | shuf -zn1
ここでは、出力を解析しない可能性がありls
、名前にスペースと面白い記号が含まれているファイルに関して100%安全である可能性があります。それらのすべてがrandf
ランダムなファイルのリストで配列を作成します。この配列はprintf '%s\n' "${randf[@]}"
、必要に応じて簡単に印刷できます。
これは同じファイルを数回出力する可能性がありN
、事前に知っておく必要があります。ここではN = 42を選択しました。
a=( * )
randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )
この機能は十分に文書化されていません。
Nが事前にわからないが、以前の可能性を本当に気に入っている場合は、を使用できますeval
。しかし、それは悪いことであり、N
徹底的にチェックすることなく、ユーザー入力から直接得られないことを本当に確認する必要があります!
N=42
a=( * )
eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
私は個人的に嫌いなeval
ので、この答え!
より簡単な方法(ループ)を使用した場合も同じです。
N=42
a=( * )
randf=()
for((i=0;i<N;++i)); do
randf+=( "${a[RANDOM%${#a[@]}]}" )
done
同じファイルを複数回使用したくない場合:
N=42
a=( * )
randf=()
for((i=0;i<N && ${#a[@]};++i)); do
((j=RANDOM%${#a[@]}))
randf+=( "${a[j]}" )
a=( "${a[@]:0:j}" "${a[@]:j+1}" )
done
注意。これは古い投稿に対する遅い回答ですが、受け入れられた回答はひどいことを示す外部ページへのリンクですバッシュ実践、そしてそれはまたの出力を解析するので、他の答えはあまり良くありませんls
。受け入れられた回答へのコメントは、Lhunathによる優れた回答を示しています。これは明らかに良い習慣を示していますが、OPには正確には回答していません。
"{1..42}"
末尾を残す部分が気に入らなかった"1"
。また、$RANDOM
15ビットのみであり、このメソッドは32767を超えるファイルからは選択できません。
ls | shuf -n 10 # ten random files
ls
。これは、たとえばファイル名に改行が含まれている場合は機能しません。
ls
「クリーン」なファイル名を提供することは保証されていないため、これに依存しないでください。これらの問題がまれまたは珍しいという事実は問題を変更しません。特に、これにはより良い解決策があります。
ls
ディレクトリと空白行を含めることができます。私はfind . -type f | shuf -n10
代わりに何かを提案します。
Pythonがインストールされている場合(Python 2またはPython 3で動作します):
1つのファイル(または任意のコマンドからの行)を選択するには、次を使用します。
ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"
N
ファイル/行を選択するにN
は、次を使用します(注はコマンドの最後にあります。これを番号に置き換えてください)
ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
これは、@ gniourf_gniourfの遅い回答に対するさらに後の回答です。これは、2倍も抜群の最良の回答であるため、私は賛成しました。(1 eval
回は回避のため、もう1回は安全なファイル名処理のためです。)
しかし、この回答で使用されている「あまりよく文書化されていない」機能のもつれを解くのに数分かかりました。Bashのスキルが十分にしっかりしていて、すぐにそれがどのように機能するかがわかる場合は、このコメントをスキップしてください。しかし、私はそうしませんでした、そしてそれを解き明かしたので、それは説明する価値があると思います。
機能#1は、シェル自体のファイルグロビングです。a=(*)
配列を作成します$a
。そのメンバーは現在のディレクトリ内のファイルです。Bashはファイル名のすべての奇妙さを理解しているので、リストは正確であることが保証され、エスケープが保証されているなどls
です。
機能#2は、配列の Bash パラメータ拡張であり、1つが別の配列内にネストされています。これは、から始まり${#ARRAY[@]}
、の長さに拡張され$ARRAY
ます。
次に、その拡張を使用して配列に添え字を付けます。1とNの間の乱数を見つける標準的な方法は、Nを法とする乱数の値を取ることです。0と配列の長さの間の乱数が必要です。ここでは、わかりやすくするために2行に分けたアプローチを示します。
LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}
しかし、このソリューションはそれを1行で実行し、不要な変数の割り当てを削除します。
機能#3はBashブレースの展開ですが、完全には理解していません。ブレース展開は、名前の25個のファイルのリストを生成するために、例えば、使用されているfilename1.txt
、filename2.txt
などを:echo "filename"{1..25}".txt"
。
上記のサブシェル内の式"${a[RANDOM%${#a[@]}]"{1..42}"}"
は、そのトリックを使用して、42の個別の展開を生成します。ブレース展開は、]
との間に1桁を配置します}
。これは、最初は配列に添え字を付けると考えていましたが、そうであれば、コロンが前に付きます。(また、配列内のランダムなスポットから42の連続した項目を返します。これは、配列から42のランダムな項目を返すこととはまったく同じではありません。)シェルに拡張を42回実行させるだけで、配列からの42個のランダムなアイテム。(しかし、誰かがそれをより完全に説明できるならば、私はそれを聞きたいです。)
Nを(42に)ハードコードする必要がある理由は、変数展開の前にブレース展開が行われるためです。
最後に、これが機能#4です。これをディレクトリ階層に対して再帰的に実行する場合は、次のようになります。
shopt -s globstar
a=( ** )
これにより、再帰的に一致させるシェルオプションがオンになります**
。これで、$a
配列には階層全体のすべてのファイルが含まれます。
フォルダーにさらにファイルがある場合は、UNIXのstackexchangeで見つけた以下のパイプコマンドを使用できます。
find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/
ここではファイルをコピーしたかったのですが、ファイルを移動したり何か他のことをしたい場合は、最後に使用したコマンドを変更してcp
ください。
これは、MacOSでbashをうまく使用できる唯一のスクリプトです。次の2つのリンクのスニペットを組み合わせて編集しました。
lsコマンド:ファイルごとに1行ずつ、再帰的なフルパスリストを取得するにはどうすればよいですか?
#!/bin/bash
# Reads a given directory and picks a random file.
# The directory you want to use. You could use "$1" instead if you
# wanted to parametrize it.
DIR="/path/to/"
# DIR="$1"
# Internal Field Separator set to newline, so file names with
# spaces do not break our script.
IFS='
'
if [[ -d "${DIR}" ]]
then
# Runs ls on the given dir, and dumps the output into a matrix,
# it uses the new lines character as a field delimiter, as explained above.
# file_matrix=($(ls -LR "${DIR}"))
file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }'))
num_files=${#file_matrix[*]}
# This is the command you want to run on a random file.
# Change "ls -l" by anything you want, it's just an example.
ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi
exit 0
MacOSにはsort -Rコマンドとshufコマンドがないため、重複なしにすべてのファイルをランダム化するbashのみのソリューションが必要でしたが、ここでは見つかりませんでした。このソリューションはgniourf_gniourfのソリューション#4に似ていますが、うまくいけばコメントが追加されます。
スクリプトは、ifを使用したカウンターまたはNを使用したgniourf_gniourfのforループを使用してNサンプルの後で停止するように簡単に変更できる必要があります。$ RANDOMは〜32000ファイルに制限されていますが、ほとんどの場合これで十分です。
#!/bin/bash
array=(*) # this is the array of files to shuffle
# echo ${array[@]}
for dummy in "${array[@]}"; do # do loop length(array) times; once for each file
length=${#array[@]}
randomi=$(( $RANDOM % $length )) # select a random index
filename=${array[$randomi]}
echo "Processing: '$filename'" # do something with the file
unset -v "array[$randomi]" # set the element at index $randomi to NULL
array=("${array[@]}") # remove NULL elements introduced by unset; copy array
done
私はこれを使用します。これは一時ファイルを使用しますが、通常のファイルを見つけてそれを返すまで、ディレクトリ内を深く行きます。
# find for a quasi-random file in a directory tree:
# directory to start search from:
ROOT="/";
tmp=/tmp/mytempfile
TARGET="$ROOT"
FILE="";
n=
r=
while [ -e "$TARGET" ]; do
TARGET="$(readlink -f "${TARGET}/$FILE")" ;
if [ -d "$TARGET" ]; then
ls -1 "$TARGET" 2> /dev/null > $tmp || break;
n=$(cat $tmp | wc -l);
if [ $n != 0 ]; then
FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
# r=$(($RANDOM % $n)) ;
# FILE=$(tail -n +$(( $r + 1 )) $tmp | head -n 1);
fi ;
else
if [ -f "$TARGET" ] ; then
rm -f $tmp
echo $TARGET
break;
else
# is not a regular file, restart:
TARGET="$ROOT"
FILE=""
fi
fi
done;
ここでKang氏から少し手を加えたPerlソリューションは
どうですか?Unixコマンドラインまたはシェルスクリプトでテキストファイルの行をシャッフルするにはどうすればよいですか?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle(<>); @lines [0..4] 'を印刷する