web-dev-qa-db-ja.com

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

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

119
Marlo Guthrie

GNU sortのランダムオプションを使用するスクリプトは次のとおりです。

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
156
Josh Lee

そのために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
84

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
    

。これは古い投稿に対する遅い回答ですが、受け入れられた回答はひどい bash プラクティスを示す外部ページにリンクしており、他の回答はls。受け入れられた回答へのコメントは、明らかに優れた実践を示しているが、OPに正確に答えていないLhunathによる優れた回答を示しています。

18
gniourf_gniourf
ls | shuf -n 10 # ten random files
9
silgon

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

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

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

7
scai

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
4
Mark

これは@gniourf_gniourfの遅い回答に対するさらに後の応答です。 (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バッシュブレースの展開 ですが、完全に理解しているわけではないことを告白しなければなりません。たとえば、ブレース展開は、filename1.txtfilename2.txtなどという名前の25個のファイルのリストを生成するために使用されます:echo "filename"{1..25}".txt"

上記のサブシェル内の式"${a[RANDOM%${#a[@]}]"{1..42}"}"は、そのトリックを使用して42個の個別の展開を生成します。中括弧の展開は、]}の間に1桁の数字を配置します。最初は配列に添え字を付けると考えましたが、そうであればコロンが前に付きます。 (また、配列内のランダムスポットから42の連続したアイテムを返していましたが、これは配列から42のランダムアイテムを返すこととはまったく同じではありません。)配列から42個のランダムアイテム。 (しかし、誰かがそれをより完全に説明できるなら、私はそれを聞きたいです。)

Nを(42に)ハードコーディングする必要がある理由は、変数展開の前にブレース展開が発生するためです。

最後に、ディレクトリ階層に対して再帰的にこれを実行したい場合、機能#4を次に示します。

shopt -s globstar
a=( ** )

これにより、 シェルオプション がオンになり、**が再帰的に一致します。これで、$a配列には、階層全体のすべてのファイルが含まれます。

4
Ken

MacOSにはsort -Rおよびshufコマンドがないため、すべてのファイルをランダム化するbashのみのソリューションが必要でした重複なしで、ここでは見つかりませんでした。このソリューションはgniourf_gniourfのソリューション#4に似ていますが、うまくいけばより良いコメントが追加されます。

スクリプトは、ifまたはnのあるgniourf_gniourfのforループを使用して、N個のサンプルの後に停止するように簡単に変更できる必要があります。

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

これは、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
benmarbles

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

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

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

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

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

0