シェルスクリプトのディレクトリからランダムファイルを選択する最良の方法は何ですか?
Bashでの私のソリューションを次に示しますが、Unixで使用するためのより移植性の高い(非GNU)バージョンに非常に興味があります。
dir='some/directory'
file=`/bin/ls -1 "$dir" | sort --random-sort | head -1`
path=`readlink --canonicalize "$dir/$file"` # Converts to full path
echo "The randomly-selected file is: $path"
誰か他のアイデアがありますか?
編集: lhunathは、ls
の解析について良い点を示しています。ポータブルにしたいかどうかにかかっていると思います。 GNU findutilsおよびcoreutilsがある場合は、次を実行できます。
find "$dir" -maxdepth 1 -mindepth 1 -type f -print0 \
| sort --zero-terminated --random-sort \
| sed 's/\d000.*//g/'
ふう、楽しかった!また、「ランダムファイル」と言ったので、私の質問によく一致します。しかし、最近ではGNUがインストールされているがPerl 5はインストールされていないUnixシステムが展開されていることを想像するのは困難です。
files=(/my/dir/*)
printf "%s\n" "${files[RANDOM % ${#files[@]}]}"
そして、lsを解析しません。読み取り http://mywiki.wooledge.org/ParsingLs
編集:信頼できる_bash
以外のソリューションを見つけて頑張ってください。ほとんどの場合、スペースや改行、ダッシュを含むファイル名など、特定の種類のファイル名では壊れます(純粋なsh
ではほとんど不可能です)。 bash
なしで正しく行うには、awk
/Perl
/python
/...に完全に移行する必要があります。処理など。
「shuf」はポータブルではありませんか?
shuf -n1 -e /path/to/files/*
または、ファイルが1つのディレクトリよりも深いかどうかを確認します。
find /path/to/files/ -type f | shuf -n1
これはcoreutilsの一部ですが、それを入手するには6.4以降が必要です... RH/CentOSには含まれていません。
# ******************************************************************
# ******************************************************************
function randomFile {
tmpFile=$(mktemp)
files=$(find . -type f > $tmpFile)
total=$(cat "$tmpFile"|wc -l)
randomNumber=$(($RANDOM%$total))
i=0
while read line; do
if [ "$i" -eq "$randomNumber" ];then
# Do stuff with file
amarok $line
break
fi
i=$[$i+1]
done < $tmpFile
rm $tmpFile
}
何かのようなもの:
let x="$RANDOM % ${#file}"
echo "The randomly-selected file is ${path[$x]}"
bashの$RANDOM
は、乱数を返す特別な変数です。次に、モジュラス除算を使用して有効なインデックスを取得し、そのインデックスを配列で参照します。
これは、POSIX機能のみに依存し、任意のファイル名に対応する(選択からドットファイルを省略した)シェルスニペットです。ランダム選択ではawkを使用します。POSIXで取得できるのはそれだけだからです。 awkのRNGには現在の時刻が秒単位でシードされるため、非常に貧弱な乱数ジェネレーターです(簡単に予測でき、1秒に複数回呼び出すと同じ選択を返します)。
set -- *
n=$(echo $# | awk '{srand(); print int(Rand()*$0) + 1}')
eval "file=\$$n"
echo "Processing $file"
ドットファイルを無視したくない場合は、ファイル名生成コード(set -- *
)をより複雑なものに置き換える必要があります。
set -- *; [ -e "$1" ] || shift
set .[!.]* "$@"; [ -e "$1" ] || shift
set ..?* "$@"; [ -e "$1" ] || shift
if [ $# -eq 0]; then echo 1>&2 "empty directory"; exit 1; fi
OpenSSLを使用できる場合は、それを使用してランダムバイトを生成できます。そうではないが、システムに/dev/urandom
がある場合、openssl
への呼び出しをdd if=/dev/urandom bs=3 count=1 2>/dev/null
に置き換えます。 n
を1から$#
の間のランダムな値に設定し、バイアスを導入しないように注意するスニペットです。このスニペットは、$#
が最大2 ^ 23-1であることを前提としています。
while
n=$(($(openssl Rand 3 | od -An -t u4) + 1))
[ $n -gt $((16777216 / $# * $#)) ]
do :; done
n=$((n % $#))
Bashで次の操作を行うことにより、ファイル名の改行を回避できます。
#!/bin/sh
OLDIFS=$IFS
IFS=$(echo -en "\n\b")
DIR="/home/user"
for file in $(ls -1 $DIR)
do
echo $file
done
IFS=$OLDIFS
要するに、移植可能な方法でUnixスクリプトで乱数を作成するにはどうすればよいですか?
1とNの間の乱数がある場合、_head -$N | tail
_を使用して中央のどこかをカットできるからです。残念ながら、シェルだけでこれを実行するポータブルな方法はありません。 PythonまたはPerl)があれば、ランダムサポートを簡単に使用できますが、標準のRand(1)
コマンドはありません。
Awkは乱数を取得するための優れたツールだと思います。 Advanced Bash Guide によると、Awkは$RANDOM
。
Bash-ismsとGNU=ツールを回避するバージョンのスクリプトを次に示します。
#! /bin/sh
dir='some/directory'
n_files=`/bin/ls -1 "$dir" | wc -l | cut -f1`
Rand_num=`awk "BEGIN{srand();print int($n_files * Rand()) + 1;}"`
file=`/bin/ls -1 "$dir" | sed -ne "${Rand_num}p"`
path=`cd $dir && echo "$PWD/$file"` # Converts to full path.
echo "The randomly-selected file is: $path"
ファイルに改行が含まれている場合、他の回答で言及されている問題を継承します。
BusyBox(組み込みデバイスで使用)は通常$RANDOM
をサポートするように構成されますが、bashスタイルの配列やsort --random-sort
またはshuf
を持ちません。したがって、次のとおりです。
#!/bin/sh
FILES="/usr/bin/*"
for f in $FILES; do echo "$RANDOM $f" ; done | sort -n | head -n1 | cut -d' ' -f2-
cut -f2-
の末尾の「-」に注意してください。これは、スペースを含むファイル(または使用する区切り文字)を切り捨てないようにするために必要です。
改行が埋め込まれたファイル名は正しく処理されません。
コマンド「ls」からの出力の各行をlineという名前の連想配列に入れて、次のようなものを選択します...
ls | awk '{ line[NR]=$0 } END { print line[(int(Rand()*NR+1))]}'
私の2セント、特殊文字を含むファイル名が存在する場合に壊れてはいけないバージョン:
#!/bin/bash --
dir='some/directory'
let number_of_files=$(find "${dir}" -type f -print0 | grep -zc .)
let Rand_index=$((1+(RANDOM % number_of_files)))
printf "the randomly-selected file is: "
find "${dir}" -type f -print0 | head -z -n "${Rand_index}" | tail -z -n 1
printf "\n"