【发布时间】:2009-12-17 21:26:15
【问题描述】:
我有一个 bash 脚本,它使用类似循环的方式处理目录中的所有文件
对于 *.txt 中的 i 做 操作...... 完成
有数千个文件,由于“*.txt”扩展,它们总是按字母数字顺序处理。
有没有一种简单的方法来随机排序并仍然确保我只处理所有文件一次?
【问题讨论】:
标签: bash
我有一个 bash 脚本,它使用类似循环的方式处理目录中的所有文件
对于 *.txt 中的 i 做 操作...... 完成
有数千个文件,由于“*.txt”扩展,它们总是按字母数字顺序处理。
有没有一种简单的方法来随机排序并仍然确保我只处理所有文件一次?
【问题讨论】:
标签: bash
假设文件名没有空格,只需替换 List::Util::shuffle 的输出即可。
for i in `perl -MList::Util=shuffle -e'$,=$";print shuffle<*.txt>'`; do
....
done
如果文件名包含空格但没有嵌入换行符或反斜杠,则一次读取一行。
perl -MList::Util=shuffle -le'$,=$\;print shuffle<*.txt>' | while read i; do
....
done
为了在 Bash 中完全安全,请使用以 NUL 结尾的字符串。
perl -MList::Util=shuffle -0 -le'$,=$\;print shuffle<*.txt>' |
while read -r -d '' i; do
....
done
效率不是很高,但如果需要,可以在纯 Bash 中执行此操作。 sort -R 在内部做了类似的事情。
declare -a a # create an integer-indexed associative array
for i in *.txt; do
j=$RANDOM # find an unused slot
while [[ -n ${a[$j]} ]]; do
j=$RANDOM
done
a[$j]=$i # fill that slot
done
for i in "${a[@]}"; do # iterate in index order (which is random)
....
done
或者使用传统的 Fisher-Yates 洗牌。
a=(*.txt)
for ((i=${#a[*]}; i>1; i--)); do
j=$[RANDOM%i]
tmp=${a[$j]}
a[$j]=${a[$[i-1]]}
a[$[i-1]]=$tmp
done
for i in "${a[@]}"; do
....
done
【讨论】:
a[j]=${a[i-1]}。 man bash 还说“旧格式 $[expression] 已弃用,将在即将发布的 bash 版本中删除。” (支持$(()),例如在你的j=$[RANDOM%i])
你可以通过排序命令来管道你的文件名:
ls | sort --random-sort | xargs ....
【讨论】:
sort (GNU coreutils) 6.10
这是一个依赖于 awk 中非常基本的功能的答案,因此它应该可以在 unice 之间移植。
ls -1 | awk '{print rand()*100, $0}' | sort -n | awk '{print $2}'
编辑:
ephemient 提出了一个很好的观点,即上述内容不是空间安全的。这是一个版本:
ls -1 | awk '{print rand()*100, $0}' | sort -n | sed 's/[0-9\.]* //'
【讨论】:
如果你有 GNU coreutils,你可以使用shuf:
while read -d '' f
do
# some stuff with $f
done < <(shuf -ze *)
这适用于名称中包含空格或换行符的文件。
题外话编辑:
在评论中说明SiegeX的观点:
$ a=42; echo "Don't Panic" | while read line; do echo $line; echo $a; a=0; echo $a; done; echo $a
Don't Panic
42
0
42
$ a=42; while read line; do echo $line; echo $a; a=0; echo $a; done < <(echo "Don't Panic"); echo $a
Don't Panic
42
0
0
管道导致while 在子shell 中执行,因此子shell 中变量的更改不会流回父shell。
【讨论】:
shuf -ze * | while read 而不是done < <(shuf -ze *),但实际上它们是相同的。
done < <() 的原因是它与done < filename 相似(避免不必要地使用cat)。
< <(foo) 的另一个好处是它不会像管道方法那样创建子shell。
这是一个使用标准 unix 命令的解决方案:
for i in $(ls); do echo $RANDOM-$i; done | sort | cut -d- -f 2-
【讨论】:
ls、sort 和 cut 不是纯 Bash 命令。在包含嵌入换行符的文件名的可怕情况下也会失败。
这是一个 Python 解决方案,如果它在您的系统上可用的话
import glob
import random
files = glob.glob("*.txt")
if files:
for file in random.shuffle(files):
print file
【讨论】:
if file.endswith('.txt')。或者你可以把它变成更通用的东西,比如shuf...