bashのディレクトリからランダムなファイルを選択するにはどうすればよいですか?


144

約2000ファイルのディレクトリがあります。Nbashスクリプトまたはパイプされたコマンドのリストを使用して、ファイルのランダムなサンプルを選択するにはどうすればよいですか?


1
Unix&Linuxでも良い答えです:unix.stackexchange.com/a/38344/24170
Nikana Reklawyks


回答:


180

以下は、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

クール、ソート-Rを知りませんでした。以前ボゴソートを使用しました:-p
alex

5
sort:無効なオプション-R詳細については、「sort --help」を試してください。

2
スペースが含まれているファイルでは機能しないようです。
Houshalter 2017年

これはスペースを含むファイルで機能するはずです(パイプラインは行を処理します)。改行が含まれている名前では機能しません。"$file"スペースの影響を受けやすいのは、表示されていないの使用だけです。
Yann Vernier 2017


108

これには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

4
OPはNランダムなファイルを選択したかったので、使用1は少し誤解を招きます。
aioobe 14

4
改行を含むファイル名がある場合:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

5
これらのランダムに選択されたファイルを別のフォルダーにコピーする必要がある場合はどうなりますか?これらのランダムに選択されたファイルに対して操作を実行する方法?
Rishabh Agrahari

18

ここでは、出力を解析しない可能性があり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には正確には回答していません。


最初と2番目の「悪い置換」を生成しました。"{1..42}"末尾を残す部分が気に入らなかった"1"。また、$RANDOM15ビットのみであり、このメソッドは32767を超えるファイルからは選択できません。
Yann Vernier 2017

13
ls | shuf -n 10 # ten random files

1
の出力に依存すべきではありませんls。これは、たとえばファイル名に改行が含まれている場合は機能しません。
bfontaine 2017

3
@bfontaineあなたはファイル名の改行に悩まされているようです:)。彼らは本当に一般的ですか?つまり、名前に改行を含むファイルを作成するツールはありますか?ユーザーとしてそのようなファイル名を作成することは非常に難しいので。インターネットからのファイルも
同様

3
@CiprianTomoiagaそれはあなたが得るかもしれない問題の例です。ls「クリーン」なファイル名を提供することは保証されていないため、これに依存しないでください。これらの問題がまれまたは珍しいという事実は問題を変更しません。特に、これにはより良い解決策があります。
bfontaine

lsディレクトリと空白行を含めることができます。私はfind . -type f | shuf -n10代わりに何かを提案します。
cherdt

9

選択するためのシンプルなソリューション 5lsの解析回避しながらランダムファイルを。また、スペース、改行、その他の特殊文字を含むファイルでも機能します。

shuf -ezn 5 * | xargs -0 -n1 echo

echoファイルに対して実行するコマンドに置き換えます。


1
+パイプにreadは解析と同じ問題がありませんlsか?つまり、1行
ずつ読み取る

3
あなたが正しいです。私の以前の解決策は、改行を含むファイル名では機能せず、おそらく特定の特殊文字を持つ他のソリューションでも壊れていました。改行の代わりにnull終了を使用するように回答を更新しました。
scai

4

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

ファイル名に改行が含まれている場合、これは機能しません。
bfontaine 2017

4

これは、@ 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行で実行し、不要な変数の割り当てを削除します。

機能#3Bashブレースの展開ですが、完全には理解していません。ブレース展開は、名前の25個のファイルのリストを生成するために、例えば、使用されているfilename1.txtfilename2.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配列には階層全体のすべてのファイルが含まれます。


2

フォルダーにさらにファイルがある場合は、UNIXのstackexchangeで見つけた以下のパイプコマンドを使用できます。

find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/

ここではファイルをコピーしたかったのですが、ファイルを移動したり何か他のことをしたい場合は、最後に使用したコマンドを変更してcpください。


1

これは、MacOSでbashをうまく使用できる唯一のスクリプトです。次の2つのリンクのスニペットを組み合わせて編集しました。

lsコマンド:ファイルごとに1行ずつ、再帰的なフルパスリストを取得するにはどうすればよいですか?

http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/

#!/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

1

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

0

私はこれを使用します。これは一時ファイルを使用しますが、通常のファイルを見つけてそれを返すまで、ディレクトリ内を深く行きます。

# 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;

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.