我有一个包含大约2000个文件的目录。如何通过使用bash脚本或管道命令列表来选择N
文件的随机样本?
答案 0 :(得分:145)
这是一个使用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
答案 1 :(得分:82)
您可以使用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
答案 2 :(得分: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
注意的。这是旧帖子的迟到答案,但是接受的答案链接到显示可怕的bash练习的外部页面,而另一个答案并不是更好,因为它还解析ls
的输出。对接受的答案的评论指出了Lhunath的一个很好的答案,这显然表明了良好的做法,但并没有完全回答OP。
答案 3 :(得分:7)
在avoiding to parse ls期间选择5
个随机文件的简单解决方案。它还适用于包含空格,换行符和其他特殊字符的文件:
shuf -ezn 5 * | xargs -0 -n1 echo
将echo
替换为您要为文件执行的命令。
答案 4 :(得分:7)
ls | shuf -n 10 # ten random files
答案 5 :(得分:4)
如果安装了Python(适用于Python 2或Python 3):
要选择一个文件(或来自任意命令的行),请使用
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
答案 6 :(得分:4)
这是对@ gniourf_gniourf的迟到答案的后来回复,我刚刚赞成,因为它是迄今为止最好的答案,两次结束。 (一次用于避免eval
,一次用于安全文件名处理。)
但是我花了几分钟时间解开了#34;没有很好的记录&#34;这个答案使用的功能。如果您的Bash技能足够坚实,您可以立即看到它是如何工作的,那么请跳过此评论。但我没有,并且解开它我认为值得解释。
功能#1 是shell自己的文件通配符。 a=(*)
创建一个数组$a
,其成员是当前目录中的文件。 Bash理解文件名的所有奇怪之处,因此保证列表正确,保证转义等。无需担心正确解析ls
返回的文本文件名。
功能#2 是parameter expansions的Bash arrays,其中一个嵌套在另一个中。这从${#ARRAY[@]}
开始,扩展到$ARRAY
的长度。
然后使用该扩展来下标数组。找到1到N之间的随机数的标准方法是取模数为N的随机数的值。我们想要一个介于0和数组长度之间的随机数。这是方法,为了清楚起见分为两行:
LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}
但是这个解决方案在一行中完成,删除了不必要的变量赋值。
功能#3 是Bash brace expansion,虽然我必须承认我并不完全理解它。例如,使用大括号扩展来生成名为filename1.txt
,filename2.txt
等的25个文件的列表:echo "filename"{1..25}".txt"
。
上面子shell中的表达式"${a[RANDOM%${#a[@]}]"{1..42}"}"
使用该技巧生成42个单独的扩展。大括号扩展在]
和}
之间放置一个数字,起初我认为是在下标数组,但如果是这样,它前面会有一个冒号。 (它也会从数组中的一个随机点返回42个连续项,这与从数组中返回42个随机项完全不同。)我认为它只是让shell运行扩展42时间,从而返回阵列中的42个随机项。 (但如果有人能够更充分地解释它,我很乐意听到它。)
N必须被硬编码(到42)的原因是支撑扩展在变量扩展之前发生。
最后,在这里功能#4 ,如果你想以递归方式为目录层次结构执行此操作:
shopt -s globstar
a=( ** )
这会打开shell option,导致**
递归匹配。现在,您的$a
数组包含整个层次结构中的每个文件。
答案 7 :(得分:1)
这是我在MacOS上与bash玩得很好的唯一脚本。我合并并编辑了以下两个链接的片段:
ls command: how can I get a recursive full-path listing, one line per file?
#!/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
答案 8 :(得分:1)
MacOS没有 sort -R 和 shuf 命令,所以我需要一个仅限bash的解决方案,随机化所有文件无重复和在这里没找到。此解决方案类似于gniourf_gniourf的解决方案#4,但希望添加更好的评论。
脚本应该很容易修改,以便在使用带有if的计数器的N个样本后停止,或者使用带有N. $ RANDOM的gniourf_gniourf循环限制为~32000个文件,但在大多数情况下应该这样做。 / p>
accept
答案 9 :(得分: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;
答案 10 :(得分:0)
如果文件夹中有更多文件,则可以使用我在Unix stackexchange中找到的以下管道命令。
find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/
这里我想复制文件,但是如果要移动文件或执行其他操作,只需更改我使用过cp
的最后一条命令即可。
答案 11 :(得分:-1)
在这里,康先生稍微篡改了一个Perl解决方案怎么样:
How can I shuffle the lines of a text file on the Unix command line or in a shell script?
$ ls | perl -MList :: Util = shuffle -e&#39; @lines = shuffle(&lt;&gt;);打印 @lines [0..4]&#39;